130 lines
5.6 KiB
Markdown
130 lines
5.6 KiB
Markdown
# Yohoho - A take-home assignment for HolidayPirates
|
|
|
|
This is my take on the take-home assignment I was given for the Backend Engineer
|
|
position at HolidayPirates.
|
|
|
|
The goal is to implement a very small REST API in Clojure. That API provides two
|
|
endpoints :
|
|
|
|
* `POST /items`: creates an item
|
|
* `GET /items`: returns the list of items
|
|
|
|
This was a great opportunity for me to learn more about Clojure. Whatever the
|
|
result of the recruitment process, it was really fun working on this assignment!
|
|
|
|
## Running and testing
|
|
|
|
This project uses Leiningen. Assuming you already have Leiningen installed,
|
|
running the API should be as easy as cloning the repo and issuing `lein run`. By
|
|
default, the server listens on port 3000. To explore the API using Swagger UI,
|
|
you can head to <http://localhost:3000/doc>.
|
|
|
|
To run the unit and integration tests: `lein test`
|
|
|
|
## Structure
|
|
|
|
The source code lies in `/src/yohoho` and is structured as follows:
|
|
|
|
* `app.clj`: ring app and main entrypoint
|
|
* `config.clj`: configuration (database and HTTP server)
|
|
* `db.clj`: functions that directly interact with the database
|
|
* `handlers.clj`: route handlers
|
|
* `helpers.clj`: the infamous file that holds function that are useful but
|
|
couldn't fit anywhere else
|
|
* `routes.clj`: API routes
|
|
* `schemas.cls`: Malli schemas used for validation
|
|
|
|
## Library/tools choices
|
|
|
|
Since this is just a small assignment, I sticked to simple and proven tools:
|
|
|
|
* SQLite for the database (easy to setup, easy to use with `next.jdbc`)
|
|
* Leiningen for managing the project (it seems to be getting outdated in favor
|
|
of Clojure CLI Tools, but is still widely used and I could find more
|
|
documentation)
|
|
* Libraries : `reitit` for route handling + `malli` for validation + `muuntaja`
|
|
for handling json content. Thanks for the suggestions in the assignment! I
|
|
chose `reitit` because it seemed more complete/integrated than Compojure.
|
|
However, in the context of this simple assignment, Compojure may have been a
|
|
better choice (would have probably been easier to learn)
|
|
* Web server: `jetty` (seems to be pretty standard)
|
|
|
|
## Comments
|
|
|
|
Respect of the assignment:
|
|
|
|
* The API features the `POST /items` and `GET /items` endpoints
|
|
* `POST /items` accepts a JSON payload, validates it and returns HTTP 400 is
|
|
validation fails
|
|
* `POST /items` persists data in SQLite
|
|
* `GET /items` returns the list of all items as JSON.
|
|
* The structure of the items is respected, although it just includes the bare
|
|
minimum (id, name, email).
|
|
* A data validation library is used (`malli`)
|
|
* Unit tests were written (although generated with the help of Claude Code)
|
|
* Integration tests were written (although generated with the help of Claude
|
|
Code too)
|
|
* The API is documented (OpenAPI `/openapi.json` + Swagger UI at `/doc`)
|
|
|
|
Divergences from the assignment:
|
|
|
|
* The `POST /items` do not take the id in the input. Instead, I prefer to let
|
|
the database generate a new id on its own.
|
|
* As a consequence, the `id` of an item is an integer, not a string. SQLite can
|
|
autogenerate/autoincrement only on a integer id.
|
|
|
|
What was not part of the assignment:
|
|
|
|
* `GET /ahoy` health check endpoint. As you may guess, this was my very first
|
|
endpoint as I was learning how to use `reitit`. I decided to keep it, because
|
|
a health check endpoint cannot hurt.
|
|
* `GET /item/:id` was included. This was quite easy to add, and also makes sense
|
|
since the `POST /items` endpoint follows standard practice of returning a
|
|
`Location` header for the newly created item.
|
|
|
|
Security:
|
|
|
|
* SQL injections: we should be ok since we are not using raw SQL statements in
|
|
`db.clj` (`next.jdbc` will use parametrized statements under the hood)
|
|
* `POST /items` input has validation
|
|
* See the "Desirable improvement" section for some missing security features
|
|
|
|
## Desirable improvements
|
|
|
|
This project is voluntarily kept simple. If it were to get deployed in
|
|
production, the following should be dealt with, in no particular order:
|
|
|
|
* `GET /items`: add pagination
|
|
* Security: Add rate limiting
|
|
* Security: Add authentication/authorization
|
|
* Security: Add and configure CORS middleware
|
|
* Add logging
|
|
* TLS: it's fine that the API is accessible only through http, but it should be
|
|
deployed behind a reverse proxy to add TLS/https support. This is quite easy
|
|
to do with Apache, Nginx or Caddy.
|
|
* Database: use a migration system. This was definitely unnecessary for this
|
|
assignment, but should be used for a more serious project. Migratus seems to
|
|
be the de-facto library for this.
|
|
* Database: switch to a more serious database (PostgreSQL comes to mind), add
|
|
connection pooling, use transactions.
|
|
* Add metrics (request rate, response time, database connection
|
|
pool stats, etc.) and monitoring (eg. using Datadog).
|
|
* Configuration: add the ability to override deffault configuration using a
|
|
dotenv file and/or environment variables.
|
|
* Add a supervisor to ensure the API keeps running (could be as easy as using
|
|
systemd, or as complex as deploying with kubernetes)
|
|
|
|
## Documentation links
|
|
|
|
The following links proved more than useful when working on this assignment:
|
|
|
|
* <https://practical.li/clojure-web-services/building-api/>
|
|
* <https://github.com/metosin/reitit/blob/master/doc/ring/content_negotiation.md>
|
|
* <https://ostash.dev/posts/2021-08-22-data-validation-in-clojure/>
|
|
* <https://clojurecivitas.github.io/malli/elements_of_malli.html>
|
|
* <https://github.com/metosin/reitit/blob/master/doc/coercion/malli_coercion.md>
|
|
* <https://cljdoc.org/d/com.github.seancorfield/next.jdbc/1.3.1086/doc/getting-started>
|
|
* <https://cljdoc.org/d/metosin/reitit/0.10.0/doc/ring/swagger-support>
|
|
* <https://www.baeldung.com/clojure-ring>
|
|
* <https://practical.li/clojure/testing/unit-testing/>
|