Введение
Эта статья является первой частью серии статей, в которых описывается мой личный подход к реализации чистой архитектуры в 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