I've always wanted to run a bikeshop and my aspitation is to build software with functional Scala. To kill two birds with one stone, I decided to build a simple CRUD service for a bikeshop. I built it using libraries from Scala Typelevel ecosystem, namely:
- http4s for the http library;
- doobie for accessing a PostgreSQL database;
- circe for JSON parsing;
- cats-effect to handle side-effects in the program with an
IO
monad.
The diagram below provides a high-level overview of the app's architecture.
The HTTP layer defines defines each endpoint’s structure of the user-facing API. The service layer contains the business logic and creates entities that the API layer can expose in JSON format. PostgreSQL database running on a Postgres Docker image serves as the main storage system.
The endpoints provided by the app are as follows:
METHOD | URL | DESCRIPTION |
---|---|---|
GET | /bikes | Returns all bikes. |
GET | /bikes/{id} | Returns a bike with the given id, 404 returned when no todo present with the id provided. |
POST | /bikes | Creates a bike, returns a 201 with newly created bike as JSON. Id is autogenatated. |
PUT | /bikes/{id} | Updates an existing bike, returns a 200 with the updated bike if a bike with the given id is present, 404 otherwise. |
DELETE | /bikes/{id} | Deletes a bike with provided id, returns 404 if there's no bike with the provided id |
Below I provide a few examples on how the endpoints can be hit with curl (or postman), assuming the app runs on port 8080:
- Get all bikes:
curl http://localhost:8080/bikes
- Get a single bike with id (id = 2 used here):
curl http://localhost:8080/bikes/2
- Create a bike:
curl -X POST http://localhost:8080/bikes -d "{\"model\":\"Viper\",\"brand\":\"Unibike\",\"year\":2021,\"price\":1200,\"size\":\"XXL\",\"id\":1}"
- Update a bike (assuming id=1):
curl -X PUT http://localhost:8080/bikes/1 -d "{\"model\":\"Roubaix\",\"brand\":\"Romet\",\"year\":1980,\"price\":350,\"size\":\"L\",\"id\":1}
- Delete a bike (assuming id =1)
I built the HTTP server with http4s which provides a purely functional library to manage HTTP requests and responses. The routes are linked to the business logic through instances of HttpRoutes
which use partial function to match an incoming HTTP request and produce an HTTP response with a side effects.
I'm using cats-effect IO
monad to delay the evaluation of the "impure" effects (i.e. database writes) the "end of the world".
http4s enables straming the responses with fs2 Streams. The fs2 stream is returned via GET/bikes
endpoint and prevents the response from being generated in memory before being sent to the client.
I'm also using http4s logger for logging all requests and responses.
circe library is another library from Typelevel ecosystem and is used in this project to convert Scala case classes into a JSON string and vice versa (de/serialisation) by defining JSON encoders and decoders.
doobie is a purely functional JDBC layer for Scala used to connect to the database. It is also integrated with cats-effect library, what allows all the effectful code in this app to be wrapped with an IO
monad.
I used Postgres as a database. A Postgres instance is spun up with using a Postgres Docker image defined in docker-compose.yml
file. So when the docker image is deleted, the state of the microservice would be lost.
I used pureconfig to read the configuration file application.conf. The advantage of pureconfig is that it enabled reading a configuration into typed objects.
Simple unit tests with mock the repository that accesses the database were performed using Scalatest library.
Currently, the app can be run locally. In the near future (once the front-end is complete), I'm planning to deploy the application in the cloud. It is assumed that the user has sbt, docker and postgresql installed on their machines.
- Please open the docker directory in the project structure in cdm and start the docker container with :
docker-compose up
- You can run the microservice with
sbt run
. It will listen to the port specifiec in theapplication.conf
(8080 by default).