Coding, Tech and Developers Blog
Vertically sliced architectures and vertical slicing in general are currently all around us in the software development industry. The main goal here is to create loosely coupled, self-contained features in your application as opposed to traditional layered architectures.
I've seen and implemented different variants of vertically sliced architectures in past and current projects and they all look different.
So while all of this looks easy at first glance, let's take a look at key aspects and things to consider when it comes to implementing vertical slicing.
The main driver starting people to move away from traditional layered architecture models (and also hexagonal architecture) can be found in complex domains and applications that are trying to model these domains. To make it a bit more tangible, consider this classical blueprint example of a multi-tier application. We could assume these to be classes and solution folders:
Now, whether or not this structure is a good way or not is not of interest, but it will help to demonstrate a few things:
At some point, you can be pretty certain that the
ProductsService needs to make use of the
CustomerService. Or maybe the users want to have a reporting feature, so you start implementing a
ReportsService that even makes you of both the other two services that I introduced. Might not seem like a bad thing in the beginning, but sooner or later your services become more and more coupled, and changes in one place will lead to unexpected behavior in completely unrelated areas of your code base.
Oftentimes, when implementing a new feature, developers will have to touch classes of all layers. Models need to be added, methods on classes and interfaces have to be adjusted, and the presentation layer needs to adjust as well. This leads to a huge number of changed files in the later pull requests, often even in classes that are not even closely related to the actual feature that was implemented.
To comply with the implicit "rules" of the layers, some classes will end up existing for no good reason. Did you ever come across a service like this?
public class CustomerService
public Customer GetCustomerById(Guid customerId)
Many of the methods within domain services, if they do not contain further logic, will simply be delegates to the data access layer. They are not doing anything useful. They are actually useless and will just add to the signal-to-noise ratio that developers face when having to work in this solution.
When people (and that includes customers, business people, developers, everyone) think about software and implementing features, they do not think about this in terms of vertical layers. A user wants to be able to order a product. They do not care about that being split up into business logic and data access. Likewise, a user story is only worthwhile if it can add value for the user, hence it will also be a metaphorical slice of the cake.
If you remember Conway's law, a system's design will closely reflect the structure of the organization building that piece of software. Horizontal layering is therefore an active attempt at fighting this principle.
As an alternative, vertically sliced architectures are emerging more and more to tackle these aforementioned downsides. In a codebase that is sliced vertically, we are hoping to achieve the following
To revisit the initial example, with vertical slicing we could have a code base that looks like this:
This kind of approach will also have its downsides like increased complexity when features need to share functionality, code duplication (WET vs DRY) and more classes in general.
There are quite a few tutorials out there that will teach you how to start a project with vertical slicing, so I will not go down this path any further. Instead, I'd like to focus on the things that will actually be different and tricky, each time you really start using this approach.
If you just need something to start with, feel free to check out the following articles as well:
Let me share some of my thoughts on things, that can easily be confusing when actually using vertical slicing in real-world applications. I've been involved in projects that were using this approach but were still very different about the actual implementation details.
In the utterly simple examples I gave earlier, I intentionally did not put any emphasis on whether the folders were just solution folders or actual projects. I worked on solutions that consisted of almost 1000 individual
.csproj files, where each of these projects represented a single feature with a consistent entry point and only the model files that it needed.
At the same time, if your application rather originates in a "clean architecture" world, you might have projects like
Infrastructure. On the other spectrum, I've designed projects with only a single project in the solution that were still applying vertical slicing just through folders and namespaces. All of these different implementations of vertical slicing were still successful and efficient within the boundaries of their requirements.
There is no such thing as "right" or "wrong" when choosing one approach over the other. It highly depends on the needs of your application.
Honestly, the question is not really whether you can mess it up - because you will. The question is more whether you are willing to reflect and reiterate your design decisions after some time has passed and you have gained experience in the application.
In every project I worked on, our initial decisions were very different from the actual implementation after a few months of development work in that solution. After some time of implementing features and fixing bugs in a codebase, you and your team will realize that something does not feel good. Maybe the initial choice of slices was not perfect and needs adjustment. Please be prepared for that to happen and put in the necessary effort to make changes to your design. If you fail to do that, your application might become just as messy as it would have been using a layered approach.
One remark about choosing the feature slices, though: When starting off, in lack of experience, most developers are tempted to choose rather "general" features, as I did in the above examples. But actually,
Products are not features. I'd honestly prefer we would talk about use cases instead of features when it comes to vertical slicing.
Products is very obviously no use case, but
OrderProduct is. A better approach to the example could then be:
Also, when defining these use cases, you are well-advised to talk with your business people about those. And if they cannot provide you with a good list of those, ask yourself: What are the entry points into the application? A public method on an API controller? An incoming message from a bus? All of these are classic use-case examples that drive your system.
No, of course not and we will keep this one short. It is very well possible to design a vertically sliced architecture without making use of the mediator pattern to create commands and queries in your application. There is also no need to split read-and-write models when it comes to talking to your data storage. All of the "tools", "best practices", and "must-haves" that you will find out there are merely crutches to get you started along your way. Some make sense, some might not. Be prepared to reflect and choose only the things you need for your project.
Let's discuss one of the most confusing and debated topics: What if features need to share functionality? What if they share models? How much code can I safely duplicate?
Short answer: It depends.
Long answer: Shared code is one of the things in vertical slicing that will cause most of the discussions among developers involved in that code base. The reason for this is that you won't be able to pick a very good solution but only a solution that represents the best compromise.
But let's start simpler: It is generally accepted bad practice to have one vertical slice call another. For example, we can be pretty sure that the
OrderProduct use case needs a
Customer at some point so that would be a possible scenario for cross-slice functionality and there are different approaches to this problem:
Customer model in the
OrderProduct slice that is completely self-contained. This is the WET approach.
But what about other shared functionality - maybe many use cases need to talk to a message bus interface or an external API? For this, basically, the same approach as above applies. It is common to have a
Shared namespace that holds everything that is actually shared between your slices. Please remember that when implementing vertical slicing, all of our known software design principles still apply. So nothing is keeping you from having an
IShipmentService somewhere in that namespace, if that is really something that is often needed in your application.
So, where do we stop duplicating and instead start reusing code? A good answer to this can only be given in the context of the specific application in question. As a rule of thumb, I would start with "If you copy a piece of code more than once, you should have a good reason for that."
Vertical slicing to me is one of the most natural approaches to designing software because it so closely mimics the way we understand and think about systems. At the same time, the devil is in the details. Our brain, for example, does not care about use cases referencing each other, it will just draw a connection from A to B and be done with it - we are easily tempted to do the same in our codebases.
I hope I could shine some light on some of the parts where vertical slicing might get hairy. The best advice I can give you as a summary is to be prepared to rethink and change your approaches if necessary. Software design is a process of constant change and vertical slicing will make this even more obvious to us.
Be the first to know when a new post was released