Skip to content

Огляд архітектури

Мета: Ознайомитися з принципами архітектури "Порти та адаптери" (Hexagonal Architecture). Навчитися розділяти бізнес-логіку та взаємодію із зовнішніми системами через інтерфейси.


Завдання

  1. Налаштування проєкту
  2. Створіть новий солюшн у C#, що складається з таких проєктів:
    • ProjectName.Domain – доменна логіка.
    • ProjectName.Application – сервіси або command/query.
    • ProjectName.Infrastructure – реалізації репозиторіїв та зовнішніх сервісів.
    • ProjectName.Presentation – консольний застосунок або ASP.NET API.
  3. Додайте необхідні бібліотеки для роботи з DI (наприклад, Microsoft.Extensions.DependencyInjection).
  4. Створення доменної логіки (ProjectName.Domain)
  5. Створіть сутність Product, яка містить:
    • Id (int)
    • Name (string)
    • Price (decimal)
    • Phot * (string)
  6. Створіть інтерфейси для бізнес-логіки:

    • IProductRepository для взаємодії з базою даних.
    • IProductProxy для отримання даних із зовнішніх API (оберіть тестовий сайт, де є дані про продукти, або використовуйте json-server).
  7. Реалізація сервісів (ProjectName.Application)

  8. Створіть IProductService, що дозволяє:
    • Додавати новий продукт.
    • Отримувати список усіх продуктів.
    • Видаляти продукт за Id.
    • Шукати продукт за name.
  9. Реалізуйте ProductService, який працює з IProductRepository та IExternalProductService.

Варіант 2?: Використання Mediatr та CQRS (Команди та Запити)

У цьому підході використовується MediatR, що дозволяє розділити бізнес-логіку на окремі запити (Queries) і команди (Commands).

  • Команди (Commands):
    • CreateProductCommand – створює новий продукт.
    • DeleteProductCommand – видаляє продукт за Id.
  • Запити (Queries):
    • GetAllProductsQuery – отримує список усіх продуктів.
    • SearchProductsByNameQuery – отримує список усіх продуктів по назві.
  • Обробники (Handlers):

    • CreateProductCommandHandler
    • DeleteProductCommandHandler
    • GetAllProductsQueryHandler
    • SearchProductsByNameHandler
  • Реалізація адаптерів (ProjectName.Infrastructure)

  • Реалізуйте репозиторії:
    • InMemoryProductRepository (зберігання в пам’яті).
    • DatabaseProductRepository (SQLite або MS SQL).
  • Реалізуйте <Service>ProductProxy для роботи з зовнішнім API.

  • Інтеграція та запуск (ProjectName.Presentation)

  • Реалізуйте консольний застосунок або API.

  • Налаштуйте DI-контейнер для реєстрації залежностей.

Додаткові посилання


Хід роботи

Проєкт продемонструє реалізацію Гексагональної Архітектури (Ports and Adapters) на платформі .NET 8. Основна мета — показати чітке розділення бізнес-логіки від зовнішніх залежностей (бази даних, API, тощо).

Структура Проєкту

Рішення складається з 4-х основних проєктів:

  1. HexagonalDemo.Domain (Ядро)

    • Містить чисту бізнес-логіку: Сутності (Entities), Об'єкти-значення (Value Objects), Доменні події та Винятки.
    • Не має залежностей від інших проєктів чи зовнішніх бібліотек (окрім базових .NET).
  2. HexagonalDemo.Application (Шар застосунку)

    • Містить сценарії використання (Use Cases), реалізовані через CQRS (Command/Query Responsibility Segregation).
    • Визначає Порти (Інтерфейси) для роботи з інфраструктурою (наприклад, IOrderRepository).
    • Залежить лише від Domain.
    • Використовує бібліотеку MediatR для обробки команд.
  3. HexagonalDemo.Infrastructure (Інфраструктура / Адаптери)

    • Реалізує інтерфейси, визначені в Application (Адаптери).
    • Містить конкретну реалізацію доступу до даних (EF Core DbContext), клієнти зовнішніх сервісів тощо.
    • Залежить від Domain та Application.
  4. HexagonalDemo.Presentation (Презентація)

    • Точка входу в застосунок (ASP.NET Core Web API).
    • Відповідає за взаємодію з клієнтом (HTTP запити).
    • Налаштовує Dependency Injection (DI Container).

Чому 4 проєкти, а не один?

Ви можете запитати: "Навіщо так ускладнювати і створювати цілих 4 проєкти для простого застосунку?". Відповідь криється у розділенні відповідальності (Separation of Concerns) та захисті бізнес-логіки.

  1. Ізоляція Домену: Проєкт Domain фізично не може звернутися до бази даних або контролерів, бо він не має на них посилань. Це гарантує, що бізнес-правила залишаються чистими і незалежними від інфраструктури.
  2. Замінність: Якщо ви захочете змінити Web API на консольну утиліту або змінити SQL Server на текстові файли, вам доведеться змінити лише Presentation або Infrastructure. Бізнес-логіка (Domain та Application) залишиться незмінною.
  3. Тестування: Легше писати модульні тести (Unit Tests) для чистої логіки, не турбуючись про підключення до бази даних.

Принципи "Порти та Адаптери"

Архітектура дозволяє застосунку бути керованим користувачами, програмами, автоматизованими тестами або скриптами, і розроблятися та тестуватися ізольовано від пристроїв виконання та баз даних.

  • Порт (Port): Інтерфейс, який визначає вхід або вихід. Наприклад, IRepository — це вихідний порт.
  • Адаптер (Adapter): Конкретна реалізація порту. Наприклад, SqlRepository — це адаптер для IRepository.

Цей підхід дозволяє легко змінювати інфраструктуру (наприклад, замінити SQL Server на MongoDB) без змін у бізнес-логіці.

Реалізація CQRS з MediatR

У проєкті використано патерн CQRS (Command Query Responsibility Segregation) — розділення відповідальності команд і запитів.

Що таке MediatR?

MediatR — це бібліотека, яка реалізує патерн "Посередник" (Mediator). Вона дозволяє зменшити зв'язність між компонентами програми. Замість того, щоб контролер напряму викликав сервіс, він надсилає "повідомлення" (Команду або Запит) через Медіатор. А Медіатор вже знає, який клас (Handler) має обробити це повідомлення.

Структура команд і запитів

Логіка розділена на два типи операцій:

  1. Команди (Commands): Змінюють стан системи (Create, Update, Delete).

    • Приклад: CreateProductCommand — містить дані для створення.
    • Обробник: CreateProductCommandHandler — виконує логіку створення.
  2. Запити (Queries): Тільки читають дані, нічого не змінюють (Get, Search).

    • Приклад: GetAllProductsQuery.
    • Обробник: GetAllProductsQueryHandler — повертає дані.

Це дозволяє оптимізувати читання та запис окремо, а також робить код чистішим і зрозумілішим.


Dependency Injection (DI)

У проєкті використано вбудований DI-контейнер ASP.NET Core (Microsoft.Extensions.DependencyInjection). Він відповідає за створення об'єктів та передачу їх залежностей.

Реєстрація відбувається у Program.cs:

  1. Singleton: AddSingleton<IProductRepository, InMemoryProductRepository>()

    • Створюється один раз на весь час життя програми.
    • Використано для InMemoryProductRepository, щоб дані не зникали між різними HTTP-запитами.
  2. Scoped: AddScoped<IProductService, ProductService>()

    • Створюється новий для кожного HTTP-запиту.
    • Це стандартний час життя для сервісів бізнес-логіки.
  3. HttpClient: AddHttpClient<IProductProxy, FakeStoreProductProxy>()

    • Спеціальна реєстрація для сервісів, що роблять HTTP-запити. Вона ефективно керує підключеннями.
  4. MediatR: AddMediatR(...)

    • Автоматично сканує збірку і реєструє всі знайдені IRequestHandler як Scoped сервіси.

Висновок

Виконання практичної роботи дозволило ознайомитися з C# та ASP.NET 8. Реалізувавши архітектуру портів та даптерів (Hexagonal Architecture). Розділяючи бізнес-логіку та взаємодію із зовнішніми системами через інтерфейси.