dateo. Coding Blog

Coding, Tech and Developers Blog

vertical-slicing
architecture

There is no template for vertical slicing

Dennis Frühauff on January 10th, 2024

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.


What are the main goals?

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:


UI/
   ...
Logic/
  ProductsService
  CustomerService
DataAccess/
   ProductsRepository
   CustomerRepository
   Model/
      Product
      Customer

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:


Increased coupling between classes

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.


Increased change set size

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.


Ghost classes

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) 
    => this.repository.GetCustomerById(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.


People do not think in terms of layers, they think in terms of features

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.


Vertical slicing as an alternative

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


  • Code that belongs together, stays together. Consequently, only the code that has to change will change.
  • Potentially faster development cycles.
  • Easier to grasp both conceptionally and with regard to implementation.
  • Fewer merge conflicts.

To revisit the initial example, with vertical slicing we could have a code base that looks like this:


UI/
   ...
Products/
  GetProducts
  Model/
    Product
Customer/
  FindCustomer
  Model/
    Customer

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:


Where it actually gets tricky

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.


Folders? Projects?

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 Core or Application and 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.


Can I mess it up?

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, Customers and 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:


UI/
   ...
Features/
  OrderProduct/
    OrderProductHandler
    Product
  FindCustomer/
    FindCustomerHandler
    Customer

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.


Mediator? CQRS? Do I have to do all that?

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.


What about shared code and code duplication?

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:


  1. Separate models completely: If you only need a subset of the information from the other slice, it might be worthwhile to actually hold a different Customer model in the OrderProduct slice that is completely self-contained. This is the WET approach.
  2. Share your models: The other side of the spectrum could be to actually share all of the model classes that have to be shared in a designated folder. That way, you will at least make apparent what is really shared among your use cases:
UI/
   ...
Features/
  OrderProduct/
    OrderProductHandler
    Product
  FindCustomer/
    FindCustomerHandler
Model/
	Customer

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."


Conclusion

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.



Please share on social media, stay in touch via the contact form, and subscribe to our post newsletter!

Be the first to know when a new post was released

We don’t spam!
Read our Privacy Policy for more info.

We use cookies on our website to give you the most relevant experience by remembering your preferences and repeat visits. By clicking “Accept All”, you consent to the use of ALL the cookies. However, you may visit "Cookie Settings" to provide a controlled consent.