gifty – Architecture overview
Today’s post is going to shortly discuss entire architecture, I’m going to implement in next three months. Since I’m not microservices master, some of my current decisions might be stupid or just not efficient. I so, please let me know in the comments. This would definitely help me out 😉 !
What are microservices?
Let’s start with defining the microservice term. In a common approach, we design our system as one huge „being”. Of course, there might be several projects involved such as DAL, Domain layer, and API but the thing is that mostly there are bounded by dependencies between them (usually we call that coupling). In many cases, this approach works fine but it causes some restrictions:
- we’re tied to a specific technology (or even its specific version).
- even small changes in a project require rebuild and deploy the entire application.
- scaling also applies only to the entire application rather than just needed module (like mailing service etc.).
And that’s where microservices come into play. The idea is quite simple, we take the monolith and split it into small, separated and independent pieces which communicate with each other using chosen technique. Three most common are:
- API calls
When we start thing about designing our applications in that way quickly we find out that this gives us a lot of advantages:
- since there’s no coupling (direct dependencies) between services we can create them independently using different technologies which suit domain the most.
- unavailability of one service may not cause the entire application to be unavailable.
- new functionalities do not force us to redeploy entire app. Simply, we take only those services which have been changed. This also means that in some cases deploying the new version of the application does not cause its maintenance.
- we can scale chosen services independently.
Knowing the idea, we can move forward to gifty’s architecture which I’m going to implement. The diagram below presents it:
So, as mentioned in my previous post, I’m going to follow CQRS pattern (if you have no idea what the heck I’m writing about feel free to read my post series about it). Therefore each user will be able to send two types of data to the server:
- Commands – objects which represent user’s intention. Used for writing to the application’s backend.
- Queries – an object which requests to the backend for some data.
Now, let’s discuss how the commands and queries are going to be processed.
- The user sends some data to API gateway.
- Data is transformed into command and published to Service Bus (which in my case is going to be RabbitMQ queue + RawRabbit library for .NET Core).
- Each service (Users, Gifts) on startup will subscribe to chosen commands/events passed by Service Bus. After receiving a proper command, it’s passed to Command Handler to execute it.
- Command Handler validates the command.
- Command Handler invokes some Domain Service method, for instance CreateUserAsync.
- Service persists data to the graph database (Neo4j) using a repository.
- The user sends a query to API gateway.
- Query is forwarded to specific microservice API (GET only)
- Service’s Nancy module invokes some Domain Service method, for instance GetAllUsersAsync
- Service gets data from the graph database (Neo4j) using a repository.
- Response is sent to API gateway
- Response is forwarded to the user.
As you can see, processing for each type of user’s request is slightly different and achieved by different communication technique. The key question here is: Why? To be honest, my first thought was to process queries also through some Query Bus since RabbitMQ provides a mechanism for Request/Response model. But soon after, Piotr Gankiewicz (who won the previous edition of „Get Noticed”) convinced me to use API for that purpose. The argument is really simple. One of the key difference between commands and queries is the fact that commands do not need to be processed immediately after sending. Imagine a situation where you add some gift to your list and click save. UI can present the gift as added but the operation might take a while. But does it really matter? No, since your intention was simply to add a gift. On the other hand, queries should be processed immediately. Let’s say that someone wants to display our gifts lists. The user expects the result to be displayed quickly, without waiting for the queue. So, in this case, making a direct call to Microservice is better and guarantee the result to be returned fast.
For my project, I created Organisation on GitHub which keeps all repos. Those repos will be soon integrated with Travis CI. The whole stack looks as follows:
- gifty.Messaging – responsible for communication with RabbitMQ using RawRabbit for .NET Core
- gifty.Shared – consists of shared resources like extension methods, helpers, contracts ets.
- gifty.Services.Users – service executing domain loging related to users
- gifty.Services.Gifts – services executing domain logic related to gifts
- gifty.API – simple Nancy API for communication with Client application
- gifty.Web – client application written in Aurelia and TypeScript
That’s all for today. If something warns you in my description or you know better solutions, please let me now in the comments section below!