At Vitcord we always try to offer the best experience for our users. Recently, we discovered that push notifications management was starting to cause a bottleneck in our monolith so we decided to face this problem before it would become worse.
At the moment we started to study the solution for this challenge, push notifications weren’t a big part of our monolith, so we realized that extracting it to an isolated microservice could be a good idea. Besides, push notifications become a key of success in terms of retention once you have an interesting user base. Extracting this responsibility from our main server means we can scale this part separately and give it a better maintenance too.
Once we decided what we were building we started to think about how we were going to do it. From the beginning, we knew that our database should be Cassandra or DynamoDB, as notifications and messaging are the kind of feature this databases are built for. Choosing the language and framework did take us a little bit longer, but in the end we went for Kotlin and Spring Boot. These two make a perfect couple and some of our tech guys had some experience with Kotlin, besides, the Android team has their app completely in this language.
When we started designing the system we knew that it wouldn’t have a lot of actions (one for each type of push notification basically), but they could be more in the future. We knew that a vertical oriented approach suited perfectly as it allows to distribute the work easily, plug in or out actions and maintain each vertical separately without touching more code than needed.
After some research we discovered an architectural pattern that fitted our design really well: Action Domain Responder. This pattern was born as an evolution for the classic Model View Controller.
MVC is an architectural pattern for apps with user interfaces born in the 90s. The Controller maintains a state of the program which tells the View how it should be showed to the user. With each change to the state the UI should render automatically. It became very popular in backend thanks to frameworks like Ruby on Rails, which follow this pattern for web applications.
Action Domain Responder was created in 2014 as a refinement for MVC in the context of web REST APIs. One key principle of REST is that is stateless and each HTTP request is treated independently, so there is no state to keep. ADR proposes a much more natural flow for backend applications:
The Action receives an HTTP request. It collects any required input and calls the Domain with them.
The Domain manipulates the information and applies any business logic. It returns a result to the Action.
The Action calls the Responder with the result of the Domain.
The Responder builds an HTTP response and sends it.
ADR suits our vertical oriented approach because we can fulfill SOLID’s Single Responsibility Principle to the max:
In ADR each Action is a separate class with only one method instead of a Controller with one method for each endpoint. Actions only connect Domain with Responders.
We can model the Domain following a Command pattern which helps us to isolate the business logic and reuse it.
Responders only job is to build HTTP responses based on an input. They can be pure functions.
Every vertical is a set of an Action, a Responder and a Command. This allows us to add or remove them without affecting more components than these. Testing also becomes easy peasy as every piece has a limited responsibility and they usually have one or two methods. Structure is also another plus as it is really easy to find every file by their corresponding Action, and you can tell every feature of the app from a top level look of the Actions.
Actions define the endpoint they are listening to and only have one execute() method that will be invoked with each request. They call the Domain with the request input, then they call the Responder with the result and lastly they return the response that the Responder built.
Domain is divided in classes following the Command or Use Case pattern. They are in charge of manipulating the data, storing them in database, publishing domain events, etc. As the Actions, they always have one invoke() method which returns an Either datatype from Arrow, the library for functional programming in Kotlin.
Either represents some operation which can fail or succeed. It works like a black box in which we operate, but we won’t know if everything went okay or not until we open it. This is by far better than throwing exceptions because we are telling explicitly that our code can fail and we force ourselves to handle every path.
In this specific case we are telling that this command can return a Notification if everything went right or it can return an Error from the Algebraic Data Type (ADT) we created (Errors), this means the error must be one of the objects under the sealed hierarchy of the parent class.
Responders just need to open the Either box and create a HTTP response based on its content. For this purpose we have the fold() method which accepts two lambdas and executes one or the other, depending if the content is an error or a success.
Modelling the errors in an ADT style forces us to handle every case thanks to Kotlin’s when. If I added another error to the hierarchy, the Responder wouldn’t compile until I handled it in the when block.
We think ADR is a better architectural pattern for REST APIs than MVC and it fits our development workflow really well. It has allowed us to deploy this project in little time and it will be easy to maintain thanks to the responsibility separation, testability and good structure the architecture is giving us.
Thank you for reading this post. Please, give us feedback by commenting or tweet us if you liked the ADR pattern. Stay tuned and follow Vitcord Tech for more posts about our experiences and learnings 🙌