← Back

Technical Decisions

Inventory Management API

This project is an implementation of a REST API for inventory management with Spring Boot. It implements CRUD, JPA relationships, validation, exception handling, JWT authentication, migrations with Flyway and deployment with Docker.

Database design

The first step was designing the whole system in layers according to what I wanted to achieve. Since it is an inventory management system, I started by designing the database, which contains 5 tables:

PostgreSQL

I chose PostgreSQL for its robustness and its good integration with Spring Boot. Once the SQL is written, it is important to make sure the data types and constraints are precise for each field. I also decided to create indexes on the foreign keys, since they will be queried heavily by the API; this increases storage and write cost, but as the API performs mostly read operations it pays off. I seeded the tables with sample data to simulate real usage.

Entities

Next I moved the design to Java. I chose Spring Boot because it provides integration with Spring Data JPA, dependency management and bean validation, which lets me focus on the business logic. To connect the database to the code, the first thing I do is create the entities (the Java representations of the database objects), using JPA annotations to define the relationships and constraints.

Repositories

The next step is the repositories (the layer that runs the SQL queries against the database). Most queries are implemented through Spring Data JPA methods, which greatly reduces the amount of hand-written SQL.

Services

Then I created the services. This is where I run the relevant validations to make sure only correct data reaches the repository methods, preventing invalid data from being written to the database. The business logic is centralised in this layer to keep the controllers thin and separate responsibilities.

Controllers

Next came the controllers, responsible for receiving HTTP requests and returning responses. I designed them following REST principles, using the HTTP methods (GET, POST, PUT and DELETE) and distinguishing between path variables (@PathVariable) and query parameters (@RequestParam). They contain no business logic, keeping responsibilities separated.

DTOs and mappers

Working directly with entities means losing control over which fields the API exposes. That is why I decided to create request and response DTOs for each entity. The request DTOs do not include an id: on creation it is generated automatically, and on update and delete it is already in the URL, so there is no point repeating it. Right now the response DTOs return every field, but it is ready to scale in the future if I add a field I want to hide. Along with the DTOs I created the mappers for the conversion.

Validation

I chose to add bean validation only on the request DTOs, because in this design no invalid data can reach an entity without first passing through the request DTOs and the service's business logic, so the entity does not need to protect itself. It also avoids duplicating validations between the request DTO and the entity.

Exception handling

The exception handling covers the following:

For the generic exception I do not expose the exception message, to avoid leaking internal details of the database structure; only an informative message is returned.

Records

I decided to use records for both the DTOs and the errors returned by the API, since they are simple classes whose only purpose is to carry data.

Pagination

As a next step I implemented pagination on the StockMovement and Product entities: on the former because the table will hold a huge number of rows and fetching them all on every query would make the API extremely slow, and on the latter to prepare the API for the future if many products are added.

Base tests Pending