Моя модернизация Byndyusoft.Infrastructure | DDD + CQRS + WebApi

от автора

Всем привет! Я часто ищу в просторах интернета «идеальную архитектуру» и несколько месяцев назад наткнулся на интересную реализацию и хотел бы поделится немного дополнив его.

Ссылка на реализацию

Немного модернизации и я получил вполне универсальный рабочий шаблон.

Для всех, кто не знаком с DDD можно начать с wiki.

В конце мы получим связку с DDD + CQRS + Entity Framework + OData + WebApi + Log4Net + Castle Windsor + Kendo UI.

Звучит громоздко, но сугубо лично, в результате получаем вполне легко масштабируемую систему.

Конечный результат примерно будет таким

Картинка кликабельная (для полного экрана)

image

Итак начнем…

Создаем папку Domain и Infrastrcutre. В папке Domain создаем 3 проекта (class library):

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

В папке Infrastrcuture создаем 4 проекта (class library):

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

И само веб-приложение (ASP MVC5), назовём его Web (с шаблоном MVC). И последний проект (class library) Web.Application.

А теперь по каждому по подробнее:

CQRS (Command Query Responsibility Segregation)

Немного о Commands and Queries

Queries: Методы возвращают результат, не изменяя состояние объекта. Другими словами у Query не никаких side effects.
Commands: Методы изменяют состояние объекта, не возвращая значение. На самом деле более корректно называть эти методы modifiers или mutators, но так исторически сложилось, что они называются командами.

В проекте Domain.Commands мы будем хранить команды которые будут менять состояние объекта и нашу бизнес-логику.

Это у нас и будет Command. А в качестве Query у нас будет служить OData.

В проекте Command.Database мы будем хранить схему базы данных (я обычно использую PowerDesigner для этого) и Seed-скрипты.

Все сущности храним в проекте Domain.Model.

Теперь папка Infrastrcuture.
Infrastrcuture.Domain — мы храним все доменные helpers, command builders, exceptions, которые нужны будут для Доменной модели.
Infrastrcuture.EntityFramework — это наш ORM.
Infrastrcuture.Logging — логгирование.
Infrastrcuture.Web — веб helpers, extensions, form handlers.

В проекте Web.Application. Создаем базовый класс для считывания (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();         }     } }

И базовый form контроллер:

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     } } 

В результате для считывания данных с базы мы просто создаем класс и наследуем его от класса ReadODataController и просто переходим на localhost:12345/odata/Stations. Весь запрос вместо нас пишет 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();         }     } }

Данный шаблон уже протестирован и сейчас один из наших проектов в продакшне нормально, без каких либо проблем работает.

Ссылка на проект: NTemplate
ссылка на оригинал статьи https://habrahabr.ru/post/327484/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *