Vertical Slice Architecture in C# – Examples on How To Streamline Code

In the realm of software development, especially within the .NET ecosystem, architectural decisions play a pivotal role in determining the success and maintainability of a project. Traditionally, many C# developers have leaned towards a layered architecture, a tried-and-true method where code is organized into distinct layers, each with its own responsibility. Think of it as neatly stacking building blocks, where each block represents a layer, such as data access, business logic, or presentation. However, in this article, we’ll explore vertical slice architecture in C# to see the benefits.

As software projects grow and evolve, this horizontal separation can sometimes lead to bloated layers, making the codebase harder to navigate and maintain. The concept of vertical slice architecture aims to address this! Gaining traction in recent years, this approach promotes organizing code around features or use cases rather than technical concerns. It’s like reimagining our building blocks, but this time, they’re stacked vertically, representing end-to-end functionality.


What is Vertical Slice Architecture?

At its essence, vertical slice architecture is about creating self-contained slices or modules that encompass all the necessary layers to implement a particular feature or functionality. Each slice is independent, ensuring that changes in one slice have minimal impact on others. This is a stark contrast to the traditional layered approach, where changes in one layer might ripple across multiple features.

For instance, in a traditional layered architecture, you might have a data access layer that interacts with all features of an application. In a vertical slice approach, each feature or use case would have its own data access, business logic, and presentation components, all bundled together. It’s like comparing a multi-story building (layered) to a series of individual cottages (vertical slices), each self-sufficient and independent.

While this article will discuss an architectural approach to vertical slices, you could make the argument that a vertical slice development process technically isn’t the same thing. If you chose to organize your code in a different way, you could still attempt to build features in vertical slices… but it might be pretty darn hard. You can see more about the vertical slicing approach to software development in this video:

YouTube player

Benefits of Adopting Vertical Slice Architecture in C#

Improved maintainability and scalability: Since each slice is independent, developers can work on, refactor, or scale a particular feature without wading through unrelated code. This modularity makes it easier to understand the impact of changes, leading to a more maintainable codebase.

Enhanced team collaboration and parallel development: With clear boundaries set by vertical slices, multiple teams or developers can work on different features simultaneously without stepping on each other’s toes. This parallel development can significantly speed up the development process, especially in agile environments.

Simplified testing and deployment: Testing becomes more straightforward in a vertical slice setup. Instead of testing entire layers, you can focus on individual slices, ensuring that a particular feature or use case works as expected. Moreover, with the rise of microservices and containerization, vertical slices can be deployed independently, allowing for more frequent and targeted deployments.

In the world of C#, where robustness and efficiency are paramount, adopting vertical slice architecture can be a game-changer. It aligns well with the principles of SOLID, especially the Single Responsibility Principle, ensuring that each slice has one reason to change. As software development continues to evolve, approaches like vertical slicing offer a fresh perspective on building scalable and maintainable applications.


Vertical Slices vs. Traditional Layers: A Deep Dive

In software architecture, there are many different approaches that can be taken. Two prominent variations, the traditional layered architecture, and the more modern vertical slice architecture, are architectural patterns that we’ll be comparing and contrasting. While both have their merits, understanding their differences is crucial for making informed architectural decisions, especially in the context of C# development.

  • Traditional Layered Architecture: This approach organizes code into horizontal layers based on their technical function. Common layers include the data access layer, business logic layer, and presentation layer. Each layer has a distinct responsibility, and they interact in a hierarchical manner. For instance, the presentation layer might call the business logic layer, which in turn interacts with the data access layer.
  • Vertical Slice Architecture: In contrast, vertical slices organize code around features or use cases. Instead of segregating code based on technical function, each slice encompasses all the necessary components to implement a specific feature, from data access to presentation. This results in multiple vertical “slices” that can operate independently of each other.

While the layered approach has been a staple in software design for years, it’s not without its challenges. As applications grow, layers can become bloated, leading to tight coupling between components. Changes in one layer might necessitate changes in others, making the codebase harder to maintain. Moreover, it can be challenging to achieve a clear separation of concerns, especially when features span multiple layers.

Vertical slice architecture aims to mitigate some of the challenges posed by the layered approach. By focusing on features, each slice remains lean and focused. Changes to one feature don’t ripple across the entire application, leading to improved maintainability. Additionally, since each slice is self-contained, there’s a clearer separation of concerns, making the code more modular and easier to understand.


Practical Steps to Implement Vertical Slice Architecture in C#

1. Setting Up the Project Structure: Begin by organizing your C# project around features rather than technical layers. Each feature or domain should have its own folder or namespace, containing all the necessary components, from data models to user interfaces.

2. Organizing Code by Features or Domains: Within each feature folder, further categorize the code based on its role. For instance, a “UserManagement” feature might have sub-folders for “DataAccess,” “Services,” and “UI.” This ensures that while the overall architecture is feature-centric, there’s still a clear organization within each slice.

3. Tips for Ensuring Clean Separation of Concerns Within Each Slice:

  • Single Responsibility: Ensure that each class or component within a slice has a single, well-defined responsibility. This aligns with the Single Responsibility Principle and ensures modularity.
  • Dependency Injection: Leverage dependency injection, especially given the robust support in C# through libraries like Autofac. This ensures that components are loosely coupled and can be easily swapped or mocked for testing.
  • Interface-Based Design: Define clear interfaces for your components, ensuring that the implementation details are abstracted away. This not only promotes clean separation but also makes the code more testable.
  • Avoiding Cross-Slice Dependencies: Ensure that slices are as independent as possible. Avoid situations where one slice directly depends on the internal components of another. Instead, use shared libraries or services to encapsulate common functionality.

The vertical slice architecture in C# offers a fresh, feature-centric approach to software design. By focusing on features and ensuring a clean separation of concerns, developers can create more maintainable, scalable, and modular applications. In fact, plugin architectures lend themselves very well to this:

YouTube player

Hands-On C# Vertical Slice Example

Defining The Scenario

Vertical slice architecture is about encapsulating all the logic for a specific feature within a cohesive unit, ensuring that each feature is isolated from others. This approach contrasts with the traditional layered architecture where components are grouped by their technical function across all features. Let’s dive into a hands-on example to better understand this.

Let’s consider working within an existing e-commerce application. We’ll consider two features that we’d like to add as vertical slices: ViewProductDetails and PlaceOrder. These will be added to the existing code base, and in our example will have a structure that looks like the following:

ECommerceApp
|-- Existing Feature 1
    |-- <some code>
|-- Existing Feature 2
    |-- <some code>
|-- ViewProductDetails
    |-- Repository.cs
    |-- Service.cs
    |-- Controller.cs
|-- PlaceOrder
    |-- Repository.cs
    |-- Service.cs
    |-- Controller.cs

As you can see, within each feature, we end up getting a mini-layered architecture. This can technically be any architectural pattern you’d like to focus on, but generally, when explaining vertical slices we show the vertical slice spanning horizontal layers.

Implementing the ViewProductDetails Feature

Let’s start implementing our first vertical slice. Within the ViewProductDetails feature:

Repository.cs: Fetches product details.

public class ProductDetailsRepository
{
    public Product GetProductById(int productId)
    {
        // Fetch and return product details
    }
}

Service.cs: Contains business logic related to viewing product details.

public class ProductDetailsService
{
    private readonly ProductDetailsRepository _repository;

    public ProductDetailsService(ProductDetailsRepository repository)
    {
        _repository = repository;
    }

    public Product FetchDetails(int productId)
    {
        return _repository.GetProductById(productId);
    }
}

Controller.cs: Displays the product details to the user.

public class ProductDetailsController
{
    private readonly ProductDetailsService _service;

    public ProductDetailsController(ProductDetailsService service)
    {
        _service = service;
    }

    public void Display(int productId)
    {
        var product = _service.FetchDetails(productId);
        Console.WriteLine($"Product Name: {product.Name}");
    }
}

These three classes demonstrate a layered architecture starting from data and working towards the UI.

Implementing the PlaceOrder Feature

For our vertical slice architecture in C#, we can take a similar approach to implementing the second feature!

Repository.cs: Manages order data.

public class OrderRepository
{
    public void SaveOrder(Order order)
    {
        // Save order to the database
    }
}

Service.cs: Contains business logic related to placing an order.

public class OrderService
{
    private readonly OrderRepository _repository;

    public OrderService(OrderRepository repository)
    {
        _repository = repository;
    }

    public void Place(Order order)
    {
        // Business logic for placing an order
        _repository.SaveOrder(order);
    }
}

Controller.cs: Handles user interaction for order placement.

public class OrderController
{
    private readonly OrderService _service;

    public OrderController(OrderService service)
    {
        _service = service;
    }

    public void PlaceOrder(Product product, User user)
    {
        var order = new Order { Product = product, User = user };
        _service.Place(order);
        Console.WriteLine($"Order placed for {product.Name} by {user.Name}");
    }
}

With this in place, we’ve now implemented two vertical slices side by side in our application. By organizing our e-commerce application into vertical slices, each feature becomes a self-contained unit, making it easier to understand, modify, or extend.

This structure ensures that changes in one feature don’t ripple across the entire application, enhancing maintainability. It also allows teams to work on different features simultaneously without stepping on each other’s toes, promoting parallel development.


Common Pitfalls and How to Avoid Them

Transitioning to a vertical slice architecture in C#, especially from a traditional layered approach, can come with its own set of challenges. However, being aware of these pitfalls and knowing how to navigate them can make the journey smoother.

1. Overcomplicating Slices: One of the primary benefits of vertical slices is simplicity. However, there’s a tendency to over-engineer or make slices too granular, leading to unnecessary complexity. Always focus on the primary feature or domain concern when defining a slice. If you find that a slice is becoming too complex, it might be worth revisiting its boundaries.

2. Inconsistent Boundaries: Without clear guidelines, different teams or developers might define slices differently, leading to inconsistency. Establish clear criteria for what constitutes a slice. Regularly review and refactor slices to ensure they align with these criteria.

3. Ignoring Shared Logic: While vertical slices promote feature isolation, it’s essential not to neglect shared logic or common functionalities. Create shared libraries or core slices that can be referenced by other slices. This ensures that common logic remains DRY (Don’t Repeat Yourself) and is consistently implemented across slices.

4. Resistance to Change: Developers accustomed to layered architectures might resist the shift to vertical slices, seeing it as an unnecessary change. Educate teams on the benefits of vertical slice architecture in C# and provide ample resources, training, and support during the transition.


Summarizing Vertical Slice Architecture in C#

A vertical slice architecture in C# is more than just a trend; it’s a transformative approach that can significantly impact how we design and develop software, especially in the C# ecosystem. By focusing on features or domain concerns rather than technical layers, vertical slices offer a more intuitive, maintainable, and scalable architecture.

For developers accustomed to traditional layered architectures, the shift might seem daunting. However, the benefits—improved code organization, enhanced team collaboration, and simplified testing and deployment—make it a compelling choice.

As with any architectural decision, it’s essential to weigh the pros and cons, consider the specific needs of the project, and be open to iterative refinement. But for those willing to embrace it, vertical slice architecture can be a game-changer.

I encourage all developers, whether you’re just starting out or have years of experience, to give vertical slice architecture in C# a try. Dive in, experiment, and witness firsthand how it can streamline your code and elevate your software development practices.

author avatar
Nick Cosentino Principal Software Engineering Manager
Principal Software Engineering Manager at Microsoft. Views are my own.

Leave a Reply