Архитектура приложения
@azure-net/kit реализует гибридную архитектуру, объединяющую принципы Domain Driven Design (DDD) и Clean Architecture. Это позволяет создавать слабо связанные, тестируемые и легко расширяемые системы.
Общие принципы архитектуры
1. Разделение на слои
Архитектура строится на четырех основных слоях:
- Domain - бизнес-логика и модели предметной области
- Infrastructure - внешние зависимости и адаптеры
- Application - сервисы приложения и оркестрация
- Delivery - предоставление данных ui-элементам
2. Инверсия зависимостей
Внутренние слои не зависят от внешних. Зависимости направлены внутрь к домену.
3. Контекстное разделение
Система разделяется на глобальные Bounded Context'ы (контексты), каждый со своей предметной областью.
Пример разделения на контексты
src/
├── app/
│ ├── contexts/ # Bounded Contexts
│ │ ├── admin/ # Административный контекст
│ │ └── account/ # Клиентский контекст
│ ├── core/ # Базовые компоненты для настройки и работы с пакетом (используются в контекстах как ядро)
│ └── shared/ # shared слой - общие и переиспользуемые элементы, которые не принадлежат конкретному модулю или контекстуДетальная структура контекста
Каждый контекст имеет следующую структуру:
Domain слой (contexts/{context}/domain/)
Назначение: Содержит бизнес-логику и модели предметной области.
domain/
├── {aggregate}/ # Агрегат предметной области
│ ├── model/ # Модели данных
│ │ └── index.ts # Интерфейсы сущностей
│ ├── enums/ # Перечисления
│ ├── constants/ # Константы относящиеся к бизнес-сущности
│ ├── ports/ # Порты (интерфейсы)
│ │ └── index.ts # Контракты для внешних зависимостей
│ └── index.ts # Экспорт агрегатаЧто содержится в Domain:
Model (Модели данных):
- Интерфейсы сущностей предметной области
- Пример:
IUser,IProduct
Enums (Перечисления):
- Enums относящиеся к сущности
- Пример: статусы пользователя, роли пользователя
Ports (Порты):
- Интерфейсы для внешних зависимостей
- Определяют контракты для репозиториев и сервисов
- Включают структуры данных для входа/выхода
- Служат единым описанием того, что именно домен ожидает от внешнего мира и что он отдаёт наружу, сохраняя при этом изоляцию доменной логики.
- Пример:
IUserRepository,IUserCreateRequest,IUserCollectionResponse,IUserCollectionQuery
Infrastructure слой (contexts/{context}/infrastructure/)
Назначение: Реализует технические детали и внешние интеграции.
infrastructure/
├── http/
│ └── repositories/ # Реализации репозиториев
│ ├── UserRepository.ts
│ ├── AuthRepository.ts
│ └── index.ts
├── providers/ # Инфраструктурные провайдеры
│ ├── InfrastructureProvider.ts
│ └── index.ts
└── index.tsЧто содержится в Infrastructure:
Repositories (Репозитории):
- Реализуют порты из Domain слоя
- Инкапсулируют логику доступа к данным
- Преобразуют внешние данные в модели домена
- Работают с HTTP API
Providers (Провайдеры):
- Предоставляют доступ к компонентам инфраструктурного слоя для других слоев
- Создают и настраивают репозитории и другие компоненты
- Инъекция зависимостей (DataSources, HTTP клиенты)
Application слой (contexts/{context}/application/)
Назначение: Работа со сценариями, которые реализуют бизнес-логику на уровне приложения
application/
├── services/ # Сервисы приложения
│ ├── JobRequestService.ts
│ ├── AuthService.ts
│ └── index.ts
├── providers/ # Application провайдеры
│ ├── ApplicationProvider.ts
│ └── index.ts
└── index.tsЧто содержится в Application:
Services (Сервисы):
- Реализуют методы репозиториев
- Создают сценарии использования и реализацию доменной бизнес-логики
- Оркеструют вызовы к домену и инфраструктуре
- Используют порты из Domain слоя
- Пример: сценарий создания пользователя, сценарий обновления пользователя и тд
Providers (Поставщики):
- Предоставляют доступ к компонентам application слоя для других слоев
- Создают и настраивают сервисы
- Инъекция зависимостей из инфраструктурного слоя
Delivery слой (contexts/{context}/delivery/)
Назначение: Обрабатывает действия юзера и предоставляет данные с application слоя.
delivery/
├── {feature}/ # Функциональная область
│ ├── {Feature}Presenter.ts # Презентер
│ ├── schema/ # Схемы валидации
│ │ ├── CreateSchema.ts
│ │ ├── UpdateSchema.ts
│ │ └── index.ts
│ └── consts/ # Константы
└── auth/ # АвторизацияЧто содержится в Delivery:
Presenters (Презентеры):
- Связывают UI с Application слоем
- Обрабатывают пользовательские действия
- Управляют состоянием UI
Schema (Схемы Ui-валидации):
- Валидация входящих данных
- Трансформация данных для API
- Обеспечение типобезопасности
Все что требуется для UI, (ui-константы, сторы для состояний и тд)
Core компоненты (app/core/)
Назначение: Базовые компоненты для настройки и работы с пакетом (используются в контекстах как ядро).
core/
├── datasources/ # Базовые источники данных
├── providers/ # Базовые провайдеры
├── presenters/ # Базовые презентеры
├── middleware/ # Компоненты для настройки и подключения в проект универсальных middlewares
├── responses/ # Базовые обработчики ответов
└── schemas/ # Базовые настройки схем ui-валидацииЧто содержится в Core:
DataSources:
- HTTP-клиенты для работы с API
- Конфигурация подключений
- Обработка ошибок и retry-логика
Providers:
- Глобальные провайдеры зависимостей
- Создание и настройка DataSources
Presenters:
- Базовые классы для презентеров и их инъекции
и т.д.
Shared компоненты (app/shared/)
Назначение: общие и переиспользуемые элементы, которые не принадлежат конкретному модулю или контексту
shared/
├── helpers/ # Глобальные вспомогательные функции
├── stores/ # Глобальные хранилища состояний
├── types/ # Общие типы
└── etc.../ # Остальные папки которые требуются в shared в вашем проектеПринципы работы с архитектурой
1. Dependency Injection через Providers
Система использует Provider Pattern для управления зависимостями:
// Создание провайдера с зависимостями
export const ApplicationProvider = createBoundaryProvider('{ContextName}ApplicationProvider', {
dependsOn: { InfrastructureProvider },
register: ({ InfrastructureProvider }) => ({
// Регистрация сервисов
UserService: () => new UserService(InfrastructureProvider.UserRepository),
SystemHealthService: () => new SystemHealthService()
}),
boot: ({SystemHealthService}) => {
// Позволяет получить доступ к уже зарегистрированным сервисам и выполнить инициализацию, запуск фоновых процессов или настройку наблюдателей.
SystemHealthService.startMonitoring()
}
});2. Порты как контракты
Ports определяют интерфейсы, которые должны быть реализованы на внешних слоях:
// Порт в Domain слое
export interface IUserCreateRequest {
name: string;
}
export interface IUserCreateResponse {
token: string;
}
export interface IUserRepository {
create(request: IUserCreateRequest): Promise<IUserCreateResponse>;
}
// Реализация в Infrastructure слое
export class UserRepository implements IUserRepository {
create(request: IUserCreateRequest): Promise<IUserCreateResponse> {
// Конкретная реализация
}
}3. Презентеры как координаторы UI
Presenters связывают пользовательский интерфейс с бизнес-логикой:
export const UserPresenter = AppPresenter('{ContextName}UserPresenter', ({ createAsyncAction }) => {
const { UserService } = ApplicationProvider();
const create = async (data: IUserCreateRequest) => {
return await createAsyncAction(() => UserService.create(data));
};
return { create };
});Рекомендации по разработке
При добавлении новой функции:
- Определите модель в Domain слое
- Создайте порт для внешних зависимостей
- Создайте репозиторий в Infrastructure слое
- Реализуйте сервис в Application слое
- Обновите провайдеры для связывания зависимостей
- Настройте презентер в Delivery слое
Преимущества данной архитектуры
- Тестируемость - каждый слой можно тестировать независимо
- Расширяемость - легко добавлять новые функции без изменения существующего кода
- Понятность - четкое разделение ответственностей
- Масштабируемость - поддержка множественных контекстов
Эта архитектура обеспечивает четкое разделение ответственностей и позволяет создавать надежные, тестируемые и легко поддерживаемые приложения.