-
Notifications
You must be signed in to change notification settings - Fork 10
Architecture overview
Monaco's architecture is based on Jimmy Bogard's Vertical Slices Architecture (VSA).
There are plenty of posts and articles writing about it so we won't get into an excessively detailed explanation about it, so we'll try to make it short: VSA bases its approach on the concept of representing the different features or functionalities as separate slices of a cake that traverses all the layers that make up the application, and each of those slices will focus on a single functionality, effectively encapsulating it, as the image displayed below:
VSA, contrary to other approaches like Clean Architecture, attempts to focus on being productive and delivering the functionality as fast as possible, without having to dance around abstractions or other complex mechanisms that focus more on respecting the purity of certain principles than on delivering value fast but in a scalable manner.
Laying out the architecture this way, we can start of thinking in the layers to be represented more like this:
We can see that we can have a Presentation layer (ie. a Web Host) that only depends on the Application layer that contains the actual Features that represent the different use cases of the application as a whole, and these would depend on the Domain to do their work. Each of them can take care of their own specific responsibility independently from each other, but all can consume the same Domain.
The previous image shows that we can do far more with this approach, because if we have the Application layer as a pluggable dependency for any kind of Presentation layer, then we have the possibility of using the same or new Features for not only a Web Host (ie. REST API) but also from a Message Processor (RabbitMQ/Azure Service Bus consumers), an Azure Function or anything else you can think of:
In order to encapsulate the features, we'll leverage patterns like Mediator and CQS, to be able to isolate the vertical layers and also, horizontally, the different features. This way, there is no need to leak dependencies from the Presentation layer into the Application one, and neither is needed that features depend on other in any way. The simplest representation would be as folows:
The Presentation layer can only trigger a call for processing the request, and, within the Application layer, there will be a handler to take care of the work to be performed as a consequence of this, which in turn will return a response to the caller. Because of this, the implementation of each of the features don't need to get in conflict with each other, as they are all encapsulated and isolated from each other.
Another of the advantages of this approach involves the YAGNI principle (You Ain't Gonna Need It), which means that there's no need to provide abstractions just for the sake of them. There is no need to implement big service classes that contain complex logic and that perform a lot of business logic within them if they are not really needed for a specific requirement. A very usual scenario for the classic approach is to have controllers calling services, which at the time call repositories, and that creates a mesh of calls and dependencies that many times end up generating something like the following:
So instead we can just focus on individual functionalities and start working on the specific work each of them require to perform, like in the following:
This doesn't mean that abstractions are prohibited. If there are features/functionalities that start repeating some code because they share portions of same behavior, this will generate code smells that will guide our refactoring, and then it's perfectly valid to create a service class to encapsulate that code and reduce its repetition in multiple features that will require doing the same; there's just no need to do it ahead of time if we're not yet at the point where we can recognize if it's really needed or not.
Because we need to keep the features as isolated and encapsulated pieces of code, it is a scenario very convenient to implement a pattern like CQS/CQRS, as it will allow us to separate the write operations as Commands and the read operations as Queries providing the context to encapsulate both types of operations and also isolate them individually. More information about how this is implemented in Monaco in the Features/Slices section.
- Index
- Getting started
- Architecture overview
-
Solution/Projects structure
- Solution structure
-
Projects structure
Monaco.Template.Common.Api
Monaco.Template.Common.Api.Application
Monaco.Template.Common.ApiGateway
Monaco.Template.Common.Application
Monaco.Template.Common.BlobStorage
Monaco.Template.Common.Domain
Monaco.Template.Common.Infrastructure
Monaco.Template.Common.Messaging.Messages
Monaco.Template.Common.Serilog
Monaco.Template.Common.Tests
Monaco.Template.Application.Tests
Monaco.Template.Domain.Tests
Monaco.Template.Api
Monaco.Template.Application
Monaco.Template.Application.Infrastructure
Monaco.Template.Application.Infrastructure.Migrations
Monaco.Template.Domain
- Features/Slices
- Validations
- Application configuration
- Template options
- Examples
[Wiki still in construction]