📜 ⬆️ ⬇️

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 I would like to share a bit with it.

Reference to implementation

It is a little modernization and I received quite universal working template.
')
For anyone not familiar with DDD, you can start with the wiki .

At the end we will get a bundle with DDD + CQRS + Entity Framework + OData + WebApi + Log4Net + Castle Windsor + Kendo UI.

It sounds cumbersome, but purely personally, as a result we get a quite easily scalable system.

The end result will be approximately
Picture clickable (for full screen)

image

So, let's begin…

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

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

In the Infrastrcuture folder we 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 template). And the last project (class library) Web.Application .

And now for each of the more:

CQRS (Command Query Responsibility Segregation)

A bit about Commands and Queries
Queries : Methods return a 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. In fact, it is more correct to call these methods modifiers or mutators, but historically, they are called teams.

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

This will be our Command . And as Query, we will use 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 project Domain.Model.

Now the folder Infrastrcuture.
Infrastrcuture.Domain - we store all 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. 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<TEntity> : ODataController where TEntity : class, IEntity { private readonly IRepository<TEntity> _repository; public ReadODataControllerBase(IRepository<TEntity> repository) { _repository = repository; } public IQueryable<TEntity> Get() { return _repository.Query(); } } } 


And the base 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>(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<TForm>().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>(TForm form) where TForm : IFileForm { var formHanlderFactory = ResolveFormHandlerFactory(); formHanlderFactory.Create<TForm>().Execute(form); return File(form.FileContent, System.Net.Mime.MediaTypeNames.Application.Octet, form.FileName); } private JsonResult JsonError<TForm>(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<IFormHandlerFactory>(); } private IUnitOfWork ResolveUnitOfWork() { return GetContainer().Resolve<IUnitOfWork>(); } private ILogger ResolveLogger() { return GetContainer().Resolve<ILogger>(); } private IWindsorContainer GetContainer() { var containerAccessor = HttpContext.ApplicationInstance as IContainerAccessor; return containerAccessor.Container; } private ICurrentUserKeeper ResolveCurrentUserKeeper() { return GetContainer().Resolve<ICurrentUserKeeper>(); } #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 go to localhost : 12345 / odata / Stations. The whole query writes us OData:

StationsController.cs
 namespace Web.Application.Station { using Domain.Model.Station; using Infrastructure.EntityFramework; public class StationsController : ReadODataControllerBase<Station> { public StationsController(IRepository<Station> 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 working fine, without any problems.

Link to project: NTemplate

Source: https://habr.com/ru/post/327484/


All Articles