Files
yohoho/README.md

5.6 KiB

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)

The following links proved more than useful when working on this assignment: