- I’ll be Learning Java Spring Boot through a Coding Challenge, join me!
- Challenge #1 – A Calculator, a RESTful man, and a Springy Boot walk into a bar…
- Challenge #2 – Staying focused with a tomato
- Challenge #3 – These are not the droids we’re looking for.
- Challenge #4 – Taking what’s long and making it short
Ha, that’s all I got. As I mentioned earlier, I’m going through a fun little coding challenge with my team at Slalom. Our first challenge is to create a calculator. Since I’m learning Spring Boot and specifically creating REST APIs, I get to create this calculator as an API… But first, the requirements, here’s what our team had to work with:
What should it do?
It should allow the user to specify a calculation and get the correct answer. For example, the user may specify “2 + 2” and their correct answer is “4”. The user can continue with the math, like specifying “- 3” and the answer should be “1”.
If building a UI, consider building a real calculator, enabling the user to press on a button to specify their desired value.
If building an endpoint or CLI, consider providing basic instructions to the user in the response and what type of calculations are supported.
Acceptance Criteria
- Accept a number, an operator, and another number, and provide the answer
- Allow the user to continue to perform calculations on the previous answer by appending another operator and number
- Allow the user to clear the current calculation to start over
Extra Credit!
- Support grouping of calculations with parenthesis
- Illustrate the math with an abacus
Pretty straightforward, a calculator. Our goal for this first one was to keep it uber simple for those of us learning something new, we can spend more time learning the thing than building it.
I’m pleased to share that I completed ACs 1, 2, and 3! and Extra Credit #1. Who knows how to use an abacus? :D
What I built
I built a basic REST API that accepts a calculation and returns the answer. It also gives you the option to continue a calculation off of a previous calculation.
Here’s a video walk through:
AC 1: Do the math
Pretty straight forward, send in the query parameter calculation
and you’ll get an answer:
/calculate?calculation=2%2B2
{
"id": 1,
"answer": 4.0,
"calculation": "2+2"
}
Pluses are unique, and have to be URI encoded to %2B
, so 2+2 is 2%2B2
.
AC 2: Continue the math
The consumer should be able to do something like 2+2 then *4 to get 16. To continue the math, we can add the id parameter for the previous answer.
/calculate?calculation=*4&id=1
{
"id": 2,
"answer": 16.0,
"calculation": "4*4"
}
AC 3: Clear and start fresh
That just happens with AC1, don’t send an id, and you’ll get a fresh start each time.
Extra Credit 1: Parenthesis
As we all know, math happens in the innermost parenthesis first, then works outwards. 3+3*10 is 33, but (3+3)*10 is 60. Fortunately, I didn’t have to do anything to support this! :D My method for handling the calculation handles parenthesis automatically. Extra credit, done.
Here’s Some Code
Here’s the code: https://github.com/DavidLozzi/SlalomCodingChallenge/tree/main/1_calculator.
The primary controller/end point is at /1_calculator/src/main/java/com/davidlozzi/calculator/CalculatorApplication.java. Here’s the entry point:
public ResponseEntity<Answer> calculate(@RequestParam(value = "calculation", defaultValue = "") String calculation,
@RequestParam(value = "id", defaultValue = "") String id) {
try {
if (!calculation.equals(null) && !calculation.equals("")) {
String newCalc = calculation;
if (id != null && !id.equals("")) {
String existingValue = JsonUtil.getString(id);
newCalc = existingValue + newCalc;
}
double result = Math.calculate(newCalc);
long resultId = counter.incrementAndGet();
Answer answer = new Answer(resultId, result, newCalc);
JsonUtil.setString(Long.toString(resultId), Double.toString(result));
return new ResponseEntity<>(answer, HttpStatus.OK);
} else {
String history = JsonUtil.getAll();
Answer answer = new Answer(0, 0, history);
return new ResponseEntity<>(answer, HttpStatus.BAD_REQUEST);
}
} catch (ScriptException ex) { // not sure I like how this is done
System.out.print(ex);
Answer answer = new Answer(0, 0, badMathMessage);
return new ResponseEntity<>(answer, HttpStatus.BAD_REQUEST);
} catch (Exception ex) {
System.out.print(ex);
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
- The
calculation
is received as a query parameter. Decided this overPOST
ing a body as it’s simpler and we’re not sending a ton of data. - If the consumer provides an
id
parameter then it checks the history for that id and adds that answer to the current calculation. - I created a little utility
JsonUtil
for reading and writing to a JSON file for some persistent storage. I suspect I’ll be using this more in future challenges. - I created a
Math
class (favorite class in high school) that handles the calculation. Funny enough, it uses a JavaScript engine to perform the calculation. As much as I try, I can’t leave JavaScript :D - I then return the
Answer
to the user. - If the user doesn’t provide a calculation parameter, I return the entire history of calculations so the consumer can pick one to continue from.
- Any errors, I return to the user, in the
Answer
object (so frustrating, see below).
How I can do it better?
Here’s what I want to do differently, better:
- As you saw above, to continue your calculations you have to make this other call, which is okay, but I should be using HATEOAS to provide the consumer the correct links to link to, to continue the math. I found this tutorial, and I’ll work on it next challenge, and maybe come back to this one.
- I seriously need to improve my error handling, or rather, returning the right object for the right use case. Right now, I’m returning errors to the consumer using the same
Answer
object I do for the correct answer. It works but it’s really not correct. I found this repo that has a great example, but I couldn’t get it working, I’ll aim to get this to work on the next challenge, or maybe #3. - I’m using a JSON file as persistent storage, which suffices for the requirements, however, if this was real, the NFRs (non-functional requirements) would dictate that I need to host this in the cloud, and the storage would have to persist even after API rebuilds/deployments. I don’t plan on going into AWS during this challenge, but might afterwards, look to move it all into the cloud. We’ll see ;)
What do you see? Where else can I improve? PLEASE TELL ME :D I am learning and can only do so if I know what I can do better. Leave a comment below or tweet me.
On to the next challenge, getting tangy with tomatoes!