Skip to content

Clean Architecture sample with .NET Core. Use cases as central organising structure, decoupled from frameworks and technology details

License

Notifications You must be signed in to change notification settings

anurbecirovic/clean-architecture-manga

 
 

Repository files navigation

Manga: The Clean Architecture Sample Implementation with .NET Core 🌀

All Contributors Build status

TODO: Add High level architecture and use cases image

Sample implementation of the Clean Architecture Principles with .NET Core. Use cases as central organizing structure, decoupled from frameworks and technology details. Has small components that are developed and tested in isolation.

Motivation

Learn how to design modular applications.

Explore the .NET Core tooling.

Learn how to design modular applications

Learning how to design modular applications will help you become a better engineer. Designing modular applications is the holy grail of software architecture, in our industry it is hard to find engineers that know how to design applications which allows adding new features in a steady speed.

Explore the .NET Core tooling

.NET Core brings a sweet development environment, an extensible and cross-platform framework. We will explore the benefits of it in the infrastructure layer and we will reduce the importance of it in the domain. The same rule is applied for modern C# constructions.

Learn from the open source community

This is continually updated, open source project.

Contributions are welcome!

Contributing

Learn from the community.

Feel free to submit pull requests to help:

  • Fix errors
  • Improve sections
  • Add new sections
  • Submit questions and bugs

Index of Clean Architecture Manga

Use Cases Description

Application architecture is about usage, a good architecture screams the business use cases to the developer and framework concerns are implementation details. On Manga sample the user can Register an account then manage the balance with Deposits and Withdrawals.

Register

An customer can register the account using his personal details.

Deposit

The customer can deposit a positive amount.

Transfer

The customer can transfer money from one account to another.

Withdraw

A customer can withdraw money but not more that the current balance.

Get Customer Details

Customer details with all accounts and transactions are returned.

Get Account Details

Account details with transactions are returned.

Close Account

Close an account, requires zero balance.

Flow of Control

The flow of control begins in the controller, moves through the use case, and then winds up executing in the presenter.

Register Flow of Control

  1. An request in received by the CustomersController and an action Post is invoked.
  2. The action creates an RegisterInput message and the Register use case is executed.
  3. The Register use case creates a Customer and an Account. Repositories are called, the RegisterOutput message is built and sent to the RegisterPresenter.
  4. The RegisterPresenter builds the HTTP Response message.
  5. The CustomersController asks the presenter the current response.

Register Flow of Control

Get Customer Details Flow of Control

  1. An request in received by the CustomersController and an action GetCustomer is invoked.
  2. The action creates an GetCustomerDetailsInput message and the GetCustomerDetails use case is executed.
  3. The GetCustomerDetails use case asks the repositories about the Customer and the Account. It could call the NotFound or the Default port of the GetCustomerDetailsPresenter depending if it exists or not.
  4. The GetCustomerDetailsPresenter builds the HTTP Response message.
  5. The CustomersController asks the presenter the current response.

Architecture Styles

Manga use ideas from popular architectural styles. They Ports and Adapters are the simplest one followed by the others, they complement each other and aim a software with use cases decoupled from implementation details.

Ports and Adapters Architecture Style

The Ports and Adapters Architecture Style divides the application into Application Core and Adapters in which the adapters are interchangeable components developed and tested in isolation. The Application Core is loosely coupled to the Adapters and their implementation details.

Onion Architecture Style

Very similar to Ports and Adapters, I would add that data objects cross boundaries as simple data structures. For instance, when the controller execute an use case it passes and immutable Input message. When the use cases calls an Presenter it gives a Output message (Data Transfer Objects if you like).

Clean Architecture Style

An application architecture implementation guided by tests cases.

Design Patterns

The following Design Patterns will help you continue implementing use cases in a consistent way.

Controller

Controllers receive Requests, build an Input message then call an Use Case, you should notice that the controller do not build the ViewModel, instead this responsibility is delegated to the presenter object.

public sealed class CustomersController : Controller
{
    
    // code omitted to simplify

    public async Task<IActionResult> Post([FromBody][Required] RegisterRequest request)
    {
        await _registerUseCase.Execute(new RegisterInput(
            new SSN(request.SSN),
            new Name(request.Name),
            new PositiveAmount(request.InitialAmount)));

        return _presenter.ViewModel;
    }
}

ViewModel

ViewModels are data transfer objects, they will be rendered by the MVC framework so we need to follow the framework guidelines. I suggest that you add [Required] attributes so swagger generators could know the properties that are not nullable. My personal preference is to avoid getters here because you know how the response object will be created, so use the constructor.

/// <summary>
/// The response for Registration
/// </summary>
public sealed class RegisterResponse
{
    /// <summary>
    /// Customer ID
    /// </summary>
    [Required]
    public Guid CustomerId { get; }

    /// <summary>
    /// SSN
    /// </summary>
    [Required]
    public string SSN { get; }

    /// <summary>
    /// Name
    /// </summary>
    [Required]
    public string Name { get; }

    /// <summary>
    /// Accounts
    /// </summary>
    [Required]
    public List<AccountDetailsModel> Accounts { get; }

    public RegisterResponse(
        Guid customerId,
        string ssn,
        string name,
        List<AccountDetailsModel> accounts)
    {
        CustomerId = customerId;
        SSN = ssn;
        Name = name;
        Accounts = accounts;
    }
}

Presenter

Presenters build the Response objects when called by the application.

public sealed class RegisterPresenter : IOutputPort
{
    public IActionResult ViewModel { get; private set; }

    public void Error(string message)
    {
        var problemDetails = new ProblemDetails()
        {
            Title = "An error occurred",
            Detail = message
        };

        ViewModel = new BadRequestObjectResult(problemDetails);
    }

    public void Standard(RegisterOutput output)
    {
        /// long object creation omitted

        ViewModel = new CreatedAtRouteResult("GetCustomer",
            new
            {
                customerId = model.CustomerId
            },
            model);
    }
}

It is important to understand that from the Application perspective the use cases see an OutputPort with custom methods to call dependent on the message, and from the Web Api perspective the Controller only see the ViewModel property.

Standard Output

The output port for the use case regular behavior.

Error Output

Called when an blocking errors happens.

Alternative Output

Called when an blocking errors happens.

Value Object

Describe the tiny domain business rules. Objects that are unique by the has of their properties. Are immutable.

public sealed class Name : IEquatable<Name>
{
    private string _text;

    private Name() { }

    public Name(string text)
    {
        if (string.IsNullOrWhiteSpace(text))
            throw new NameShouldNotBeEmptyException("The 'Name' field is required");

        _text = text;
    }

    public override string ToString()
    {
        return _text;
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj))
        {
            return false;
        }

        if (ReferenceEquals(this, obj))
        {
            return true;
        }

        if (obj is string)
        {
            return obj.ToString() == _text;
        }

        return ((Name) obj)._text == _text;
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = 17;
            hash = hash * 23 + _text.GetHashCode();
            return hash;
        }
    }

    public bool Equals(Name other)
    {
        return this._text == other._text;
    }
}

Entity

Objects that are unique by their IDs. Entities are mutable and instances of domain concepts.

Aggregate Root

Repository

Unit of Work

Use Case

Separation of Concerns

Encapsulation

Test-Driven Development TDD

Outside-In Approach

Fakes

Clean Tests

xUnit

SOLID

Single Responsibilty Principle

Open-Closed Principle

Liskov Substituiton Principle

Interface Segregation Principle

Dependency Inversion Principle

.NET Core

.NET Core Web API

Swagger

API Versioning

Microsoft Extensions

Feature Flags

Logging

Localizing

Data Annotations

Authentication

Authorization

Entity Framework Core

Add Migration

Run the EF Tool to add a migration to the Manga.Infrastructure project.

$ dotnet ef migrations add "InitialCreate" -o "EntityFrameworkDataAccess/Migrations" --project source/Manga.Infrastructure --startup-project source/Manga.WebApi

Update Database

Generate tables and seed the database via Entity Framework Tool:

dotnet ef database update --project source/Manga.Infrastructure --startup-project source/Manga.WebApi

Environment Configurations

DevOps

Running the Application Locally

Manga is a cross-platform application, you can run it from Mac, Windows or Unix. To develop new features, you may use Visual Studio or Visual Studio Code ❤️.

The single requirement is to install the latest .NET Code SDK.

We made avaiable scripts to create and seed the database quickly via Docker.

Finally to run it locally use:

$ dotnet run --project "source/Manga.WebApi/Manga.WebApi.csproj"

Running the Tests Locally

Continuous Integration

Continuous Delivery

Continuous Deployment

Docker

SQL Server

To spin up a SQL Server in a docker container using the connection string Server=localhost;User Id=sa;Password=<YourNewStrong!Passw0rd>; run the following command:

$ ./source/scripts/sql-docker-up.sh

Related Content and Projects

Contributors ✨

Thanks goes to these wonderful people (emoji key):

Ivan Paulovich
Ivan Paulovich

🎨 ⚠️ 💻
Petr Sedláček
Petr Sedláček

⚠️ 💻
Gus
Gus

🎨 ⚠️
arulconsultant
arulconsultant

⚠️
Guilherme Silva
Guilherme Silva

🎨 ⚠️ 💻
Ondřej Štorc
Ondřej Štorc

🎨 ⚠️
Marlon Miranda da Silva
Marlon Miranda da Silva

🎨 ⚠️
NicoCG
NicoCG

⚠️
Filipe Augusto Lima de Souza
Filipe Augusto Lima de Souza

🎨 ⚠️ 💻
sshaw-sml
sshaw-sml

⚠️ 💻

This project follows the all-contributors specification. Contributions of any kind welcome!

About

Clean Architecture sample with .NET Core. Use cases as central organising structure, decoupled from frameworks and technology details

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • C# 99.4%
  • Other 0.6%