Table of Contents
This project is a technical demonstration of how to build a simple project using Kotlin and Spring Boot. This project also wants to reflect clean code and clean architecture techniques and best practices.
The goal of the app is to display all the albums hosted in JSON Placeholder API and the details of the photos in those albums.
The application was created using Spring Initializr based on Spring Boot 3.3.2 and java 21.
These are the tools with which the application has been built:
The repository was tagged each time a significant change was made so that the entire development process could be explored.
These are the different tags available:
- base-project: The project created with Spring Initializr
- base-skeleton: Added domain objects, separated the application into layers and added service interfaces.
- album-service-impl: Added Mockito dependency, added Album Service Unit Tests and Implementation.
- photo-service-impl: Added unit tests and Photo Service implementation. Lately a small refactoring has been done in input-adapters-impl.
- output-adapters-impl Added Integration with JSON Placeholder API.
- input-adapters-impl: Added Swagger dependencies and Rest Controllers implementation.
- dockerized-app: Added Docker and Docker Compose files. Added Live Demo and Enhanced README.md.
- docker-image-publish: Added github actions workflow for build and publish imagen in Dockerhub registry.
- enhanced-album-details: Added Photos to Album Models to retrieve the photo list along with the Album Details.
This section explores how different aspects of development were addressed for the reader's clarity.
All the developments were done in a feature branch and merged to main once the branch scope was complete. Once the changes reach main, a new tag is created so that they can be easily located.
Also, Conventional Commits specification (see more) was followed.
Perhaps in a real scenario, merging the changes by squashing commits would have been appropriate, but in this case I have chosen to merge all commits so that the entire development process could be seen.
Hexagonal Architecture, also known as Ports and Adapters Architecture (read more), is an architectural pattern that aims to create applications with low coupling between its different components.
This allows its components to be easily replaced without compromising other parts of the software. In addition, it also facilitates the maintenance and extension of the code, as well as its testing.
It may seem that for such a simple application it is not necessary. But if, in the future, we would like to add more functionality, integrate with a database to create a real repository of images or add extra functionality, having developed the application following this pattern will make the process much easier.
The main code is separated into the following folder hierarchy:
main
├── AlbumExplorerApplication.kt
├── application
...
├── domain
...
└── infra
...
- application: This layer contains the implementation of the application's business logic. It also defines interfaces of input adapters (services that implement the business logic) and output adapters (connections to external repositories and services).
- domain: This layer implements the objects that define the core elements of the business.
- infra: This layer implements the input and output adapters (The input and output connections with external services.)
Unit tests for the application layer were developed using Junit 5 and Mockito. Also, TTD (see more) was followed, creating first the test suit and implementing the services later.
To track this during development, you can consult the commit history for the tags album-service-impl and photo-service-impl.
Additionally, to structure the tests, I have tried to follow the GIVEN/THEN/WHEN pattern (see more). Kotlin has an awesome language feature that allow us to use backticks to write freeform function names. This helps to declare test names that are descriptive of the purpose of the test.
Here is an example of such tests:
@Test
fun `GIVEN an albumId without photos WHEN photos by albumId are requested THEN an empty list is returned`() {
// GIVEN
Mockito.`when`(photoRepository.findByAlbumId(1L)).thenReturn(emptyList())
// WHEN
val result = photoService.find(1L)
// THEN
assertNotNull(result)
assertTrue(result.isEmpty())
Mockito.verify(photoRepository, Mockito.times(1)).findByAlbumId(1L)
}
There are a few properties configured for the smooth running of the project:
spring.application.name=album-explorer
springdoc.writer-with-order-by-keys=true
springdoc.swagger-ui.tags-sorter=alpha
json-placeholder.url=https://jsonplaceholder.typicode.com
The most relevant is json-placeholder.url, which is injected into the services that consume the JSON Placeholder API.
If you want to run the application, you must make sure that you have the following dependencies installed:
To run the application it is necessary to type the following commands:
gradle build
gradle bootRun
If you only want to run the test, run the following command:
gradle test
To use the application you can access the Swagger UI. To do so, you can open a browser and go to this url:
http://localhost:8080/swagger-ui/index.html
You can also perform request to the API invoking directly the available endpoints:
http://localhost:8080/photos/{{id}}
{{id}} Should be a Long. It will return a 404 Error if the photo does not exist.
http://localhost:8080/photos
You can use the query parameter albumId to retrieve all the pictures that belongs to that album.
http://localhost:8080/photos?albumId={{id}}
{{id}} Should be a Long. If the album does not exist, It will return an empty list.
http://localhost:8080/albums/{{id}}
{{id}} Should be a Long. It will return a 404 Error if the album does not exist.
http://localhost:8080/albums
To facilitate the deployment of the application, if you have Docker on your system you can run the application with the following command:
docker-compose up -d
Once the container is up and running, you can access it as normal:
On the swagger front end:
http://localhost:8080/swagger-ui/index.html
By querying the api directly, as in this example:
http://localhost:8080/photos
Aditionaly, a Github Actions Workflow was created for build and publish the image every time a new change is pushed into main (see workflow).
You do not need to download the project for executing it in your local. In order to use the published image, just create a docker-compose.yml
file like this:
services:
backend:
container_name: album-explorer-backend
image: mreiros/album-explorer-backend:latest
volumes:
- ./src:/app/src
ports:
- 8080:8080
Once you have the file created, just run the following commands:
First You need to retrieve the image (also if You have it already and want to update to a new release):
docker pull mreiros/album-explorer-backend:latest
Then, You can start the application running the following command:
docker-compose up -d
A live demo is also available without downloading the code. You can consult the swagger panel at the following link:
http://album-explorer.sauleiros.com/swagger-ui/index.html
Or make requests directly to the api as in this example:
http://album-explorer.sauleiros.com/photos
Note that HTTPS requests are not available. Enabling SSL connections in Swagger is a work in progress.
There is currently a problem accessing the Swagger panel in the Live Demo. If accessed via https, it will not be possible to test operations.
It would be great to be able to integrate the changes automatically into the Demo server. For this, a CI/CD plan could be created using the pipelines offered by github.
Currently, the exception handling done at the REST layer is limited and could be improved by providing for a larger volume of exceptions.
The project has been developed for training purposes, so if you have any suggestions, you are more than welcome to leave a comment by opening an Issue in the repository or contacting me through my Linkedin.