Архитектура проекта Order Management System
В этой статье мы разберём архитектуру системы управления заказами (Order Management System) -- веб-приложения на C# .NET 10, построенного на принципах Domain-Driven Design, SOLID и Clean Architecture.
Обзор системы
Order Management System -- это REST API для управления заказами, которое демонстрирует промышленный подход к построению корпоративных приложений. Система позволяет создавать заказы, добавлять позиции, подтверждать и отменять их, а также развёртываться в облаке Azure.
Проект организован в виде .NET Solution, содержащего несколько проектов -- каждый отвечает за свой архитектурный слой.
Структура решения
OrderManagement/
├── src/
│ ├── OrderManagement.Domain/ # Ядро -- бизнес-логика
│ │ ├── Entities/
│ │ ├── ValueObjects/
│ │ ├── Aggregates/
│ │ ├── Events/
│ │ ├── Enums/
│ │ ├── Exceptions/
│ │ └── Interfaces/
│ │
│ ├── OrderManagement.Application/ # Use Cases -- прикладная логика
│ │ ├── Commands/
│ │ ├── Queries/
│ │ ├── DTOs/
│ │ ├── Interfaces/
│ │ ├── Behaviors/
│ │ └── Mappings/
│ │
│ ├── OrderManagement.Infrastructure/ # Реализация -- БД, внешние сервисы
│ │ ├── Persistence/
│ │ ├── Repositories/
│ │ ├── Services/
│ │ └── Configuration/
│ │
│ └── OrderManagement.API/ # Точка входа -- Web API
│ ├── Controllers/
│ ├── Middleware/
│ ├── Filters/
│ └── Program.cs
│
├── tests/
│ ├── OrderManagement.Domain.Tests/
│ ├── OrderManagement.Application.Tests/
│ └── OrderManagement.Infrastructure.Tests/
│
└── .github/
└── workflows/
└── ci-cd.yml
Каждый проект (.csproj) компилируется в отдельную сборку (.dll). Такое разделение обеспечивает физическую изоляцию между слоями -- компилятор не позволит обратиться к классу из слоя, на который нет ссылки.
Правило зависимостей
Ключевой принцип архитектуры -- зависимости направлены внутрь, от внешних слоёв к внутренним:
API → Application → Domain ← Infrastructure
Это означает следующее:
- Domain не зависит ни от чего. Это чистый C# без внешних библиотек (кроме интерфейсов MediatR.Contracts для доменных событий).
- Application зависит только от Domain. Знает о сущностях, Value Objects и интерфейсах репозиториев.
- Infrastructure зависит от Application и Domain. Реализует интерфейсы, определённые во внутренних слоях.
- API зависит от Application (для отправки команд и запросов) и Infrastructure (только для регистрации зависимостей в DI-контейнере).
Обратите внимание на стрелку: Infrastructure зависит от Domain, а не наоборот. Domain определяет интерфейс IOrderRepository, а Infrastructure его реализует через Entity Framework Core. Это принцип инверсии зависимостей (DIP из SOLID).
Ответственность каждого слоя
Domain Layer -- ядро
Содержит бизнес-правила, сущности, объекты-значения, агрегаты и доменные события. Здесь живёт ответ на вопрос «что делает система с точки зрения бизнеса». Domain Layer не знает ни о базе данных, ни о HTTP, ни об Azure.
Application Layer -- координация
Содержит сценарии использования (Use Cases): команды для изменения данных и запросы для чтения. Использует паттерн CQRS с MediatR. Здесь же располагается валидация входных данных через FluentValidation и Pipeline Behaviors для кросс-функциональных задач (логирование, валидация).
Infrastructure Layer -- техническая реализация
Содержит всё, что связано с внешним миром: Entity Framework Core для работы с базой данных, реализации репозиториев, интеграции с Redis, Service Bus и другими сервисами Azure. Этот слой можно заменить без изменения бизнес-логики.
API Layer -- точка входа
Содержит ASP.NET Core контроллеры, middleware для обработки ошибок, конфигурацию DI-контейнера и документацию API через OpenAPI и Scalar. Контроллеры максимально тонкие -- они только принимают HTTP-запрос, создают Command или Query, отправляют через MediatR и возвращают результат.
Технологический стек
| Компонент | Технология |
|---|---|
| Язык | C# 14, .NET 10 |
| Web Framework | ASP.NET Core Controllers |
| ORM | Entity Framework Core 10 |
| CQRS | MediatR |
| Валидация | FluentValidation |
| API-документация | OpenAPI (встроенный) + Scalar |
| Логирование | Serilog + Application Insights |
| Тестирование | xUnit, FluentAssertions, Moq, Testcontainers |
| БД | Azure SQL Database |
| CI/CD | GitHub Actions |
Диаграмма компонентов
┌──────────────────────────────────────────────────┐
│ Клиенты │
│ (SPA, Mobile, Other) │
└──────────────────┬───────────────────────────────┘
│ HTTPS
┌──────────────────▼───────────────────────────────┐
│ API Layer (ASP.NET Core) │
│ Controllers / Middleware / Scalar │
└──────────────────┬───────────────────────────────┘
│ MediatR
┌──────────────────▼───────────────────────────────┐
│ Application Layer (Use Cases) │
│ Commands / Queries / Validators / DTOs │
└──────────────────┬───────────────────────────────┘
│ Interfaces
┌──────────────────▼───────────────────────────────┐
│ Domain Layer (Core) │
│ Entities / Value Objects / Aggregates / Events │
└──────────────────────────────────────────────────┘
▲ implements
┌──────────────────┴───────────────────────────────┐
│ Infrastructure Layer │
│ EF Core / Repositories / Azure Services │
└──────────────────────────────────────────────────┘
Запрос клиента проходит следующий путь: HTTP-запрос попадает в контроллер API Layer, контроллер формирует команду и отправляет её через MediatR, MediatR пропускает команду через Pipeline Behaviors (логирование, валидация) и передаёт в Handler Application Layer, Handler использует интерфейсы репозиториев из Domain Layer, а Infrastructure Layer предоставляет реализации этих интерфейсов.
Создание проекта
Для создания всей структуры используются команды .NET CLI:
// Создание Solution
// dotnet new sln -n OrderManagement
// Создание проектов по слоям
// dotnet new classlib -n OrderManagement.Domain
// dotnet new classlib -n OrderManagement.Application
// dotnet new classlib -n OrderManagement.Infrastructure
// dotnet new webapi -n OrderManagement.API
// Установка зависимостей между проектами
// Application -> Domain
// Infrastructure -> Domain + Application
// API -> Application + Infrastructure
Domain и Application создаются как Class Library (.dll), а API -- как Web API (с точкой входа Program.cs).
Преимущества выбранной архитектуры
Разделение на слои даёт конкретные преимущества:
- Тестируемость. Domain Layer тестируется без базы данных, HTTP-сервера и внешних зависимостей. Это самые быстрые и надёжные тесты.
- Заменяемость. Можно поменять базу данных с SQL Server на PostgreSQL, изменив только Infrastructure Layer.
- Независимость команды. Разные разработчики могут работать над разными слоями параллельно.
- Понятная структура. Новый разработчик сразу видит, где искать бизнес-логику, а где -- технические детали.