(copied from task repo)
Given the list of recipe names, start dates and tray numbers outlined below, create an application that queries the Recipe API (see info below) and generates a JSON file with a schedule. The schedule should outline at exactly what time light and water commands should be sent to the Tower, and what the light intensity or amount to water should be.
The dates in the schedule should be in UTC.
{
input: [
{
trayNumber: 1,
recipeName: "Basil",
startDate: "2022-01-24T12:30:00.0000000Z"
},
{
trayNumber: 2,
recipeName: "Strawberries",
startDate: "2021-13-08T17:33:00.0000000Z"
},
{
trayNumber: 3,
recipeName: "Basil",
startDate: "2030-01-01T23:45:00.0000000Z"
}
]
}
- Clone this repo incl. submodules (the task repo was pulled in as a submodule)
git clone --recurse-submodules https://github.com/LasseWolter/IGS-TechTest-RecipeSchedulingAPI.git
cd
into the cloned repo and rundocker-compose up
- You'll now have the both the SchedulingAPI and RecipeAPI running
- RecipeAPI: http:localhost:8080
- ScheduleAPI: http:localhost:8090
- Navigate to http:localhost:8090/swagger
- You'll see three endpoints:
- Both POST endpoints are prepopulated with sensible example data, so you can send requests directly from SwaggerUI
- Use the Try it out button on the
api/v1/schedule/multiple
to run the example input from section Objective
- Given that the data is passed in as JSON and JSON should be returned, creating an API seems sensible
- A simple WebAPI endpoint processing the input JSON and returning the resulting JSON
- I could possibly add an extra feature where I can process a single input/not just a list
I added REMARK:
code comments throughout the whole codebase.
These comments document things like:
- Why did I make a certain design decision?
- Are there alternatives to this approach?
- What would I do differently to make this code ready for production
In addition, the following section outlines some design decisions that I wanted to highlight.
-
I decided to use API-Controllers instead of minimal API
- For this simple example a minimal API would have been sufficient
- However, I like having the code separate
- Also, in case you wanted to extend this API, it's nice to have separate controllers for different routes
-
I decided to use composition instead of inheritance
- I prefer having all logic/properties defined in one class instead of having to jump up the inheritance chain to find the properties.
- However, I'm always happy to convinced otherwise. One of my fav concepts is 'Disagree and Commit' - let's discuss it and then decide what's best for the situation.
-
I decided to use one command type for both watering and lighting commands
- Unused fields will just be null
- Could make a case for using separate forms to send less data over the wire
- Having a single model allows for easy and fast development for now. I would revisit this for produdction code
-
I decided against using a custom JSON serializer and handle invalid dates by using a custom setter
- In hindsight, I would have chosen a custom JSON serializer
- Initially, I thought a custom JSON serializer would be overkill and found my solution with a custom setter quite neat
- However, after working on this project for a bit, I'd say using a custom JSON serializer would be better because
- it's a common standard
- if an error occurs, it's obvious that it happened during the parsing stage
- it allows returning an error early and not storing data that's invalid
- My current approach could also return early, but I feel like it would be less clean
- Another downside of the current code, it does not return an error message stating that a date was invalid (see section Outstanding Issues), it simply logs the error and
- skips the request (if it's part of a list)
- returns a 500 (if it's the only request/all requests are invalid)
-
I'm cautious when it comes to sensitive data - e.g.
appsettings.Development.json
- I added
appsettings.Development.json
to .gitignore- Depending on the dev setup this file could contains sensitive information. I'd opt for not commiting it to source control.
- for production: defo use secret management
- for dev: ideally use secret managment. at least keep settings local and NEVER commit sensitive configs
- Depending on the dev setup this file could contains sensitive information. I'd opt for not commiting it to source control.
- I added
appsettings.Development.json
to .dockerignore- currently without effect, see section Outstanding Issues
- I added
These are just a handful of decisions. Obviously coding includes almost infinite choice (I think that's part of the fun). I'm happy to answer any other questions during the next interview.
**/appsettings.Development.json
in my .dockerignore doesn't work- I'm not sure why and would debug this further before moving to production
- Including files that shouldn't be included in your docker-image poses a security risk
- I'd advise against using the
COPY . .
command and explicitly state files you want to copy for a production app
- I'd advise against using the
- API doesn't return a message to state that dates are invalid
The main issue is lack of feedback. All the code you see was solely written by me without discussing anything with other engineers or product. This approach can easily lead to a final product that doesn't meet the requirements.
That put aside, here are a few improvements on code level:
- enable HTTPS
- add proper monitoring
- move endpoints to
appsettings.json
- currently hardcoded in the controller - add more unit tests
- for JSON parsing
- for other parts of the scheduling logic
- factor out the HttpService and add caching
I enjoyed this little challenge. It got me thinking about different parts of the development lifecycle and gave me the opportunity to practice some skills I haven't used in a while (like Docker).
Towards the end of the challenge, I was questioning the usefulness of two separate APIs for recipes and scheduling. For the sake of the challenge it makes sense but for production code I'd recommend moving the scheduling logic into a separate controller inside the RecipeAPI. This could save a lot of code, allow us to easily share data models, and reduce the amount of data send over the network.
I'm looking forward to talking to you about my code.