My Upgrade Byndyusoft.Infrastructure | DDD + CQRS + WebApi

Hello! I often search the Internet for “ideal architecture” and a few months ago I came across an interesting implementation and would like to share it by adding a little.

Link to implementation

A bit of modernization and I got a completely universal working template.

For anyone new to DDD, you can start with the wiki .

In the end, we get a bunch with DDD + CQRS + Entity Framework + OData + WebApi + Log4Net + Castle Windsor + Kendo UI.

It sounds cumbersome, but purely personal, as a result we get a very easily scalable system.

The end result will be something like this
Clickable image (for full screen)

image

So here we go ...

Create a Domain and Infrastrcutre folder. In the Domain folder, create 3 projects (class library):

  1. Domain.Commands
  2. Domain.Database
  3. Domain.Model

In the Infrastrcuture folder, create 4 projects (class library):

  1. Infrastrcuture.Web
  2. Infrastrcuture.Domain
  3. Infrastrcuture.EntityFramework
  4. Infrastrcuture.Logging

And the web application itself (ASP MVC5), let's call it Web (with the MVC pattern). And the last project (class library) Web.Application .

And now for each in more detail:

CQRS (Command Query Responsibility Segregation)

A bit about Commands and Queries
Queries : Methods return the result without changing the state of the object. In other words, Query has no side effects.
Commands : Methods change the state of an object without returning a value. Actually, it is more correct to call these methods modifiers or mutators, but it so happened historically that they are called commands.

In the Domain.Commands project, we will store teams that will change the state of the object and our business logic.

This will be Command . And as Query we will have OData.

In the Command.Database project, we will store the database schema (I usually use PowerDesigner for this) and Seed scripts.

All entities are stored in the Domain.Model project.

Now the Infrastrcuture folder.
Infrastrcuture.Domain - we store all the domain helpers, command builders, exceptions that will be needed for the Domain model.
Infrastrcuture.EntityFramework is our ORM.
Infrastrcuture.Logging - logging.
Infrastrcuture.Web - web helpers, extensions, form handlers.

In the Web.Application project. We create a base class for reading (OData):

ReadODataControllerBase.cs
namespace Web.Application
{
    using System.Linq;
    using System.Web.Http.OData;
    using Infrastructure.Domain;
    using Infrastructure.EntityFramework;
    public class ReadODataControllerBase : ODataController
        where TEntity : class, IEntity
    {
        private readonly IRepository _repository;
        public ReadODataControllerBase(IRepository repository)
        {
            _repository = repository;
        }
        public IQueryable Get()
        {
            return _repository.Query();
        }
    }
}


And the basic form controller:

FormControllerBase.cs
namespace Web.Application
{
    using System;
    using System.Net;
    using System.Web.Mvc;
    using Castle.Core.Logging;
    using Castle.Windsor;
    using Infrastrcuture.Web.Forms;
    using Infrastructure.Domain.Exceptions;
    using Infrastructure.EntityFramework;
    using Services.Account;
    using Services.Account.Models;
    public class FormControllerBase : Controller, ICurrentUserAccessor
    {
        public JsonResult Form(TForm form)
            where TForm : IForm
        {
            var formHanlderFactory = ResolveFormHandlerFactory();
            var unitOfWork = ResolveUnitOfWork();
            var logger = ResolveLogger();
            try
            {
                logger.Info($"Begin request of <{CurrentUser.DisplayNameWithNk}> with form <{ form.GetType().Name }>.");
                formHanlderFactory.Create().Execute(form);
                unitOfWork.SaveChanges();
                logger.Info($"Complete request of <{CurrentUser.DisplayNameWithNk}> with form <{ form.GetType().Name }>.");
                return Json(new { form });
            }
            catch (BusinessException be)
            {
                return JsonError(form, be, logger);
            }
            catch (FormHandlerException fhe)
            {
                return JsonError(form, fhe, logger);
            }
            catch (Exception e)
            {
                return JsonError(form, e, logger);
            }
        }
        //Add exception logging
        public FileResult FileForm(TForm form)
            where TForm : IFileForm
        {
            var formHanlderFactory = ResolveFormHandlerFactory();
            formHanlderFactory.Create().Execute(form);
            return File(form.FileContent, System.Net.Mime.MediaTypeNames.Application.Octet, form.FileName);
        }
        private JsonResult JsonError(TForm form, Exception e, ILogger logger)
        {
            logger.Error($"Rollback request of <{CurrentUser.DisplayNameWithNk}> with form <{ form.GetType().Name }>.", e);
            Response.TrySkipIisCustomErrors = true;
            Response.StatusCode = (int)HttpStatusCode.InternalServerError;
            return Json(new
            {
                form,
                exceptionMessage = e.Message
            });
        }
        #region Dependency resolution
        private IFormHandlerFactory ResolveFormHandlerFactory()
        {
            return GetContainer().Resolve();
        }
        private IUnitOfWork ResolveUnitOfWork()
        {
            return GetContainer().Resolve();
        }
        private ILogger ResolveLogger()
        {
            return GetContainer().Resolve();
        }
        private IWindsorContainer GetContainer()
        {
            var containerAccessor = HttpContext.ApplicationInstance as IContainerAccessor;
            return containerAccessor.Container;
        }
        private ICurrentUserKeeper ResolveCurrentUserKeeper()
        {
            return GetContainer().Resolve();
        }
        #endregion
        #region CurrentUserAccessor Memebers
        public ApplicationUser CurrentUser
        {
            get
            {
                var currentUserKeeper = ResolveCurrentUserKeeper();
                return currentUserKeeper.GetCurrentUser();
            }
        }
        #endregion
    }
}


As a result, to read data from the database, we simply create a class and inherit it from the ReadODataController class and simply switch to localhost : 12345 / odata / Stations. The entire request instead of us writes OData:

Stationscontroller.cs

namespace Web.Application.Station
{
    using Domain.Model.Station;
    using Infrastructure.EntityFramework;
    public class StationsController : ReadODataControllerBase
    {
        public StationsController(IRepository repository) : base(repository)
        {
        }
    }
}


ODataConfig.cs

ODataConfig.cs

namespace Web
{
    using System.Linq;
    using System.Web.Http;
    using System.Web.Http.OData.Builder;
    using System.Web.Http.OData.Extensions;
    using Domain.Model.Station;
    using Infrastrcuture.Web.Extensions;
    using Microsoft.Data.Edm;
    public class ODataConfig
    {
        public static void Register(HttpConfiguration config)
        {
            var builder = new ODataConventionModelBuilder();
            config.Routes.MapODataServiceRoute("odata", "odata", GetEdmModel(builder));
        }
        public static IEdmModel GetEdmModel(ODataConventionModelBuilder builder)
        {
            var entityTypes = typeof (Station).Assembly.GetTypes().Where(x => x.IsClass && !x.IsNested);
            var method = builder.GetType().GetMethod("EntitySet");
            foreach (var entityType in entityTypes)
            {
                var genericMethod = method.MakeGenericMethod(entityType);
                genericMethod.Invoke(builder, new object[]
                {
                    entityType.Name.Pluralize()
                });
            }
            return builder.GetEdmModel();
        }
    }
}


This template has already been tested and now one of our projects in production is normal, it works without any problems.

Project Link: NTemplate

Also popular now: