As the concept of clean architecture tends to be about how to design software, it’s sometimes hard to see the entire picture and truly appreciate the benefits clean architecture can bring to the table. More than once I’ve gotten the comment that clean architecture looks fine on paper, but the added complexity is not something that can be sold easily to managers or architects. So perhaps it’s time to roll up our sleeves and get coding. For those that have read ‘REST in Practice’, there is a nice example in the on how a REST service for a coffeeshop (no, not the Netherlands variant) might look like. It has a clear scope, so it’s ideal for this session. The entire exercise will be too long to put into a single article, so this will be a article in multiple episode. Episode 1: the application API layer and the REST interface.
From a use case standpoint, we can identify 6 use cases:
- Create a new order for coffee
- Show all the orders that haven’t been delivered yet
- Get the status of an order
- Pay for an order
- Delete an order before it is delivered to the customer
- Deliver an order to the customer
Starting from a Spring Boot project template, we can start coding the application API. After we’re finished, we’re left with 6 interfaces and request/response models. For example, the CreateOrder use case would look like this:
And the GetOrders use case would look like this:
If you look closely, there is something that should catch your eye: Milk, Size and Status. These are simple enums, for examle Milk:
We could opt to put these into the application API layer, but that would mean we’d have to copy these inside the domain layer (the application API layer and domain layer do not have a dependency, remember?). There might be valid reasons to do so, for example if the application layer has the possibility to change from the domain variant you should copy them, but in this case they are something that is called ‘shared vocabulary’. This is the one module in clean architecture on which every other module depends on. This module should be cared for tremendously. In most cases, the only thing that goes into these are simple shared datastructures (like monetary values if you’re not using
javax.money), shared enums and perhaps a couple of exceptions. In this case, we’ll put int the three enums
Another thing that you notice is the use of higher-order functions as parameters. This way we can defer the presentation logic to the consumer, yet ensure that the use case delivers the data structure needed by the consumer. It’s a nice trick that you can use in Kotlin.
Now that we have our business interactions modelled, we can go in 2 directions, either we start going towards the web infrastructure layer, of we go towards the persistence infrastructure layer. As the shortest route is towards the web layer, I’ll be going that route now, we’ll just have to mock the implementations for now.
From a REST standpoint, we have a couple of endpoints:
POSTfor creating an order and a
GETfor getting the orders
DELETEfor the deletion of an order
GETfor the status
POSTto deliver the order
POSTto pay for the order
There are a couple of variants that are possible here. You can opt to use a
PATCH for updating the status after delivery or adding a payment. Or you can use a
PUT to change the status. As with most RESTful APIs, there really isn’t correct answer when it comes to actions that cannot directly (and reasonably) be translated to resources. These kind of discussions tend to become religious in nature and you’re best to avoid them. Pick a style and stick to it.
In the end, we end up with the Spring MVC controller looking something like this:
Again, you can see a couple of interesting pieces here. For example, the POST of
/order actually directly uses the request object of the use case. If we wanted to be completely decouples, we should copy that data structure into a separate one, but in this case, it would be a one-to-one copy. Re-using the request object will save you some code and time here, but off course this comes at a cost. If you wanted to change the request of the use case, your REST endpoint will suddenly be impacted as well. It’s a compromise, but as long as you’re aware of the dangers, it’s a valid compromise you can make in this case.
Here you also see the higher-order functions in action. They are able to map the response models from the use case into REST specific datastructures. For example, the response of the creation of an order also adds some HAL hypermedia links.
If you want to run this, you’ll have to provide some mock implementations of the use cases for now. For example, you can (for now) provide the following implementations in your application layer:
And add some component scanning magic in your main partition to make sure Spring can handle your custom annotions:
Running this application will yield you the REST API you can already consume. Next up: implementing the application layer and the domain.Tweet this article