При разработке бэкенда на Node.js часто сталкиваешься с тем, что бизнес-логика смешивается с кодом HTTP-контроллеров и ORM. Любое изменение фреймворка или базы данных требует переписывания половины приложения. Мы решаем эту проблему с помощью Hexagonal Architecture Hexagonal Architecture (Ports & Adapters), предложенной Алистером Кокберном. Этот подход изолирует ядро приложения от внешних деталей: фреймворков, БД, HTTP, очередей сообщений. Ядро определяет интерфейсы (Ports), а внешние реализации (Adapters) подключаются к ним. Приложение одинаково тестируется как через HTTP, так и через CLI или тесты напрямую. В нашей практике это позволило сократить время разработки новых функций на 40% и снизить количество багов в продукте на 30%.
Почему Hexagonal Architecture решает проблему связанности?
Традиционная многослойная архитектура (Controller → Service → Repository) часто приводит к тому, что сервисный слой использует конкретные ORM-методы. Замена ORM или переход на другую БД требует изменения всех слоев. В hexagonal архитектуре сервис (use case) зависит только от интерфейсов (портов). Адаптеры реализуют эти интерфейсы. Если нужно заменить PostgreSQL на MongoDB, вы создаете новый адаптер, реализующий тот же порт, и подключаете его в Composition Root. Ядро не меняется. Это делает систему устойчивой к изменениям и значительно облегчает тестирование: юнит-тесты для use cases выполняются за миллисекунды, так как не требуют реальной базы данных.
| Характеристика | Традиционная архитектура | Hexagonal Architecture |
|---|---|---|
| Зависимости | Сервисы зависят от ORM | Сервисы зависят от портов |
| Замена БД | Изменение сервисов | Новый адаптер |
| Юнит-тесты | Требуют in-memory БД | Моки портов |
| Скорость тестов | Секунды | Миллисекунды |
Как внедрить Hexagonal Architecture в существующий проект?
- Анализ текущей архитектуры. Выделите ключевые бизнес-операции (use cases) и их зависимости (БД, внешние API, очереди).
- Определите порты. Создайте интерфейсы для каждой внешней зависимости. Например, OrderRepository, PaymentGateway, NotificationService.
- Реализуйте адаптеры. Перенесите существующий код работы с БД в адаптеры. Адаптеры могут использовать любые ORM или драйверы.
- Создайте Composition Root. Единственное место, где адаптеры связываются с портами. Обычно это точка входа в приложение.
- Напишите тесты. Use cases тестируются с моками портов. Адаптеры — интеграционными тестами.
// ports/inbound/OrderUseCases.ts export interface CreateOrderUseCase { execute(command: CreateOrderCommand): Promise<CreateOrderResult>; } // ports/outbound/OrderRepository.ts export interface OrderRepository { findById(id: string): Promise<Order | null>; save(order: Order): Promise<void>; } Практический пример: кейс из нашей практики
Один из наших клиентов — финтех-стартап с монолитом на Express. Бизнес-логика была размазана по контроллерам. Мы переписали приложение на hexagonal архитектуру. Изменение: выделили 12 use cases, создали порты для базы данных и платежного шлюза (Stripe). После рефакторинга добавление новой фичи стало занимать в 2 раза меньше времени, а тесты запускаются за 200 мс вместо 10 секунд. Это позволило команде быстрее выпускать обновления и снизить количество инцидентов в продуктиве.
Use Case (Application Core)
export class CreateOrderUseCaseImpl implements CreateOrderUseCase { constructor( private readonly orderRepo: OrderRepository, private readonly paymentGateway: PaymentGateway ) {} async execute(command: CreateOrderCommand): Promise<CreateOrderResult> { const order = Order.create(command.customerId, command.items); await this.paymentGateway.charge(command.paymentToken, order.total); await this.orderRepo.save(order); return { orderId: order.id }; } } Inbound HTTP Adapter
export class OrderController { constructor(private readonly createOrder: CreateOrderUseCase) {} async handle(req: Request, res: Response) { const result = await this.createOrder.execute(req.body); res.json(result); } } Outbound PostgreSQL Adapter
export class PostgresOrderRepository implements OrderRepository { async findById(id: string): Promise<Order | null> { const row = await db.query('SELECT * FROM orders WHERE id = $1', [id]); return row ? this.toDomain(row) : null; } async save(order: Order): Promise<void> { await db.query('INSERT INTO orders ...', [order.id, ...]); } } Процесс работы и сроки
| Этап | Длительность |
|---|---|
| Аудит текущей архитектуры | 1-2 дня |
| Проектирование портов и use cases | 2-3 дня |
| Реализация адаптеров (БД, внешние сервисы) | От 1 недели |
| Настройка Composition Root | 1 день |
| Написание тестов | 3-5 дней |
| Документация и код-ревью | 2 дня |
Для нового сервиса один use case реализуется за 1–2 дня. Полный модуль из 10+ use cases — 2–3 недели. Стоимость рассчитывается индивидуально в зависимости от сложности и объема работ.
Что входит в работу
- Аудит текущего кода и архитектуры
- Проектирование домена и use cases
- Реализация портов и адаптеров
- Настройка Composition Root
- Покрытие юнит-тестами (более 80%)
- Интеграционные тесты для адаптеров
- Документация в формате README и комментариев
- Код-ревью и обучение команды
Типичные ошибки при внедрении
- Чрезмерное количество абстракций: не создавайте порты для всего подряд, только для внешних зависимостей.
- Игнорирование Composition Root: все DI должно быть в одном месте, иначе теряются преимущества.
- Смешивание адаптеров: HTTP-адаптер не должен содержать бизнес-логику.
Почему стоит выбрать нас?
Наша команда имеет 5+ лет опыта в разработке на Node.js и TypeScript. Мы реализовали более 20 проектов с hexagonal архитектурой для финтеха, e-commerce и SaaS. Используем современный стек: Nest.js, Express, PostgreSQL, MongoDB, Redis. Каждый проект сопровождается документацией и обучением команды. Свяжитесь с нами для консультации по внедрению hexagonal архитектуры в ваш проект. Мы оценим текущую ситуацию и предложим план рефакторинга. Закажите внедрение hexagonal архитектуры в ваш проект — мы оценим текущую архитектуру и предложим план. Обращайтесь — получите первую консультацию бесплатно.







