NuGet пакет с типовыми решениями для ускорения разработки.
Содержит в себе базовые классы, которые необходимы при разработке по DDD + CQRS + Event Sourcing (immediate consistency). Библиотека совместима со спецификациями из open source библиотеки Force (https://github.com/hightechgroup/force). Имеет прямые зависимости к Entity Framework.
Реализованы по принципу "is a", а не "can do".
Использование:
publicclassAnimal:EntitypublicclassAddress:ValueObjectРеализованы с помощью библиотеки MediatR.
publicclassAddProductCommand:CommandpublicclassProductAddedEvent:EventStartup.cs
services.AddMediatR(typeof(Startup));// Хранилище Eventsservices.AddScoped<IEventStore,EventStore>();// Шина обработки событийservices.AddScoped<IMediatorHandler,InMemoryBus>();// Domain - Eventsservices.AddScoped<INotificationHandler<ProductAddedEvent>,ProductAddedEventHandler>();// Domain - Commandsservices.AddScoped<IRequestHandler<AddProductCommand,bool>,ProductCommandHandler>();ProductCommandHandler.cs
publicclassProductCommandHandler:CommandHandlerBase,IRequestHandler<ProductAddCommand,bool>...
public async Task<bool> Handle(ProductAddCommandcommand,CancellationTokenct){boolisValidOperation;// Валидацияif(!isValidOperation){awaitMediator.RaiseEventAsync(newDomainNotification(nameof(DomainNotification),Resources.NotValidOperation),ct);returnfalse;}// Бизнес логикаif(!awaitCommitAsync())return false;await_mediator.RaiseEventAsync(newProductAddedEvent(product.Id,product.Price,// Другие поля),ct);returntrue;ProductsController.cs
varcommand=_mapper.Map<ProductAddCommand>(model);awaitMediator.SendCommandAsync(command);if(!IsValidOperation())returnBadRequest(Errors);returnOk();Глобальная шина для функционирования MediatR. Настроена на сохранение всех наследников Event в EventStore, кроме DomainNotification, который предназначен для хранения ошибок во время выполнения бизнес логики.
InMemoryBus.cs
publicvirtualasyncTaskRaiseEventAsync<T>(T@event,CancellationTokenct=default)whereT:Event{if(!@event.MessageType.Equals("DomainNotification"))// Записываем наследников Event и с типом не DomainNotification.awaitEventStore.SaveAsync(@event,ct);// Паблишим ивенты. Они будут доступны при реализации INotificationHandler<T>, где T = EventawaitMediator.Publish(@event,ct);}Startup.cs
services.AddScoped<IMediatorHandler,InMemoryBus>();ProductsController.cs
publicclassProductsController:FormattedApiControllerBase{privatereadonlyIMediatorHandler_mediator;publicProductsController(IMediatorHandlermediator){_mediator=mediator;}
...[HttpGet]publicIActionResultPost(AddProductmodel){
...
await _mediator.SendCommandAsync(command);Обработчик, с помощью которого построена архитектура обработки ошибок через INotification библиотеки MediatR.
Startup.cs
services.AddScoped<INotificationHandler<DomainNotification>,DomainNotificationHandler>();ProductCommandHandler.cs
publicclassProductCommandHandler:CommandHandlerBase,IRequestHandler<ProductAddCommand,bool>...
public async Task<bool> Handle(ProductAddCommandcommand,CancellationTokenct){boolisValidOperation;// Валидацияif(!isValidOperation){awaitMediator.RaiseEventAsync(newDomainNotification(nameof(DomainNotification),Resources.InvalidOperation),ct);// Ошибка типа DomainNotification попала в InMemoryBus и сохранилась в памяти. // Теперь она доступна через DomainNotificationHandler для дальнейшей обработки.returnfalse;}ApiControllerBase.cs
publicabstractclassApiControllerBase:FormattedApiControllerBase{privatereadonlyDomainNotificationHandler_notifications;protectedreadonlyIMediatorHandlerMediator;protectedApiControllerBase(IMediatorHandlermediator,INotificationHandler<DomainNotification>notifications){Mediator=mediator;_notifications=(DomainNotificationHandler)notifications;}protectedIEnumerable<DomainNotification>Notifications=>_notifications.Notifications();protectedIEnumerable<string>Errors=>Notifications.Select(x =>x.Value);protectedboolIsValidOperation()=>!_notifications.HasNotifications();}Репозиторий для обработки наследников Event, для последующиего сохранения в базу. Содержит в себе аггрегат, тип, время, и тело события в сериализованном виде. Служит для хранения историчности аггрегата. Используется в InMemoryBus как await EventStore.SaveAsync(@event, ct).
Startup.cs
services.AddScoped<IEventStore,EventStore>();InMemoryBus.cs
publicclassInMemoryBus:IMediatorHandler{protectedreadonlyIMediatorMediator;protectedreadonlyIEventStoreEventStore;
...publicvirtualasyncTaskRaiseEventAsync<T>(T@event,CancellationTokenct=default)whereT:Event{if(!@event.MessageType.Equals("DomainNotification"))awaitEventStore.SaveAsync(@event,ct);awaitMediator.Publish(@event,ct);}Базовый класс обработчика команд, который содержит в себе базовые зависимости, необходимые для обработки доменных ошибок и взаимодействия с базой данных. Реализованные методы Commit() и CommitAsync() не позволят записать в базу, если найдутся доменные ошибки. Также умеют выбрасывать свои ошибки при наличии исключений во время записи в базу, которые можно в будущем аггрегировать и завернуть в тело Bad Request итд. Содержит зависимости IMediatorHandler, DomainNotificationHandler, IUnitOfWork.
Класс, реализующий паттерн Unit Of Work, предлагает единую точку коммита транзакции в базу данных. Реализованы виртуальные методы Commit() и CommitAsync() с логикой орбаботки интерфейсов IHasCreationTime и IHasModificationTime. При необходимости можно добавить свою реализацию, наследовавшись от класса и перезаписать методы Commit(), CommitAsync().
Набор инструментов, ускоряющих разработку Application слоя приложения.
Набор атрибутов для работы с файлами в REST Api.
- AllowedExtensionsAttribute
- MaxFileSize
- HasValidFileName
Пример.
[HasValidFileName][MaxFileSize(500*1024*1024)]// 500 mb[AllowedExtensions(new[]{".jpg",".png",".mp4",".jpeg"})]publicIFormFileFile{get;set;}- DevelopmentOnly
Атрибут, который позволяет выключать метод в не Development окружении. Пример.
[HttpDelete][DevelopmentOnly][ApiExplorerSettings(IgnoreApi=true)]publicasyncTask<IActionResult>Delete([FromQuery]DeleteRequestrequest)Инструмент, необходимый для форматирования ответа приложения и приведения ответа в единую стилистику.
Положительный формат ответа:
{
"content": {
"key": "value"
},
"debugData": "string"
}Отрицательный формат ответа:
{
"subStatus": "string",
"errorContent": [
"string"
],
"debugData": "string"
}Необходимо зарегистрировать следующим образом.
Startup.cs
services.AddControllers().SetCompatibilityVersion(CompatibilityVersion.Latest).AddResponseOutputFormatter();services.Configure<ApiBehaviorOptions>(options =>{options.InvalidModelStateResponseFactory=
actionContext =>InvalidModelStateResponseFactory.CreateFrom(Resources.InvalidModel,actionContext.ModelState);});ApiControllerBase.cs
publicabstractclassApiControllerBase:FormattedApiControllerBase{Использование.
/// <summary>/// Вывод списка продуктов./// </summary>[HttpGet][SwaggerResponse(200,SwaggerResponseMessages.Ok+" Возвращается список продуктов.",typeof(SwaggerSuccessApiResponse<PagedResponse<ProductItemModel>>))]publicIActionResultGet([FromQuery]PagingQueryModelquery){varproducts=_context.Set<Domain.Entities.Products>().OrderByDescending(x =>x.Rating).ProjectTo<ProductItemModel>(_mapper.ConfigurationProvider).Paginate(query.Page,query.Size);returnOk(products);}Ответ.
{
"pagination": {
"itemsTotal": 0,
"page": 0,
"total": 0,
"size": 0,
"hasPrevious": true,
"hasNext": true
},
"items": [
{
"id": 0,
"name": "string",
"price": 0,
"rating": 0
}
]
}Упрощение разработки swagger документации. Автоматически дополняет ответ ошибкой 500, а также 401 и 403 если метод покрыт авторизацией. Для упрощения документирования различных ответов присутствует набор шаблонных сообщений SwaggerResponseMessages. Как референс для текстов ошибок использовалась официальная документация Mozilla. SwaggerErrorApiResponse и SwaggerSuccessApiResponse добавлены для построения тел ответов на swagger странице при использовании в связке с Response Formatter.
Регистрация.
services.AddSwaggerGen(options =>{// Ваш конфигoptions.OperationFilter<ResponseOperationFilter>();});Использование. 500, 403, 401 ошибки добавились автоматически.
/// <summary>/// Удаление продукта./// </summary>[HttpDelete("{id}")][Authorize][SwaggerResponse(204,SwaggerResponseMessages.NoContent,typeof(void))][SwaggerResponse(400,SwaggerResponseMessages.BadRequest,typeof(SwaggerErrorApiResponse<IEnumerable<string>>))]publicasyncTask<IActionResult>Delete(longid)Методы для инициализации данных в БД.
- MigrationsInitializer - используется для автомиграции при старте.
- DataInitializerBase - базовый метод для инициализации данных в базу. Имеет фабричный метод InitializeAsync, который необходимо реализовать.
Использование.
Program.cs
publicstaticasyncTaskMain(string[]args){varhost=CreateHostBuilder(args).Build();awaithost.InitAsync();awaithost.RunAsync();}Startup.cs
services.AddAsyncInitializer<MigrationsInitializer>();services.AddAsyncInitializer<AdminsInitializer>();В начале советуется запускать MigrationsInitializer, так как сначала должна инициализироваться актуальная схема, а потом уже все остальное.