Реализация чистой архитектуры в ASP.NET Core 6

Введение

Эта статья является первой частью серии статей, в которых описывается мой личный подход к реализации чистой архитектуры в ASP.NET Core 6, и, надеюсь, даст вам несколько идей о том, как построить свою собственную чистую архитектуру.

Эта часть — просто обзор общей архитектуры. Более подробная информация о внутреннем устройстве и реализации будет представлена в отдельных постах.

Кроме того, в этом посте не будет подробно описана концепция чистой архитектуры, а также введение в Domain-Driven Design или лучшие практики. Если вам нужно узнать больше о концепциях чистой архитектуры в деталях, вы можете перейти по одной из ссылок внизу этого поста.

Цель этого проекта — стать отправной точкой для создания решений ASP.NET Core. Он слабо связан, в значительной степени опирается на принцип инверсии зависимостей и продвигает Domain-Driven Design для организации вашего ядра (хотя это не является принудительным). Цель, которую я поставил перед собой, заключалась в том, чтобы построить «голую», поддерживаемую и расширяемую архитектуру.

Абстракция — ключ

Моей вторичной целью было абстрагироваться от таких понятий, как события домена, CQRS и сорсинг событий, от зависимости от конкретных реализаций — или даже от других абстракций, привнесенных некоторыми известными пакетами — такими как MediatR, MassTransit, EventStore и т.д.

Еще одна конкретная вещь, которую я хотел сделать с помощью этого, это возможность комбинировать и легко переключаться между реляционными (или нереляционными, если на то пошло) методами персистентности и потоковыми методами или методами источников событий, не затрагивая при этом никакой базовой архитектуры. Я видел довольно много реализаций, в которых решение использовать поиск событий для персистентности глубоко укоренилось (или, по крайней мере, в значительной степени очевидно) в том, как организовано ядро. Я хотел избежать этого и абстрагироваться.

Хотя это не полноценная реализация хранилища событий (реализация по умолчанию использует Entity Framework Core с SQL Server для сохранения событий, а не что-то вроде EventStore, что было бы более уместно с технической точки зрения), она служит цели вышеупомянутой абстракции. Специфика фактической реализации поиска событий с использованием Entity Framework Core, а также включение моментальных снимков и ретроактивных событий будет подробно описана в следующей статье.

Отказ от ответственности

Я не утверждаю, что эта конструкция в любом случае лучше или чище других. Это просто то, что очень хорошо работало для меня в нескольких моих проектах. Большинство разработчиков программного обеспечения признают, что не существует единственной архитектуры, которая была бы лучшей. Лучшая архитектура — это та, которая хорошо работает для вас, вашей команды и вашего типа проекта. Это ни в коем случае не готовое к производству решение, это не фреймворк, это всего лишь отправная точка.

Кроме того, это решение отнюдь не является полным с точки зрения возможностей. Есть несколько функций, которые могут быть полезны в зависимости от типа проекта, который вы создаете (кэширование, аналитика и т.д.), которые не включены на момент написания этой статьи, но могут быть легко добавлены при необходимости. Я стараюсь поддерживать его и добавлять новые возможности, насколько позволяет время, поэтому если у вас есть какие-либо предложения, не стесняйтесь спрашивать или вносить свой вклад.

История

Когда я решил создать этот «голый» дизайн, я, конечно, воспользовался советами и рекомендациями некоторых очень хорошо спроектированных архитектур с открытым исходным кодом на GitHub:

https://github.com/ardalis/CleanArchitecture

https://github.com/dotnet-architecture/eShopOnContainers

https://github.com/jasontaylordev/CleanArchitecture

https://github.com/blazorhero/CleanArchitecture

Есть отличная статья от дяди Боба (Robert C. Martin), который является большим сторонником чистого кода и практики чистой архитектуры: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html. Его книга Clean Architecture также является большим источником ценной информации об универсальных правилах архитектуры программного обеспечения, так что я определенно рекомендую прочитать ее, если вы заинтересованы в создании поддерживаемых, слабосвязанных и долговечных решений.

В этом видео также есть отличный доклад Джейсона Тейлора, сделанный на NDC Sydney в 2019 году, в котором он рассказывает о Clean Architecture в ASP.NET Core 3.0.

Используемые технологии

  • ASP.NET Core 6
  • Entity Framework Core 6
  • MassTransit
  • AutoMapper
  • Razor Components
  • ASP.NET Core MVC
  • GuardClauses
  • xUnit
  • Moq
  • Fluent Assertions
  • FakeItEasy
  • Docker

Особенности

Особенности данного конкретного решения кратко описаны ниже, без особого порядка:

  • Локализация для поддержки нескольких языков
  • Поиск событий с использованием Entity Framework Core и SQL Server в качестве постоянного хранилища, включая моментальные снимки и ретроактивные события.
  • Репозиторий EventStore и общий репозиторий DataEntity. Постоянство может меняться между ними, тонкая настройка для отдельных сущностей
  • Постоянные конфигурации приложений с опциональным шифрованием
  • Встроенный аудит операций с данными (для сущностей, не использующих EventStore)
  • Управление локальными пользователями с помощью ASP.NET Core Identity
  • Чистое разделение сущностей данных и объектов домена и отображение между ними для сохранения/поиска с помощью AutoMapper
  • ASP.NET Core MVC с Razor Components, используемый для презентации
  • CQRS с использованием абстракций обработчиков для поддержки MassTransit или MediatR с очень небольшими изменениями
  • Абстракции сервисной шины для поддержки решений брокеров сообщений, таких как MassTransit или MediatR (реализация по умолчанию использует медиатор MassTransit)
  • Усиленное продвижение Domain-Driven Design с помощью агрегатов, сущностей и абстракций событий домена.
  • Легкая структура авторизации с использованием ASP.NET Core AuthorizationHandler
  • Поддержка контейнеризации Docker для SQL Server и веб-приложения

Некоторые другие полезности:

  • Реализация генератора паролей на основе конфигурации паролей ASP.NET Core Identity
  • Библиотека классов Razor, содержащая готовые компоненты Blazor для часто используемых функций, таких как кнопки CRUD, функциональность тостов, модальные компоненты, Blazor Select2, интеграция DataTables и загрузчик страниц.
  • Общая библиотека с различными расширениями типов, объектами-обертками результатов, структурами данных страничных результатов, конвертером формата даты и т.д.

Обзор архитектуры

Ниже приведен рисунок, изображающий общую архитектуру в терминах слоев и их компонентов. Этот рисунок предназначен для представления того, как реальное решение является многоуровневым и показывает постепенную взаимосвязь между слоями, а не просто показывает логическую структуру чистой архитектуры.

В любом случае, приведенная ниже диаграмма, взятая из доклада Джейсона Тейлора на NDC Sydney (2019), в общих чертах отображает логическую структуру архитектуры:

Фактическое решение состоит из нескольких проектов, для удобства разделенных по папкам, представляющим слои. На рисунке ниже представлена карта кода, сгенерированная на основе решения. На ней показаны различные слои и проекты, а также их взаимозависимости.

Как видно из стрелок, все зависимости направлены вниз (или внутрь, если это круговая диаграмма). Между слоями Core, Infrastructure и Presentation нет стрелок, направленных вверх.

Уровень ядра

Уровень ядра состоит из двух частей: внутреннего ядра и внешнего ядра. Внутреннее ядро — это домен, а внешнее ядро — это приложение. Он состоит из двух проектов в решении в папке Core, проектов Application и Domain.

Внутреннее ядро (уровень домена)

Этот слой содержит независимую от приложения бизнес-логику. Это та часть бизнес-логики, которая была бы той же самой, даже если бы мы не создавали программное обеспечение. Это формулировка основных бизнес-правил.

Организация этого проекта следует паттернам проектирования, ориентированным на домены, хотя это вопрос предпочтений и может быть решена любым способом, который вы считаете нужным. Сюда входят:

  • Агрегаты, сущности, объекты значений, пользовательские исключения домена и интерфейсы для служб домена.
  • Интерфейсы для концепций проектирования, ориентированных на домен (например, IAggregateRoot, IDomainEvent, IEntity).
  • Базовые реализации агрегатного корня и доменного события. Также содержит специфические события домена, относящиеся к бизнес-процессам.

Внешнее ядро (прикладной уровень)

Этот слой содержит специфическую для приложения бизнес-логику. Он содержит то, «что» должна делать система. Сюда входят:

  • Интерфейсы для компонентов инфраструктуры, таких как репозитории, единицы работы и источники событий.
  • Модели и обработчики команд и запросов
  • Интерфейсы и DTO для сквозных задач (например, сервисная шина).
  • Операции авторизации, требования и реализация обработчиков
  • Интерфейсы и конкретные реализации специфических для приложения сервисов бизнес-логики.
  • Профили отображения между сущностями домена и моделями CQRS

Инфраструктурный уровень

Этот слой содержит детали, конкретные реализации для репозиториев, единицы работы, хранилища событий, реализации сервисной шины и т.д. Здесь содержится информация о том, «как» система должна делать то, что должна делать. Разделение между прикладным и инфраструктурным уровнем позволяет структурам решений, подобным этому, изменять и/или добавлять конкретные реализации по мере изменения требований проекта.

В общих чертах, этот слой содержит:

  • Общие и специфические реализации репозиториев
  • EF DbContexts, модели данных и миграции
  • Реализации персистентности и сервисов с использованием источников событий
  • Реализации для сквозных проблем (т.е. служба конфигурации приложений, служба локализации и т.д.)
  • Реализация аудита сущностей данных

Это состоит из 3 проектов в решении в папке Infrastructure, проектов Auditing, Data и Shared.

Проект Auditing состоит из различных методов расширения для DbContext, в основном связанных с SaveChanges. Он отвечает за генерацию записей аудита для каждого отслеживаемого изменения сущности.

Проект Data содержит доменные и общие (CRUD и event-sourcing) реализации хранилищ, DbContexts, миграции EF Core, конфигурации типов сущностей (если таковые имеются), реализацию хранилища событий (включая снимки), отображения сущностей данных на доменные объекты и сервисы, связанные с персистентностью (например, сервис инициализации базы данных).

Проект Resources содержит локализованные общие ресурсы и ключи ресурсов, а также реализацию служб локализации.

Проект Shared содержит реализацию сервисов для таких сквозных задач, как управление пользователями и аутентификация, хранение файлов, сервисная шина, локализация, конфигурация приложений и генератор паролей.

Презентационный уровень

Этот слой, по сути, содержит компоненты ввода-вывода системы, графический интерфейс пользователя, REST API, мобильные приложения или консольные оболочки, а также все, что непосредственно связано с ними. Это начальная точка нашего приложения.

Для данного решения начальной точки оно содержит следующее:

  • Веб-приложение ASP.NET Core MVC с использованием Razor Components
  • Общая библиотека классов, содержащая общие компоненты Razor Components, такие как уведомления о тостах, модальные компоненты, Blazor Select2, интеграция DataTablesJS и кнопки CRUD.

Общий слой

Эта единая библиотека классов содержит общие типы, используемые во всех остальных слоях. Как правило, в эту библиотеку включаются те вещи, которые вы собираете в пакет Nuget и используете в различных решениях. Для пояснения, это не является стратегическим паттерном Shared Kernel в DDD. Некоторые из них включают:

  • Общая обертка результатов
  • страничные модели результатов для операций запроса
  • Расширения типов CLR
  • Модели уведомлений
  • Пользовательские атрибуты и конвертеры

Да, да, дайте мне код

Репозиторий с исходным кодом этого решения можно найти здесь. В качестве примера решение включает чрезвычайно базовую доменную модель для приложения интернет-магазина. Однако часть пользовательского интерфейса для этого не включена. Домен заказа существует для того, чтобы просто продемонстрировать различные элементы архитектуры.

Заключительные мысли

Как и любое программное решение, эта архитектура не является универсальной. Это просто то, что очень хорошо сработало в моих проектах. Как уже упоминалось во введении к этому посту, в это решение еще многое нужно включить, поэтому я буду обновлять репозиторий на GitHub, а также этот пост, когда будет добавляться что-то новое.

Пожалуйста, не стесняйтесь оставлять отзывы или предложения по этому поводу, а также если вы хотите, чтобы в проект было включено что-то конкретное.

Будущая работа

В ближайшем будущем я планирую внести следующие дополнения/изменения в решение:

  • Добавить поддержку кэширования, с реализацией для Redis
  • Заменить MVC для презентации на Blazor Server
  • Включить RESTful API для бизнес-логики домена
  • Дополнительная реализация событийного сорсинга с использованием EventStoreDB

Ресурсы

Вот несколько дополнительных статей, которые могут оказаться полезными для понимания сути чистой архитектуры:

https://www.freecodecamp.org/news/a-quick-introduction-to-clean-architecture-990c014448d2/

https://www.oncehub.com/blog/explaining-clean-architecture

Оставьте комментарий

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