Rules to Better Clean Architecture

​​​

Hold on a second! How would you like to view this content?
Just the title! A brief blurb! Gimme everything!
  1. Do you know the main principles of Clean Architecture?

    With Clean Architecture the Domain and the Application layers are at the centre of the design. This is known as the Core of the application. The Domain layer contains the enterprise logic and types and the Application layer contains the business logic and types. The difference being that enterprise logic could be shared with other systems whereas business logic would typically be specific to this system.

    onion-view-clean-architecture.png
    Figure: Onion View of Clean Architecture​

    Instead of having Core depend on data access and other infrastructure concerns we invert these dependencies, therefore Infrastructure, Persistence, and Presentation all depend on Core. This is achieved by adding abstractions, such as interfaces or abstract base classes, to the Application layer. Layers outside of Core, such as Infrastructure and Persistence, then implement these abstractions.

    A good example is an implementation of the Repository pattern. Within this design, we would first add an IRepository interface to the Application layer. Next, we would implement this interface within Persistence by creating a Repository class using our preferred data access technology. Finally, within Core the logic we write will only use the IRepository interface, so Core will remain independent of data access concerns.

    With this design, all dependencies must flow inwards. Core has no dependencies on any outside layers. Infrastructure, Persistence, and Presentation depend on Core, but not on one another.

    This results in an architecture and design that is:

    • Independent of Frameworks - Core should not be dependent on external frameworks such as Entity Framework
    • Testable -The logic within Core can tested independently of anything external, such as UI, databases, servers. Without external dependencies the tests are very simple to write.
    • Independent of UI - It is easy to swap out the Web UI for a Console UI, or Angular for Vue. Logic is contained within Core, so changing the UI will not impact logic.
    • Independent of Database - Initially you might choose SQL Server or Oracle, but soon we will all be switching to Cosmos DB
    • Independent of anything agency - Core simply doesn't know anything about the outside world

    While the design in the below figure only includes three circles, you may need more - just think of this as a starting point.

    ​References

  2. Do you know the best Clean Architecture learning resources?

    ​There are many great resources for learning the principles of Clean Architecture, but a the best place to start is with Jason Taylor’s video:
     

    To read further, start with Robert C Martin’s blog post: The Clean Architecture.

    ​Then, check out these books:

    This book by Robert C. Martin (aka ‘Uncle Bob’) should be anyone’s starting point for reading further.

    clean-architecture-book-1.jpg
    Figure: Clean Architecture: A Craftsman's Guide to Software Structure and Design

    This resource by Steve Smith is available as an online e-book and contains up-to-date specific examples for ASP.NET Core and Azure.​

    clean-architecture-book-2.png​​​
    Figure: Architecting Modern Web Applications with ASP.NET Core and Microsoft Azure
  3. Do you keep your domain layer independent of the data access concerns?

    The domain layer should be independent of data access concerns. The domain layer should only change when something within the domain changes, not when the data access technology changes. Doing so ensures that the system will be easier to maintain well into the future, since changes to data access technologies won't impact the domain, and vice versa.

    This is often a problem when building systems that leverage Entity Framework, as it's common for data annotations to be added to the domain model. Data annotations, such as the Required or MinLength attributes, support validation and help Entity Framework to map objects into the relational model. In the next example, data annotations are used within the domain model:

    domain-layer-1.png
    Bad Example: Domain is cluttered with data annotations

    As you can see in the above example, the domain is cluttered with data annotations. If the data access technology changes, we will likely need to change all entities as all entities will have data annotations. In the following example, we will remove the data annotations from the entity and instead use a special configuration type:

    domain-layer-2.png
    domain-layer-3.png
    Good Example: Domain is lean, configuration for entity is contained within a separate configuration type

    This is a big improvement! Now the customer entity is lean, and the configuration can be added to the persistence layer, completely separate of the domain. Now the domain is independent of data access concerns.

    Learn more about this approach by reading about self-contained configuration for code first.​

  4. Do you keep business logic out of the presentation layer?

    ​It's common for business logic to be added directly to the presentation layer. When building ASP.NET MVC systems, this typically means that business logic is added to controllers as per the following example:​

    business-logic-presentation-layer-bad.png
    Figure: Bad example - Although this application clearly has repository and business logic layers, the logic that orchestrates these dependencies is in the ASP.NET Controller and is difficult to reuse

    The logic in the above controller cannot be reused, for example, by a new console application. This might be fine for trivial or small systems but would be a mistake for enterprise systems. It is important to ensure that logic such as this is independent of the UI so that the system will be easy to maintain now and well into the future. A great approach to solving this problem is to use the mediator pattern with CQRS.

    CQRS stands for Command Query Responsibility Segregation. It's a pattern that I first heard described by Greg Young. At its heart is the notion that you can use a different model to update information than the model you use to read information
    ...
    There's room for considerable variation here. The in-memory models may share the same database, in which case the database acts as the communication between the two models. However they may also use separate databases, effectively making the query-side's database into a real-time reporting database.
    Martin Fowler - https://martinfowler.com/bliki/CQRS.html ​

     
    CQRS means clear separation between Commands (Write operations) and Queries (Read operations).
    CQRS can be used with complex architectures such as Event Sourcing but the concepts can also be applied to simpler applications with a single database.​

    ​MediatR is an open source .NET library by Jimmy Bogard that provides an elegant and powerful approach for writing CQRS, making it easier to write clean code.

    For every command or query, you create a specific request class that explicitly defines the “input” required to invoke the operation.​

    business-logic-presentation-layer-simple.png
    Figure: (from MediatR docs) A Simple Request class

    Then the implementation of that command or query is implemented in a handler class. The handler class is instantiated by a Dependency Injection container – so can use any of the configured dependencies (Repositories, Entity Framework, services etc).

    business-logic-presentation-layer-handler.png
    Figure: A handler class

    This approach brings many benefits:

    • Each command or query represents an atomic, well-defined operation such as "Get My Order Details" (Query) or "Add Product X to My Order" (Command)
    • In Web APIs, this encourages developers to keep logic out of controllers. The role of controllers becomes reduced to "Receive a request from the web and immediately dispatch to MediatR". This helps implement the "Thin controllers" rule:  https://rules.ssw.com.au/do-you-use-thin-controllers-fat-models-and-dumb-views. When logic is in a controller, the only way to invoke it is via web requests. Logic in a mediator handler can be invoked by any process that is able to build the appropriate request object, such as background workers, console programs or SignalR hubs
    • Mediator also provides a simple pub/sub system allowing "side effects" to be explicitly implemented as additional, separate handlers. This is great for related or event-driven operations such as "Update the search index after a change to the product has been saved to database"
    • Using a specific handler class for each operation means that there is a specific dependency configuration for each command or query
    • Developers often implement interfaces and abstractions between the layers of their applications. Examples of this might include an IMessageService for sending emails or an IRepository interface to abstract database access. These techniques abstract specific external dependencies such as "How to save an order entity in the database" or "How to send an email message". We have witnessed many applications with clean, persistence-ignorant repository layers but then with messy spaghetti code on top for the actual business logic operations. MediatR commands and queries are better at abstracting and orchestrating higher-level operations such as "Complete my order" that may or may not use lower-level abstractions. Adopting MediatR encourages clean code from the top down and help developers "fall into the pit of success"
    • Building even a simple app with this approach makes it easy to consider more advanced architectures such as event sourcing. You have clearly defined "What data can I get" and "What operations can I perform". You are then free to iterate on the best implementation to deliver the defined operations. MediatR handlers are easy to mock and unit test
    • MediatR handlers are easy to mock and unit test
    • The interface for MediatR handlers encourages the implementation of best-practice async methods with cancellation token support.
    • MediatR introduces a pipeline behaviour system allowing custom to be injected around handler invocation. This is useful for implementing cross-cutting concerns such as logging, validation or caching 
    • For complex operations, it’s possible to compose from multiple smaller commands and queries. Each command or query is an atomic, potentially reusable operation. Such complexity should be adopted very carefully. The developer should be aware of two sometimes conflicting principles: DRY or Don't Repeat Yourself and SRP or the Single Responsibility Principle. In practice, any branching logic inside a handler to support use inside multiple contexts should be considered a violation of the Single Responsibility Principle and should be aggressively avoided​
    business-logic-presentation-layer-good.png
    Figure: Good example - MediatR simplifies the dependencies injected into the controller. The incoming web request is simply mapped directly to a MediatR request that orchestrates all the logic for this operation. The implementation and dependencies needed to complete “GetItemForEdit” are free to change without needing to change the controller class

  5. Do you know how to improve the discoverability of your MediatR requests?

    ​​When using MediatR within an ASP.NET Controller it is typical to see actions such as the following:

    improve-mediatr-typical.png
    Figure: A Typical ASP.NET Controller using Mediator

    In the above example, the API contains a Create action that includes a CreateProductCommand parameter. This command is a request to create a new product, and the request is associated with an underlying request handler. The request is sent using MediatR with the method call _mediator.Send(command). ​​MediatR will match the request to the associated request handler and return the response (if any). In a typical implementation, the request and request handler would be contained within separate files:

    improve-mediatr-bad.png
    Figure: Bad Example - The request is contained within CreateProductCommand.cs
    improve-mediatr-bad-2.png
    Figure: Bad Example - The request handler is contained within CreateProductCommandHandler.cs

    In the above implementation, the request handler is cleanly separated from the request. However, this separation results in reducing discoverability of the handler. For example, a developer looking at the action in this first figure might be interested in the logic behind the command. So, they press F12 to go to the definition and can see the request (CreateProductCommand), but not the logic since it is contained within the request handler (CreateProductCommandHandler). The developer must then navigate to the handler using Solution Explorer or some keyboard wizardry. This is assuming that the developer is familiar with the design, and knows that there is an underlying request handler that contains the logic of interest. We can avoid these problems and improve discoverability by instead using the following approach:

    business-logic-presentation-layer-good.png
    Figure: Good Example - Nesting the Request Handler within the Request Improves Discoverability of Command and Associated Command Logic

    In the above example the request handler is nested within the request, there by improving the discoverability of the command and associated command logic.


  6. Do you know the difference between data transfer objects and view models?

    Data Transfer Objects (DTOs) and View Models (VMs) are not the same concept! The main difference is that while VMs can encapsulate behaviour, DTOs do not.

    The purpose of a DTO is the transfer of data from one part of an application to another. Since DTOs do not encapsulate behaviour, they can easily be serialised and deserialized into other formats, e.g. JSON, XML, and so on.​

    The purpose of a VM is also the transfer of data, however VMs can encapsulate behaviour. This behaviour is useful, for example, when creating a WPF + MVVM application, but not so useful when creating a SPA - since you can't serialize the behaviour and pass it from ASP.NET MVC to the client.

    Learn more about the above concepts in the following Weekly Dev Tips podcasts:

    ·  Data Transfer Objects (part 1)

    ·  Data Transfer Objects (part 2)

  7. Do you know the best approach to validate your client requests?

    ​When building Web APIs, it is important to validate each request to ensure that it meets all expected pre-conditions. The system should process valid requests but return an error for any invalid requests. In the case of ASP.NET Controllers, such validation could be implemented as follows:​

    validate-client-requests-bad.png
    Figure: Bad Example - Managing Request Validation within the Controller

    In the above example, model state validation is used to ensure the request is validated before it is sent using MediatR. I am sure you are wondering - why is this a bad example? Because in the case of creating products, we want to validate every request to create a product, not just those that come through the Web API. For example, if we're creating products using a console application that invokes the command directly, we need to ensure that those requests are valid too. So clearly the responsibility for validating requests does not belong within the Web API, but rather in a deeper layer, ideally just before the request is actioned.

    One approach to solving this problem is to move validation to the Application layer, validate immediately before the request is executed. In the case of the above example, this could be implemented as follows:

    validate-client-requests-ok.png
    Figure: OK Example - Validation Handled Manually within Request Handler Ensuring All Requests are Validated

    The above implementation solves the problem. Whether the request originates from the Web API or a console app it will be validated before further processing occurs. However, the above code is boilerplate and will need to be repeated for each and every request that requires validation. And of course, it will only work if the developer remembers to include the validation check in the first place!
     
    Fortunately, if you are following our recommendations and combining CQRS with MediatR you can solve this problem by incorporating the following behaviour into your MediatR pipeline:

    validate-client-requests-good.png
    Figure: Good Example - Automatically Validate All Requests By Using a MediatR Pipeline Behaviour

    This RequestValidationBehavior class will automatically validate all incoming requests and throw a ValidationException should the request be invalid. This is the best and easiest approach since existing requests, and new requests added later, will be automatically validated. This is possible through the power of MediatR pipeline behaviours. The documentation for MediatR includes a section on Behaviours; https://github.com/jbogard/MediatR/wiki/Behaviors. Review this documentation to understand how you can enhance your request handlers with behaviours and how to register pipeline behaviours.​

    The only step that remains is handle any validation exceptions. Within the console app, a try catch block will suffice. The action taken within the catch block will of course depend on requirements. Within the Web API, use an ExceptionFilterAttribute to catch these exceptions and convert them into a BadRequest result as follows:

    validate-client-requests-good-2.png
    Figure: Good Example – Use an ExceptionFilterAttribute to Catch and Handle Exceptions within the Web API
  8. Do you know when to use value objects?

    When defining a domain, entities are created and consist of properties and methods. The properties represent the internal state of the entity and the methods are the actions that can be performed. The properties typically use primitive types such as strings, numbers, dates, and so on.​​

    As an example, consider an AD account. An AD Account consists of a domain name and user name, e.g. SSW\Jason. It is a string so using the string type makes sense. Or does it?

    when-use-value-bad.png
    Figure: Bad Example - Storing an AD Account as a String (AD Account is a complex type)

    An AD Account is a complex type. Only certain strings are valid AD accounts. Sometimes you will want the string representation (SSW\Jason), sometimes you will need the domain name (SSW), and sometimes just the user name (Jason). All of this requires logic and validation, and the logic and validation cannot be provided by the string primitive type. Clearly, what is required is a more complex type such as a value object.

    when-use-value-good.png
    Figure: Good Example - Storing an AD Account as a Value Object to Support Logic and Validation

    The underlying implementation for the AdAccount class is as follows:

    when-use-value-good-2.png
    Figure: Good Example - Implementation of the AdAccount Value Object Supports Logic and Validation

    The AdAccount type is based on the ValueObject type, which you can view here; https://github.com/SSWConsulting/NorthwindTraders/blob/master/Northwind.Domain/Infrastructure/ValueObject.cs.

    Working with the AD accounts will now be easy. You can construct a new AdAccount with the factory method For as follows:

    when-use-value-eg-1.png

    The factory method For ensures only valid AD accounts can be constructed and for invalid AD account strings, exceptions are meaningful, i.e. AdAccountInvalidException rather than IndexOutOfRangeException.

    Given an AdAccount named account, you can access:

    1. The domain name using; account.Domain
    2. The user name using; account.Name
    3. The full account name using; account.ToString()

    The value object also supports implicit and explicit conversion operators. You can:

    1. Implicitly convert from AdAccount to string using; (string)account
    2. Explicitly convert from string to AdAccount using; (AdAccount)"SSW\\Jason"

    If you're using Entity Framework Core, you should also configure the type as follows:

    when-use-value-eg-2.png
    Figure: Using Entity Framework Core to Configure Value Objects as Owned Entity Types

    ​With the above configuration in place, EF Core will name the database columns for the properties of the owned entity type as AdAccount_Domain and AdAccount_Name. You can learn more about Owned Entity Types by reviewing the EF Core documentation.​

    Next time you are building an entity, consider carefully if the type you are defining is a primitive type or a complex type. Primitive types work well for storing simple state such as first name or order count, complex types work best when defining types that include complex logic or validation such as postal or email addresses. Using a value object to encapsulate logic and validation will simplify your overall design.