Skip to content

Архитектура приложения

@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 для управления зависимостями:

typescript
// Создание провайдера с зависимостями
export const ApplicationProvider = createBoundaryProvider('{ContextName}ApplicationProvider', {
  dependsOn: { InfrastructureProvider },
  register: ({ InfrastructureProvider }) => ({
    // Регистрация сервисов
    UserService: () => new UserService(InfrastructureProvider.UserRepository),
    SystemHealthService: () => new SystemHealthService()
  }),
  boot: ({SystemHealthService}) => {
    // Позволяет получить доступ к уже зарегистрированным сервисам и выполнить инициализацию, запуск фоновых процессов или настройку наблюдателей.
    SystemHealthService.startMonitoring()
  }
});

2. Порты как контракты

Ports определяют интерфейсы, которые должны быть реализованы на внешних слоях:

typescript
// Порт в 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 связывают пользовательский интерфейс с бизнес-логикой:

typescript
export const UserPresenter = AppPresenter('{ContextName}UserPresenter', ({ createAsyncAction }) => {
  const { UserService } = ApplicationProvider();
  
  const create = async (data: IUserCreateRequest) => {
    return await createAsyncAction(() => UserService.create(data));
  };
  
  return { create };
});

Рекомендации по разработке

При добавлении новой функции:

  1. Определите модель в Domain слое
  2. Создайте порт для внешних зависимостей
  3. Создайте репозиторий в Infrastructure слое
  4. Реализуйте сервис в Application слое
  5. Обновите провайдеры для связывания зависимостей
  6. Настройте презентер в Delivery слое

Преимущества данной архитектуры

  1. Тестируемость - каждый слой можно тестировать независимо
  2. Расширяемость - легко добавлять новые функции без изменения существующего кода
  3. Понятность - четкое разделение ответственностей
  4. Масштабируемость - поддержка множественных контекстов

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