If you haven’t read the first part of this article, you can reach it from here for better understanding of the subject.

In this second part of this article, I will mention about the minimal implementation of the clean architecture concept with .NET Core.

As an example, we will develop a simple API which we can use to add and list movies.

Implementation of Application Domain

First, we will create the application domain part which will be the hearth of the architecture.

Before that, let’s recall the “Application Domain“. This layer is located in the middle of the architecture and is isolated from other frameworks, databases, UI, etc. It mainly contains domain entities, use-cases and external interfaces.

Well, first we need to create a class library called “Minimal.Core”.

dotnet new classlib -n Minimal.Core

After that, let’s create a folder called “Models” then define domain models as follows.

using System;

namespace Minimal.Core.Models
{
    public abstract class EntityBase
    {
        public Guid Id { get; set; }
        public DateTime CreatedAt { get; set; }
        public DateTime ModifiedAt { get; set; }
    }
}
namespace Minimal.Core.Models
{
    public class Movie : EntityBase
    {
        public string Name { get; set; }
        public double Rating { get; set; }
        public int AgeGroup { get; set; }
        public bool IsDeleted { get; set; }
    }
}

After creating the models, we need to create a folder called “Interfaces” where we will define ports. Then, let’s define an interface/port called “IRepository“.

With this port, we will be able to perform isolated database operations in the “Application Domain” layer without depending on any technologies.

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading.Tasks;

namespace Minimal.Core.Interfaces
{
    public interface IRepository<T> : IDisposable where T : class
    {
        Task CreateAsync(T entity);
        Task<IEnumerable<T>> GetWhereAsync(Expression<Func<T, bool>> predicate);
    }
}

After defining the port, we also need to define the data transfer objects to be used between adapters.

To do this, let’s create another folder called “Dtos“. Then, in this folder, we can define “BaseResponse” and “Movie” DTOs.

using System.Collections.Generic;
using System.Linq;

namespace Minimal.Core.Dtos
{
    public class BaseResponseDto<TData>
    {
        public BaseResponseDto()
        {
            Errors = new List<string>();
        }

        public bool HasError => Errors.Any();
        public List<string> Errors { get; set; }
        public int Total { get; set; }
        public TData Data { get; set; }
    }
}
namespace Minimal.Core.Dtos
{
    public class MovieDto
    {
        public string Name { get; set; }
        public double Rating { get; set; }
        public int AgeGroup { get; set; }
    }
}

We will create request models under another folder called “Requests“. Therefore, let’s create a folder called “Requests” under the “Dtos” folder.

Minimal.Core
-Models
-Interfaces
-Dtos
--Requests

Before creating request models, first we need to include the MediatR package in the project via NuGet.

dotnet add package MediatR

With this package, we will be able to perform communication between API and Application Domain in a loosely coupled way from single point. In addition, we will also benefit from MediatR package when implementing the use-case approach.

NOTE: If you want, you can also define and implement your own use-case interfaces without using any packages.

Now, in the “Requests” folder, we can define request models which we will use for movie operations. Let’s define the “CreateMovieRequest” and “GetBestMoviesForKidsRequest” models as follows.

using MediatR;

namespace Minimal.Core.Dtos.Requests
{
    public class CreateMovieRequest : IRequest<BaseResponseDto<bool>>
    {
        public string Name { get; set; }
        public double Rating { get; set; }
    }
}
using System.Collections.Generic;
using MediatR;

namespace Minimal.Core.Dtos.Requests
{
    public class GetBestMoviesForKidsRequest : IRequest<BaseResponseDto<List<MovieDto>>>
    {
        
    }
}

We have created the models together with its return types by using the “IRequest” marker interface of MediatR.

Until this point, we have defined “Domain” models, “DTOs” and external interfaces, I mean “Ports“.

So, we can start to implement business use-cases. First, let’s create “Services/MovieUseCases” folders under the “Minimal.Core” project.

Minimal.Core
-Models
-Interfaces
-Dtos
--Requests
-Services
--MovieUseCases

In this folder, we will implement all movie-related use-cases.

Now, under the “MovieUseCases” folder, let’s create a class called “CreateMovieHandler” and implement the movie creation operation as follows.

using System;
using System.Threading;
using System.Threading.Tasks;
using MediatR;
using Microsoft.Extensions.Logging;
using Minimal.Core.Dtos;
using Minimal.Core.Dtos.Requests;
using Minimal.Core.Events;
using Minimal.Core.Interfaces;
using Minimal.Core.Models;

namespace Minimal.Core.Services.MovieUseCases
{
    public class CreateMovieHandler : IRequestHandler<CreateMovieRequest, BaseResponseDto<bool>>
    {
        private readonly IRepository<Movie> _repository;
        private readonly ILogger<CreateMovieHandler> _logger;
        private readonly IMediator _mediator;

        public CreateMovieHandler(IRepository<Movie> repository, ILogger<CreateMovieHandler> logger, IMediator mediator)
        {
            _repository = repository;
            _logger = logger;
            _mediator = mediator;
        }

        public async Task<BaseResponseDto<bool>> Handle(CreateMovieRequest request, CancellationToken cancellationToken)
        {
            BaseResponseDto<bool> response = new BaseResponseDto<bool>();

            try
            {
                var movie = new Movie
                {
                    Name = request.Name,
                    Rating = request.Rating,
                    IsDeleted = false,
                    CreatedAt = DateTime.Now
                };

                await _repository.CreateAsync(movie);

                response.Data = true;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, ex.Message);
                response.Errors.Add("An error occurred while creating the movie.");
            }

            return response;
        }
    }
}

Let’s take a look at what we are doing.

First of all, by implementing the “IRequestHandler” interface, we have specified that the “CreateMovieHandler” class is a use-case request handler. This handler basically handles the “CreateMovieRequest” model and returns a response model type of “BaseResponseDto“.

We implemented the business use-case as dummy in the “Handle” method. The important point here is the usage style of the “IRepository” interface.

If we look at the above code block, we can see the “IRepository” interface is located in the “Minimal.Core.Interfaces” namespace and not in any other layer/adapter. I mean, we are using the external port that we defined in the “Minimal.Core” project.

This means the “CreateMovieHandler” use-case will not be affected by any technology changes.

Now let’s implement the use-case, which will return the best movies for children. To do that, let’s create another class called “GetBestMoviesForKidsHandler” and implement it as follows.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediatR;
using Microsoft.Extensions.Logging;
using Minimal.Core.Dtos;
using Minimal.Core.Dtos.Requests;
using Minimal.Core.Interfaces;
using Minimal.Core.Models;

namespace Minimal.Core.Services.MovieUseCases
{
    public class GetBestMoviesForKidsHandler : IRequestHandler<GetBestMoviesForKidsRequest, BaseResponseDto<List<MovieDto>>>
    {
        private readonly IRepository<Movie> _repository;
        private readonly ILogger<GetBestMoviesForKidsHandler> _logger;

        public GetBestMoviesForKidsHandler(IRepository<Movie> repository, ILogger<GetBestMoviesForKidsHandler> logger)
        {
            _repository = repository;
            _logger = logger;
        }

        public async Task<BaseResponseDto<List<MovieDto>>> Handle(GetBestMoviesForKidsRequest request, CancellationToken cancellationToken)
        {
            BaseResponseDto<List<MovieDto>> response = new BaseResponseDto<List<MovieDto>>();

            try
            {
                List<MovieDto> movies = (await _repository.GetWhereAsync(m => m.AgeGroup <= 16)).Select(m => new MovieDto
                {
                    Name = m.Name,
                    Rating = m.Rating,
                    AgeGroup = m.AgeGroup
                }).ToList();

                response.Data = movies;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, ex.Message);
                response.Errors.Add("An error occurred while getting movies.");
            }

            return response;
        }
    }
}

In this use-case, we have coded a logic which simply returns movies that are best for children. According to our example, the application domain layer is now ready.

Now let’s pass to the infrastructure section where we will perform database operations.

Implementation of Infrastructure

First, let’s create a class library called “Minimal.Infrastructure“.

dotnet new classlib -n Minimal.Infrastructure

After that, we need to add “Minimal.Core” project as reference.

Since we will use EntityFrameworkCore as ORM, we need to add also “Microsoft.EntityFrameworkCore” package to the project via NuGet.

If we remember in the “Minimal.Core” project, we had defined a port called “IRepository” to reverse the dependency flow. In the infrastructure, we will implement this port.

First we need a DbContext. To do that, let’s define a class called “AppDbContext” and code as follows.

using Microsoft.EntityFrameworkCore;
using Minimal.Core.Models;

namespace Minimal.Infrastructure
{
    public class AppDbContext : DbContext
    {
        public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
        { }

        public DbSet<Movie> Movies { get; set; }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);

            //...
        }
    }
}

Now we can implement the repository class.

To do this, we need to create a folder called “Repositories” and then implement the “Repository” class in this folder as follows.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Minimal.Core.Interfaces;
using Minimal.Core.Models;

namespace Minimal.Infrastructure.Repositories
{
    public class Repository<T> : IRepository<T> where T : EntityBase
    {
        private readonly AppDbContext _context;
        private DbSet<T> _dbSet;

        public Repository(AppDbContext context)
        {
            _context = context;
            _dbSet = context.Set<T>();
        }

        public async Task CreateAsync(T entity)
        {
            _dbSet.Add(entity);

            await _context.SaveChangesAsync();
        }

        public async Task<IEnumerable<T>> GetWhereAsync(Expression<Func<T, bool>> predicate)
        {
            return await _dbSet.Where(predicate).ToListAsync();
        }

        private bool _disposed = false;
        protected virtual void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    _context.Dispose();
                }
            }
            _disposed = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }
}

At the above code block, we have implemented a simple generic repository by using the db context which we created. Thus, we have completed the implementation of the “Infrastructure” adapter.

Lastly, we need a communication adapter/API.

Implementation of API

If we look from the architecture point of view, we can see the implementation operation of the API is not different from the implementation of other adapters. If we recall, we had said that we can think for hexagonal architecture as plug-in-based structure, which locates the core layer in the middle of the architecture, which we can plug an adapter, or out.

For implementation, first, let’s create an ASP.NET Core Empty Web project.

dotnet new web -n Minimal.API

Then, let’s add application domain layer and infrastructure adapter as reference.

dotnet add reference ../Minimal.Core/Minimal.Core.csproj
dotnet add reference ../Minimal.Infrastructure/Minimal.Infrastructure.csproj

We had benefited from the MediatR library while implementing business use-cases in the application domain. With this approach, we had mentioned that we can perform the communication between API and application domain in a loosely coupled way from single point.

To do this, we have to include “MediatR” and “MediatR.Extensions.Microsoft.DependencyInjection” packages in the API project via NuGet.

dotnet add package MediatR
dotnet add package MediatR.Extensions.Microsoft.DependencyInjection

Now we are ready for implementation.

Now let’s create an API controller class called “MoviesController” under the “Controllers” folder and then code as follows.

using System.Collections.Generic;
using System.Threading.Tasks;
using MediatR;
using Microsoft.AspNetCore.Mvc;
using Minimal.Core.Dtos;
using Minimal.Core.Dtos.Requests;

namespace Minimal.API.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class MoviesController : ControllerBase
    {
        private readonly IMediator _mediator;

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

        [HttpPost]
        public async Task<ActionResult<string>> CreateMovieAsync([FromBody]CreateMovieRequest createMovieRequest)
        {
            BaseResponseDto<bool> createResponse = await _mediator.Send(createMovieRequest);

            if (createResponse.Data)
            {
                return Created("...", null);
            }
            else
            {
                return BadRequest(createResponse.Errors);
            }
        }

        [HttpGet("kids")]
        public async Task<ActionResult<List<MovieDto>>> GetBestMoviesForKidsAsync()
        {
            BaseResponseDto<List<MovieDto>> getBestMoviesForKidsReponse = await _mediator.Send(new GetBestMoviesForKidsRequest());

            if (!getBestMoviesForKidsReponse.HasError)
            {
                return Ok(getBestMoviesForKidsReponse.Data);
            }
            else
            {
                return BadRequest(getBestMoviesForKidsReponse.Errors);
            }
        }
    }
}

In the above code block, we have implemented the “CreateMovieAsync” method, which we will use to create a movie, and the “GetBestMoviesForKidsAsync” method, which we will use to get best movies for children.

In the methods, we have set only the relevant request models, which we want it to handle, as parameters to the mediator as follows.

await _mediator.Send(createMovieRequest);
await _mediator.Send(new GetBestMoviesForKidsRequest());

Mediator will find the relevant request handlers for us and execute the request models. Performing these operations from single point is good approach, right?

Now let’s perform the injection operations in the “Startup” class.

using MediatR;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Minimal.Core.Interfaces;
using Minimal.Core.Services.MovieUseCases;
using Minimal.Infrastructure;
using Minimal.Infrastructure.Repositories;

namespace Minimal.API
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

            //Infrastructure
            services.AddDbContext<AppDbContext>(opt => opt.UseInMemoryDatabase("TestDB"));
            services.AddScoped(typeof(IRepository<>), typeof(Repository<>));

            //Services
            services.AddMediatR(typeof(CreateMovieHandler));
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseMvc();
        }
    }
}

In the “Startup” class, we specified that we will use an in-memory database for testing purpose. Then we performed the injection operations of the “IRepository” interface and MeaditR library.

Now, this adapter is also ready.

Thus, we can say that we completed the implementation operation of this example project with clean architecture.

Conclusion

Especially if we have got used to a dependency flow from top to bottom, it may seem a bit strange to define the ports (I mean, interfaces) in the application domain, and then implement their adapters around the architecture. After getting used to this architecture, we can see how this approach gives us speed and flexibility in the face of future changes/new features, testability and maintenance.

For example, let’s assume we decided to use a NoSQL technology as the database. All we need to do is create an adapter that implements the “IRepository” port, and then perform the necessary injection operations.

Because, by nature of the architecture we are building an inner/core layer with no dependencies on the outside world.

We equip the around of the core layer with replaceable adapters.

https://github.com/GokGokalp/CleanArchitectureBoilerplates/tree/master/src/MinimalCleanArchitecture