DEV Community

Cover image for C# Clean Architecture With MediatR: How To Build For Flexibility
Dev Leader
Dev Leader

Posted on • Originally published at devleader.ca

C# Clean Architecture With MediatR: How To Build For Flexibility

The post C# Clean Architecture with MediatR: How To Build For Flexibility appeared first on Dev Leader.

In this article, we’ll be going over C# Clean Architecture with MediatR. Starting with Clean Architecture, this is a design philosophy that promotes separation of concerns and scalability, which is highly relevant in modern application development. By dividing software into layers with distinct responsibilities, you can create systems that are easier to maintain, test, and evolve. The core idea centers around the ‘Domain’ and ‘Application’ layers remaining independent of frameworks, databases, and user interfaces, leading to more resilient and flexible software architectures.

MediatR is a .NET library implementing the ‘Mediator’ pattern, which helps reduce dependencies by allowing objects to communicate indirectly through handlers and requests. MediatR facilitates the decoupling of software components, streamlining the implementation of C# Clean Architecture by managing interactions between different parts of an application in an elegant and maintainable manner.

Understanding how to leverage MediatR within C# Clean Architecture can significantly enhance your code’s organization and efficiency. It promotes a clean separation of concerns and a well-structured pipeline for your requests. Together, they empower you to write code that is easier to understand, refactor, and test, ultimately delivering a higher quality product. Or so that’s what we’re hoping for!


Grasping CSharp Clean Architecture with MediatR: Foundations and Benefits

Clean Architecture, as conceived by Robert C. Martin, lays down a blueprint for structuring software applications as a series of concentric layers, each with clear roles and dependencies. At its heart, it focuses on the creation of systems that are modular, maintainable, and seamlessly testable. Its foundational principle advises that dependencies should only point inwards, meaning that inner layers such as Domain Models and Business Rules remain unaffected by changes in outer layers like UI or Infrastructure.

The application of Clean Architecture elevates the standard of your coding endeavors. You achieve separation of concerns, where changes in database access or user interface details ripple minimally through the system. Code quality also gets a significant boost – your business logic becomes the foundation, unaffected by external frameworks and libraries.

Exploring The Layers of Clean Architecture in C

When implementing C# Clean Architecture with MediatR, you will encounter these layers:

  • Domain Layer: Includes entities, enums, exceptions, interfaces, types, and logic specific to the domain itself.

  • Application Layer: Houses application logic and defines interfaces that are implemented by the outer layers. It orchestrates the flow of data to and from the domain entities, and leverages MediatR for commanding and querying.

  • Infrastructure Layer: Provides implementations for interfaces defined in the application layer, such as data access code typically involving Entity Framework or Dapper.

  • Presentation Layer: The UI layer, which could be anything from a web API to a desktop application. This is where MediatR handles come into play for mediating between user actions and application logic.

  • Cross-Cutting Concerns: Aspects that span multiple layers such as logging, caching, and error handling.

Adopting Clean Architecture encourages improvement and adaptability in your software projects. While it’s not the only such architecture that does so, it aims to make it easier!

Getting Started: Clean Architecture in .NET

It's time to level up! Check out this course on Dometrain!


Introducing MediatR in CSharp Clean Architecture

MediatR is a lightweight .NET library that serves as a messaging tool, allowing objects to communicate with each other without direct references, thus following the mediator pattern. It acts as a central point for handling requests and commands, and dispatching them to their corresponding handlers. In C# Clean Architecture, MediatR streamlines the process of decoupling layers by serving as a connection between the UI (or any entry point into the application) and the Application Layer where the business logic resides.

You can find a video of using MediatR here:

Incorporating MediatR into C# Clean Architecture involves adhering to the principle that the Application Layer should not be concerned with the direct invocation of services or data persistence details. MediatR facilitates this by allowing you to construct clear and focused command/query handlers that are only responsible for single actions.

Setting up the MediatR Package in Your Project

Initiating MediatR in a C# project is straightforward. Here’s how to get it rolling:

  1. Install NuGet Packages: To begin using MediatR, you’ll first need to install its package via NuGet into your Application Layer. Run the following command in the Package Manager Console:

    Install-Package MediatR
    
  2. Add MediatR Service: Next, you will add the MediatR service to your application’s dependency injection container. If you’re using ASP.NET Core, this is typically done in the Startup.cs file, within the ConfigureServices method:

    // Assuming your handlers are in the same assembly as your Startup class.
    services.AddMediatR(typeof(Startup));
    
  3. Configure the Mediator: The configuration process links your requests to their handlers. MediatR automatically identifies request-handler pairs by scanning the assembly, alleviating you from any manual mapping.

An Example With MediatR

Let’s illustrate with a code snippet featuring a CreateUserCommand and its handler inside the Application Layer:

public class CreateUserCommand : IRequest<bool>
{
    public string Username { get; set; }
    // Additional properties here
}

public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand, bool>
{
    public Task<bool> Handle(
        CreateUserCommand request,
        CancellationToken cancellationToken)
    {
        // Handle the logic to create a user here
        return Task.FromResult(true);
    }
}
Enter fullscreen mode Exit fullscreen mode

By executing these steps, you’ve laid the groundwork for enhanced command and query handling in your applications, all while maintaining the separation of concerns promoted by Clean Architecture.


Implementing Commands and Queries with MediatR

Incorporating the Command Query Responsibility Segregation (CQRS) pattern into your application’s architecture means clearly separating the operations for reading data (queries) from the operations for modifying data (commands). This approach aligns perfectly with MediatR’s ability to distinctly separate requests that change the state of a system from those that simply retrieve data. When applied within C# Clean Architecture with MediatR, CQRS helps maintain a clean separation and high coherence of the various components involved.

Commands in MediatR represent operations that change the state of the application, such as adding or updating data. They often encapsulate all data required to perform the action and return void or a result indicating a command result. In contrast, queries fetch data from the application, typically returning a read-only response without causing any side effects.

A Command and Query Implementation Example

Here’s an example of command and query handlers with MediatR in C#:

// Command
public class UpdateOrderCommand : IRequest<bool>
{
    public int OrderId { get; set; }
    public bool IsShipped { get; set; }
}

public class UpdateOrderCommandHandler : IRequestHandler<UpdateOrderCommand, bool>
{
    public Task<bool> Handle(
        UpdateOrderCommand request,
        CancellationToken cancellationToken)
    {
        // Logic to update the order shipping status
    }
}

// Query
public class GetOrderByIdQuery : IRequest<OrderDto>
{
    public int OrderId { get; set; }
}

public class GetOrderByIdQueryHandler : IRequestHandler<GetOrderByIdQuery, OrderDto>
{
    public Task<OrderDto> Handle(
        GetOrderByIdQuery request,
        CancellationToken cancellationToken)
    {
        // Logic to retrieve the order by ID
    }
}
Enter fullscreen mode Exit fullscreen mode

Enhancing Code Readability and Maintenance

Leveraging command and query patterns with MediatR can improve the readability and maintenance of your codebase. It does so by encapsulating business logic within clearly defined boundaries that lead to a more organized and intuitive code structure. This eases the cognitive load on developers who are navigating and updating the application.

Consider adhering to the following tips for maximum clarity:

  • Consistent Naming: Always name commands and queries after the actions they perform; for example, CreateProductCommand or GetUserByIdQuery. This immediately communicates their purpose.

  • Focused Classes: Command and query classes should be lean and specialized, containing only properties relevant to the particular action.

  • Logic Segregation: Ensure command handlers execute precisely one action, modifying state, and that query handlers focus exclusively on fetching data.

These guidelines help ensure that the purpose and responsibility of each class are apparent, streamlining future development work and reducing the risk of introducing unwanted side effects when making changes.


CSharp Clean Architecture with MediatR: Best Practices

Integrating MediatR into Clean Architecture in C# can help streamline communication between layers in your application. However, there are some best practices to ensure your implementation is effective, maintainable, and scalable. Otherwise, you run into common pitfalls that can lead to tightly coupled code, poor performance, and difficulty in identifying and resolving errors — The exact opposite of what we want here!

When it comes to error handling, keep your logic consistent across command and query handlers. Use exceptions sparingly (I prefer a custom result type), and consider implementing global exception handling that can catch and process unhandled exceptions. This helps to centralize your error management logic and keeps your handlers clean and focused where otherwise you have similar-but-potentially-different logic scattered everywhere.

For unit testing, every command and query handler should be covered by tests to ensure they perform the expected operations correctly — Or at least be written in a way where you CAN test them. Mocking dependencies and using in-memory databases (or check out Testcontainers!) can help simulate different scenarios.

When writing tests, I find it valuable to follow the Arrange-Act-Assert pattern, where you first set up the test environment (Arrange), execute the command or query (Act), and then verify the results (Assert). This structured approach leads to more readable and maintainable test cases, and it’s popular amongst many programmers.

Optimizing Performance and Scalability

In larger applications, where performance and scalability become paramount, there are additional considerations to keep in mind when implementing C# Clean Architecture with MediatR. One consideration is the use of pipeline behaviors. These can intercept requests and offer a place to implement cross-cutting concerns, such as logging, validation, or performance tracing, without cluttering the handlers themselves.

Caching is another crucial element to address. By caching frequently accessed data, you can reduce database calls and improve response times. However, it’s crucial to ensure your caching strategy does not introduce outdated data or complex cache invalidation logic.

To keep your MediatR implementation performant and scalable, here are some useful guidelines:

  • Simple Handlers: Keep your handlers as simple as possible. Complex logic in handlers can be difficult to maintain and test.

  • Asynchronous Processing: Make use of asynchronous processing to handle requests without blocking threads, allowing for higher throughput.

  • Avoid Premature Optimizations: Implement performance optimizations thoughtfully. Over-optimization can sometimes lead to unnecessary complexity, so always profile and identify bottlenecks before optimizing.

  • Dependency Injection: Leverage dependency injection to manage handler dependencies. This makes your application easier to scale and maintain.

By following these best practices for C# Clean Architecture with MediatR, you ensure that your application not only benefits from a well-organized structure but is also prepared to grow and adapt to future demands efficiently.


Wrapping Up CSharp Clean Architecture with MediatR

C# Clean Architecture with MediatR can help us build some awesome applications — but it’s important we understand what we’re working with. We saw that Clean Architecture is a software design philosophy that emphasizes modular, maintainable, and testable design. Its key principles include separation of concerns, the dependency inversion principle, and the use of defined software layers to organize the codebase.

We also covered MediatR, which is a mediator pattern implementation in .NET that allows in-process messaging between objects. In Clean Architecture, MediatR helps decouple layers by allowing indirect communication between parts of the application. It ties very nicely into the architecture’s emphasis on separation of concerns.

I hope that you have an opportunity to try implementing MediatR in your application! Let me know how it goes!

Getting Started: Clean Architecture in .NET

It's time to level up! Check out this course on Dometrain!


Want More Dev Leader Content?

  • Follow along on this platform if you haven’t already!
  • Subscribe to my free weekly software engineering and dotnet-focused newsletter. I include exclusive articles and early access to videos: SUBSCRIBE FOR FREE
  • Looking for courses? Check out my offerings: VIEW COURSES
  • E-Books & other resources: VIEW RESOURCES
  • Watch hundreds of full-length videos on my YouTube channel: VISIT CHANNEL
  • Visit my website for hundreds of articles on various software engineering topics (including code snippets): VISIT WEBSITE
  • Check out the repository with many code examples from my articles and videos on GitHub: VIEW REPOSITORY

Top comments (0)