Всем привет! Я часто ищу в просторах интернета «идеальную архитектуру» и несколько месяцев назад наткнулся на интересную реализацию и хотел бы поделится немного дополнив его.
Немного модернизации и я получил вполне универсальный рабочий шаблон.
Для всех, кто не знаком с DDD можно начать с wiki.
В конце мы получим связку с DDD + CQRS + Entity Framework + OData + WebApi + Log4Net + Castle Windsor + Kendo UI.
Звучит громоздко, но сугубо лично, в результате получаем вполне легко масштабируемую систему.
Итак начнем…
Создаем папку Domain и Infrastrcutre. В папке Domain создаем 3 проекта (class library):
- Domain.Commands
- Domain.Database
- Domain.Model
В папке Infrastrcuture создаем 4 проекта (class library):
- Infrastrcuture.Web
- Infrastrcuture.Domain
- Infrastrcuture.EntityFramework
- Infrastrcuture.Logging
И само веб-приложение (ASP MVC5), назовём его Web (с шаблоном MVC). И последний проект (class library) Web.Application.
А теперь по каждому по подробнее:
CQRS (Command Query Responsibility Segregation)
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):
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 контроллер:
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:
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
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/

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