DEV Community

Leonardo
Leonardo

Posted on • Updated on

Agile and Modular Development with Vertical Slice Architecture and MediatR in C# Projects

Developing software in an agile and efficient manner is essential to meet the growing demands of modern software projects. The combination of Vertical Slice Architecture with the MediatR library in C# projects offers a powerful approach to creating modular, decoupled, and high-quality software. In this article, we will delve into Vertical Slice Architecture, show how MediatR seamlessly integrates with this approach.

Understanding Vertical Slice Architecture

Vertical Slice Architecture is a software design methodology that emphasizes the development of complete and independent functionalities, as opposed to traditional layer-based organization. Instead of structuring code into layers such as presentation, business logic, and data access, Vertical Slice Architecture organizes code into "vertical slices," where each slice represents a complete system functionality. This includes not only the user interface but also the business logic and data access related to that specific functionality.

Introduction to MediatR

MediatR is a popular library in .NET that simplifies the implementation of request mediation patterns. It allows separating business logic into request handlers, making the code more organized, decoupled, and easy to maintain. MediatR aligns perfectly with Vertical Slice Architecture as it helps orchestrate actions within a vertical slice.

Folder Structure for Vertical Slice Architecture Projects

A well-organized folder structure is essential for maintaining code clarity and manageability in projects following Vertical Slice Architecture. Below is a suggested folder structure:

MyProject/
|-- MyApiProject/
|   |-- Controllers/
|   |   |-- CartController.cs
|   |   |-- UserController.cs
|-- MyApplicationProject/
|   |-- Modules/
|   |   |-- Cart/
|   |   |   |-- AddToCart/
|   |   |   |   |-- Requests/
|   |   |   |   |   |-- AddToCartRequest.cs
|   |   |   |   |-- Handlers/
|   |   |   |   |   |-- AddToCartHandler.cs
|   |   |   |   |-- Validators/ (optional)
|   |   |   |   |   |-- AddToCartValidator.cs
|   |-- User/
|   |   |   |-- CreateUser/
|   |   |   |   |-- Requests/
|   |   |   |   |   |-- CreateUserRequest.cs
|   |   |   |   |-- Handlers/
|   |   |   |   |   |-- CreateUserHandler.cs
|   |   |   |   |-- Validators/ (optional)
|   |   |   |   |   |-- CreateUserValidator.cs
|-- MySharedProject/
|   |-- Infrastructure/
|   |   |-- Persistence/
|   |   |   |-- DbContext.cs
|   |   |-- Services/
|   |   |   |-- EmailService.cs
|-- MyProject.sln
Enter fullscreen mode Exit fullscreen mode

Suggested Folder Structure:

  • Modules: This folder is the core of Vertical Slice Architecture. It houses subfolders for each module that represents a specific system functionality. For example, you may have a module called "Cart" for shopping cart-related functionalities and another module called "User" for user-related functionalities.

  • Controllers: Within each module, you can find the "Controllers" folder. This is the user interface layer where controllers reside. Controllers are responsible for handling user interface interactions related to a specific module.

  • Requests and Handlers: For each module, there are separate subfolders called "Requests" and "Handlers." Requests are structured to define the data needed to perform an action within the module. On the other hand, request handlers are responsible for implementing the corresponding business logic for these requests. This keeps the module's business logic encapsulated and well-organized.

  • Validators (optional): The "Validators" folder is an optional but valuable addition. Here, you can include validators that ensure input data is valid before being processed by request handlers. This contributes to data integrity and system security.

  • Shared: Finally, the "Shared" folder is the place for shared code between modules. This includes infrastructure services, general configurations, and any functionality used by multiple modules. This organization promotes code reuse and ease of maintenance.

This folder structure provides a clear division of responsibilities in your project, following Vertical Slice Architecture guidelines. Each module is an independent and cohesive unit of functionality, making your code more modular, scalable, and manageable.

Implementation with MediatR in Detail

Now, let's delve deeper into our understanding of Vertical Slice Architecture and MediatR with a practical example of implementation in a specific module, such as "Cart."

Step 1: Identifying the Functionality

Let's assume we want to implement the "Add a product to the shopping cart" functionality.

Step 2: Developing the Vertical Slice

Defining a Request

First, we create a AddToCartRequest class that contains information about the product to be added to the cart.

public class AddToCartRequest : IRequest<Result>
{
    public int ProductId { get; set; }
    public int Quantity { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Creating a Request Handler

Next, we implement a request handler AddToCartHandler that processes the request and adds the product to the cart.

public class AddToCartHandler : IRequestHandler<AddToCartRequest, Result>
{
    private readonly ICartService _cartService;

    public AddToCartHandler(ICartService cartService)
    {
        _cartService = cartService;
    }

    public async Task<Result> Handle(AddToCartRequest request, CancellationToken cancellationToken)
    {
        // Logic to add the product to the cart using the cart service
        bool addToCartResult = await _cartService.AddToCart(request.ProductId, request.Quantity);

        bool isAddToCartSuccessful = addToCartResult; // Check if adding the product to the cart was successful.
        return Result.SuccessIf(isAddToCartSuccessful, "Failed to add the product to the cart."); // Return failure if adding to cart fails.
    }
}
Enter fullscreen mode Exit fullscreen mode

Using MediatR in the Controller

In the control or application layer, we use MediatR to process the request.

public class CartController : ControllerBase
{
    private readonly IMediator _mediator;

    public CartController(IMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpPost]
    public async Task<IActionResult> AddToCart([FromBody] AddToCartRequest request)
    {
        var result = await _mediator.Send(request);

        if (result.IsSuccess)
        {
            return Ok("Product added to the cart successfully.");
        }
        else
        {
            return BadRequest(result.ErrorMessage);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Testing and Validation

After implementing the vertical slice, we perform comprehensive testing to ensure that the functionality works as expected. We test both success cases and error scenarios, verifying that the product is added correctly to the cart.

Benefits of Vertical Slice Architecture with MediatR

Adopting Vertical Slice Architecture with MediatR offers several benefits:

  • Rapid Development: It enables rapid development of independent functionalities.

  • Decoupling: The use of MediatR promotes a high degree of decoupling between parts of the system, facilitating maintenance and scalability.

  • Testability: The MediatR structure makes it easier to write unit tests for request handlers.

  • Scalability: As the system grows, new vertical slices can be added without affecting existing ones, making the system highly scalable.

Conclusion

The combination of Vertical Slice Architecture and MediatR is a powerful approach to agile development in C# projects. It allows the creation of independent and decoupled functionalities, accelerating development and easing code maintenance. By implementing this approach in your team, you will be well-prepared to confidently tackle the complex challenges of software development.

In this article, we have delved into Vertical Slice Architecture and MediatR. Now, it's time to apply these concepts to your own projects and reap the benefits of more efficient and modular development. As you embrace these practices, you will be better equipped to meet the dynamic needs of the software development world.

Top comments (0)