7.4 KiB
Decisions
Fill this in before you send your submission back — a few minutes is plenty. It is the artifact we pay the most attention to, and it is what we will dig into together at the next stage.
What I built and why
- A new
POSTendpoint was added to allow for the creation of mission launches. An example curl request for the new endpoint is:
curl --request POST \
--url http://localhost:5173/api/missions/551ba7ad-ce93-4539-9083-3b0ef337f011/launches \
--header 'Content-Type: application/json' \
--data '{
"scheduledFor": "2019-07-26T00:00:00",
"status": "pending"
}'
- The Mission -> Launch relationship is defined as one-to-many. Originally chose to have a one-one relationship, but changed after deciding that there could be more than one launch per mission (and makes the frontend look a little more interesting).
- For the UI, I chose to have a table within a table, with an accordion element. It seems to work well, however could get a little cluttered if there are more entities related to Missions in the future.
- I chose to have the form as a pop-over on the table as the form was small enough, and it allows the user to have the context of the Mission they are adding the Launch to, which something like a modal doesn't do as well.
- Created a very simple CI workflow at
.gitea/workflows/build-test.ymlwhich runs on PR andmainmerges. You can find the runs at https://git.stevenjw.co.uk/StevenJW/mission-control/actions, the latest ones should be green.
Where AI helped, and what I accepted or rejected
I wrote this code by hand (you might be able to tell!), but used AI to remind myself of libraries that I have not used in a while and answer a few questions around testing. I do usually generate a decent amount of code whilst working, however I had a blast doing this and really enjoyed being in my editor for this one!
One interesting snippet I dismissed was around Enum validation in the CreateMissionLaunchRequestValidator. I don't have this in the final code since I let the JSON deserializer do the validation for me now, but AI suggested this for the validation:
public record MyRequest(string Status);
public class MyRequestValidator : Validator<MyRequest>
{
public MyRequestValidator()
{
RuleFor(x => x.Status)
.Must(status => Enum.TryParse<MyEnum>(status, true, out _))
.WithMessage("The status field contains an invalid choice.");
}
}
Which looks ok, but FluentValidation has a builtin way to do this:
public record MyRequest(string Status);
public class MyRequestValidator : Validator<MyRequest>
{
public MyRequestValidator()
{
RuleFor(x => x.Status)
.IsEnumName(typeof(MyEnum), true)
.WithMessage("The status field contains an invalid choice.");
}
}
Always nice to use the builtins, and it's much clearer what is going on too!
What I would do differently with more time
There are definitely many things I would change for next time, or if I was to implement this for a production service.
Backend
Use more robust integration testing for the .NET tests.
Whilst calling the endpoints directly is ok, it doesn't give you the full picture of how the endpoint is called in a real system. Things like the URL is omitted (which is quite big for the /missions/{MissionId}/launch URL I have created), validation is not done on the endpoint directly and other HTTP pipelines are not triggered, such as middleware. The current tests are useful, but I would still maintain a large amount of full integration tests (including using something like TestContainers) to ensure everything is working as expected as clients would be expecting. Also, as mentioned in the ListMissionsTests class, using SQLite as a stub/test database is not really representative of a full DBMS as it does not support all the features that MSSQL does (making it not a great testbed!) - another reason I would use something like TestContainers for a full run-through.
Pagination/caching
When working on these small example datasets it's fine to have all the data being returned at once, but it's not ok when the data gets even a little larger. Caching is also important, whether it's in-memory or external like Redis.
Authentication
Whilst this exercise does say that auth is omitted, it's always necessary in web services.
More restrictive validation
I have implemented very basic validation (I am a big fan of the Validator<T> pattern that fast-endpoints gives!), I think there could be better validation in how dates and launch statuses are submitted. For example, it would be strange to provide a 4 year old date with the Pending launch status. It might be that this is a legitimate request, but it's worth considering to prevent bad data from being inputted.
Moving database calls to a service layer
Not every database call necessarily needs to go in a service, but most will be used in multiple places (for example, checking if a Mission exists), which makes it a good candidate to extract out for better reuse. This also opens the door for mocking the service layer for testing, too - depending on how deep I'd want to go with unit testing.
Have a look at the infra
It's been quite a while since I've worked on Terraform, and I still think it's my preferred IaC platform (having used Ansible for a little bit now) - and I would like to dive back into it. A lot of the infrastructure already looks to be there! I'm also pretty happy to see the SPA be hosted by .NET, really cool.
Add more robust CI/CD pipelines
I am a big fan of CI/CD pipelines. They are a great way to automate checking (ie. make code reviews faster and more focused), and deployment. Every project should have some pipelines, and it's nice to see formatters and linters for both frontend and backend! I have added a very basic one in .gitea/workflows for building, formatting and testing the backend and frontend. You can view the runs at https://git.stevenjw.co.uk/StevenJW/mission-control/actions.
Frontend
Split out the Frontend components a little more
Currently, the frontend components are a little too tightly coupled, and deal with a lot of external behavior (such as data fetching). Allowing the top-level page/component to fetch the data and pass it down (or using a context) allows for data mocking much easier in tests, allows better re-usability, and makes components more focused.
Add more tests
I didn't want to spend too long on the testing, but I would have liked to test features like the form validation more. Unfortunately, I didn't have enough time to split away the query context from the components, so testing is a lot harder.
Add some E2E tests
I'm a big fan of E2E tests, where the backend is running and the frontend is interacting with it. I've used Cypress and Playright previously, and they are both pretty good.
Make it prettier
I wanted to get the basic functionality in there, but there is always room for improvement for the aesthetics.
Make it more accessible
There are a few shortcuts in the frontend that I have made here that make this site slightly less accessible, and I've not really focused on the aria elements either. I've also just picked some colours at random, which doesn't help readability much. We've run lighthouse and accessibility tests before deploying before in our CI, which is quite a nice benefit!