- 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
Here we go with challenge 2 of our little coding challenge! What’s with the tomato? Well, this challenge was to make a pomodoro timer, check this out:
Our second challenge gets more tangy! We’ll be building a Pomodoro timer.
To provide some background, the Pomodoro Technique is a time management method developed by Francesco Cirillo in the late 1980s. The technique uses a timer to break down work into intervals, traditionally 25 minutes in length, separated by short breaks. Each interval is known as a pomodoro, from the Italian word for ‘tomato’, after the tomato-shaped kitchen timer that Cirillo used as a university student.
What should it do?
The timer should allow the user to set a 25 minute timer, to focus on the task at hand, followed by a 5 minute break. Every 4th break should be a long 15 minute break.
Lots of room to explore here! You can build it as a browser extension, mobile app, desktop app, CLI, Amazon Alexa skill or even as a VSCode extension.
Acceptance Criteria
- Take a button click or command to start the timer
- Show time left on timer both during focus time and during the break
- Prompt user when the focus period or break ends
- Allow user to pause and reset the timer
- Show the total pomodoros completed on a given day
Extra Credit
- Custom intervals – User should be able to set custom intervals for focus time and breaks e.g. 30 minute focus period followed by 10 minute breaks and a 30 minute long break
- Sounds – Factor in a ding once the timer ends and a gong when the break ends
- Raspberry Pi – Extra credit for sure if you dust off that Raspberry Pi or Arduino and build a Pomodoro timer with a physical button
Don’t get too excited, I didn’t accomplish all of the above, however, I did figure out a few things from challenge 1 so I still call this a win. Putting a timer-based app purely in a REST endpoint is just silly. Clearly, an app like this is best handled with a front-end application, something that can more immediately manage time for the user. But alas, I’m leaning Java so I built enough in the back end to prove I can do it. I did not cover every requirement, again, because a timer in an API is just silly.
I completed ACs 1 & 2, but really just focused on the first 25 minutes. As a REST end point, I can’t do 3 (other than prompting the user through another method like sending a text, but that’s outside of me wanting to learn Java right now).
So let’s get to it!
What I built
I built 2 REST endpoints, one to start the timer and one to get the status. I figured if there was a front-end to this service, they would submit their request to start, then continue to ping the service to get a timer update. Again, poor design to put this in an API (last time I’ll bring this up).
Here’s my walkthrough of what I built:
AC 1: Start the timer
Super simple, call the start endpoint and you’ll get back your timer,
/start
{
"id": 2,
"startTime": "2021-05-06T07:17:23.403958",
"duration": 25,
"remainingSeconds": 1499.0,
"_links": {
"self": {
"href": "http://localhost:8080/start"
},
"status": {
"href": "http://localhost:8080/status?id=2"
}
}
}
Hey hey, see that _links
? That’s HATEOAS working! Learned that in this challenge 😎 .
AC 2: Get the timer status
Note the status
link in the response above? I can call that to get the status of my timer, how many seconds remain.
/status?id=2
{
"id": 2,
"startTime": "2021-05-06T07:17:23.403958",
"duration": 25,
"remainingSeconds": 817.0,
"_links": {
"self": {
"href": "http://localhost:8080/status?id=2"
}
}
}
Now for some code
Check out all of the code here: https://github.com/DavidLozzi/SlalomCodingChallenge/tree/main/2_pomodoro.
Here’s my primary controller with the two end points, located at: /2_pomodoro/src/main/java/com/davidlozzi/pomodoro/PomodoroApplication.java
@GetMapping("/start")
@ResponseBody
public ResponseEntity start() {
try {
long timerId = counter.incrementAndGet();
Timer newTimer = new Timer(timerId, LocalDateTime.now().toString(), 25);
JsonUtil.addTimer(Long.toString(timerId), newTimer);
newTimer.add(linkTo(methodOn(PomodoroApplication.class).start()).withSelfRel());
newTimer
.add(linkTo(methodOn(PomodoroApplication.class).status(Long.toString(newTimer.getId()))).withRel("status"));
return new ResponseEntity<>(newTimer, HttpStatus.OK);
} catch (Exception ex) {
System.out.print(ex);
RuntimeException en = new RuntimeException(ex.toString());
return new ResponseEntity<>(en, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@GetMapping("/status")
@ResponseBody
public ResponseEntity status(@RequestParam(value = "id", defaultValue = "") String id) {
try {
if (!id.equals(null) && !id.equals("")) {
long timerId = Long.parseLong(id);
Timer timer = JsonUtil.getTimer(timerId);
timer.add(linkTo(methodOn(PomodoroApplication.class).status(Long.toString(timer.getId()))).withSelfRel());
return new ResponseEntity<>(timer, HttpStatus.OK);
} else {
throw new Exception("No id provided");
}
} catch (Exception ex) {
System.out.print(ex);
RuntimeException en = new RuntimeException(ex.toString());
return new ResponseEntity<>(en, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
start()
- Doesn’t receive any parameters
- Creates a new ID using an
AtomicLong
- Creates a new
Timer
object, which extendsRepresentationModel
to allow for HATEOAS support - Similar
JsonUtil
from the first challenge, writes this timer object to a JSON file for later retrieval - Then it adds two links to the timer object, these are the actual HATEOAS links
- My error handling has improved here as well, I now have a custom error object and I return that if there is an error above.
status()
- Receives one parameter, the id of the timer
- Using the
JsonUtil
I retrieve the timer from the JSON file - I add a self referencing link, supporting HATEOAS
- Then return the timer object
- Same error handling here
How I improved over last challenge
I’m feeling the Java juices, or coffee, flowing now. This was definitely faster to build this time. There were two major points from the last challenge that I wanted to improve upon here:
Error Handling
I am now returning a custom error object in the event an error occurs, yay! Big thanks to Bruno’s Exception Handling repo example. Although I found this example bigger than I needed, it helped me understand how to do it. Well, how to do it for now. 😉 I suspect I’m not thinking it through correctly, missing a big piece, or something. Whatever the case may be, what I did in this challenge is a good start. Next challenge I’ll continue to improve it (like removing the stack trace).
Here’s an example of an error response:
{
"cause": null,
"stackTrace": [
{
"classLoaderName": "app",
"moduleName": null,
"moduleVersion": null,
"methodName": "status",
.......
"fileName": "Thread.java",
"lineNumber": 834,
"className": "java.lang.Thread",
"nativeMethod": false
}
],
"message": "java.lang.NullPointerException",
"suppressed": [],
"localizedMessage": "java.lang.NullPointerException"
}
HATEOAS
I added HATEOAS support! This was surprisingly easy and I found it immeditaely helpful as the start
end point returns the exact URI I need to get the status of that timer.
Want to learn more about HATEOAS, check out Spring’s guide, I found this incredibly helpful. I will also write another post on how I did it here soon.
How can I make this better
As always, I’d like to hear from you, what did you think? How could I make this better? Obviously, not better by completing all of the ACs, but the Java code itself. Here’s what I think:
- Although I figured out the error handling piece that eluded me in Challenge 1. The example repo is great, but I think more complex than I needed here. After I spent some time with it and learned what it was doing, I was able to reduce the large example down some. It works, however, it’s literally the error message from the code. I should be returning a more consumer-friendly error message, something that helps the consumer know why and what errored.
- Colleagues recommended I check out lombok, and I’m glad I did! Its ability to build my constructors on a POJO (plain old Java object) is fantastic, all I need to do is create the props, and it builds the getters and setters. Love it! However, after I added HATEOAS, it didn’t work as expected. I skipped it for now and will check it out next challenge.
What am I missing? Leave a comment below!
Onto the next challenge, with star wars!