Реализация Hexagonal Architecture для бэкенда

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация Hexagonal Architecture для бэкенда
Сложная
от 2 недель до 3 месяцев
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1214
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    852
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    815

Реализация Hexagonal Architecture для бэкенда

Hexagonal Architecture (Ports & Adapters, автор — Alistair Cockburn) изолирует ядро приложения от внешних деталей: фреймворков, БД, HTTP, очередей сообщений. Ядро определяет интерфейсы (Ports), внешние реализации (Adapters) подключаются к ним. Приложение одинаково тестируется как через HTTP, так и через CLI или тесты напрямую.

Структура

src/
├── domain/              # Чистый домен: сущности, VO, бизнес-правила
│   ├── entities/
│   ├── value-objects/
│   └── exceptions/
├── application/         # Use Cases + Ports (интерфейсы)
│   ├── use-cases/
│   ├── ports/
│   │   ├── inbound/     # Driving ports — как вызывают приложение
│   │   └── outbound/    # Driven ports — что приложение вызывает
├── infrastructure/      # Adapters
│   ├── persistence/     # PostgreSQL, Redis adapters
│   ├── messaging/       # Kafka, RabbitMQ adapters
│   ├── http/            # REST, GraphQL controllers
│   └── external/        # Stripe, SendGrid adapters
└── main.ts              # Composition Root — сборка адаптеров

Inbound и Outbound Ports

Inbound Port (Driving) — интерфейс, через который внешний мир вызывает приложение:

// ports/inbound/OrderUseCases.ts
export interface CreateOrderUseCase {
  execute(command: CreateOrderCommand): Promise<CreateOrderResult>;
}

export interface GetOrderUseCase {
  execute(query: GetOrderQuery): Promise<OrderDto | null>;
}

Outbound Port (Driven) — интерфейс, который приложение использует для внешних зависимостей:

// ports/outbound/OrderRepository.ts
export interface OrderRepository {
  findById(id: string): Promise<Order | null>;
  save(order: Order): Promise<void>;
}

// ports/outbound/PaymentGateway.ts
export interface PaymentGateway {
  charge(amount: Money, card: CardDetails): Promise<PaymentResult>;
  refund(paymentId: string, amount: Money): Promise<RefundResult>;
}

// ports/outbound/NotificationService.ts
export interface NotificationService {
  sendOrderConfirmation(order: Order, customer: Customer): Promise<void>;
}

Use Case (Application Core)

// application/use-cases/CreateOrderUseCase.ts
export class CreateOrderUseCaseImpl implements CreateOrderUseCase {
  constructor(
    private orderRepo: OrderRepository,        // outbound port
    private productRepo: ProductRepository,    // outbound port
    private paymentGateway: PaymentGateway,    // outbound port
    private notificationSvc: NotificationService // outbound port
  ) {}

  async execute(cmd: CreateOrderCommand): Promise<CreateOrderResult> {
    const order = Order.create(cmd.customerId);

    for (const item of cmd.items) {
      const product = await this.productRepo.findById(item.productId);
      if (!product) throw new ProductNotFoundError(item.productId);
      order.addItem(product, item.quantity);
    }

    order.submit();

    const payment = await this.paymentGateway.charge(
      order.total, cmd.paymentDetails
    );
    order.confirmPayment(payment.id);

    await this.orderRepo.save(order);

    // Fire and forget
    this.notificationSvc.sendOrderConfirmation(order, { id: cmd.customerId });

    return { orderId: order.id, status: order.status };
  }
}

Use Case ничего не знает о HTTP, PostgreSQL или Stripe — только о портах.

Adapters

Inbound HTTP Adapter (Express/Fastify):

// infrastructure/http/OrderController.ts
export class OrderController {
  constructor(private createOrder: CreateOrderUseCase) {}

  async create(req: Request, res: Response) {
    try {
      const result = await this.createOrder.execute({
        customerId: req.user.id,
        items: req.body.items,
        paymentDetails: req.body.payment
      });
      res.status(201).json(result);
    } catch (e) {
      if (e instanceof ProductNotFoundError) {
        return res.status(422).json({ error: e.message });
      }
      throw e;
    }
  }
}

Outbound PostgreSQL Adapter:

// infrastructure/persistence/PostgresOrderRepository.ts
export class PostgresOrderRepository implements OrderRepository {
  constructor(private db: Pool) {}

  async findById(id: string): Promise<Order | null> {
    const row = await this.db.query(
      'SELECT * FROM orders WHERE id = $1', [id]
    ).then(r => r.rows[0]);

    return row ? OrderMapper.toDomain(row) : null;
  }

  async save(order: Order): Promise<void> {
    const data = OrderMapper.toPersistence(order);
    await this.db.query(
      `INSERT INTO orders (id, customer_id, status, total, created_at)
       VALUES ($1, $2, $3, $4, $5)
       ON CONFLICT (id) DO UPDATE SET status=$3, total=$4`,
      [data.id, data.customerId, data.status, data.total, data.createdAt]
    );
  }
}

Outbound Stripe Adapter:

// infrastructure/external/StripePaymentGateway.ts
export class StripePaymentGateway implements PaymentGateway {
  private stripe: Stripe;

  async charge(amount: Money, card: CardDetails): Promise<PaymentResult> {
    const intent = await this.stripe.paymentIntents.create({
      amount: Math.round(amount.value * 100),
      currency: amount.currency.toLowerCase(),
      payment_method: card.tokenId,
      confirm: true
    });

    return { id: intent.id, status: intent.status };
  }
}

Composition Root

Единственное место, где адаптеры подключаются к портам:

// main.ts
const db = new Pool({ connectionString: process.env.DATABASE_URL });
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

const orderRepo = new PostgresOrderRepository(db);
const productRepo = new PostgresProductRepository(db);
const paymentGateway = new StripePaymentGateway(stripe);
const notificationSvc = new SendGridNotificationService(process.env.SENDGRID_KEY);

const createOrderUseCase = new CreateOrderUseCaseImpl(
  orderRepo, productRepo, paymentGateway, notificationSvc
);

const orderController = new OrderController(createOrderUseCase);

// Register routes
app.post('/orders', (req, res) => orderController.create(req, res));

Тестирование

Главное преимущество — тестирование без реальных зависимостей:

describe('CreateOrderUseCase', () => {
  it('создаёт заказ и списывает оплату', async () => {
    const mockOrderRepo = { save: jest.fn(), findById: jest.fn() };
    const mockPaymentGateway = {
      charge: jest.fn().mockResolvedValue({ id: 'pay_123', status: 'succeeded' })
    };
    const mockNotifications = { sendOrderConfirmation: jest.fn() };

    const useCase = new CreateOrderUseCaseImpl(
      mockOrderRepo, mockProductRepo, mockPaymentGateway, mockNotifications
    );

    const result = await useCase.execute(validCommand);

    expect(result.orderId).toBeDefined();
    expect(mockPaymentGateway.charge).toHaveBeenCalledWith(
      expect.objectContaining({ value: 150 }),
      validCommand.paymentDetails
    );
    expect(mockOrderRepo.save).toHaveBeenCalled();
  });
});

Юнит-тесты запускаются за миллисекунды. Никаких запущенных БД или Stripe.

Сроки реализации

  • Рефакторинг существующего Express/Fastify приложения к гексагональной архитектуре — 2–4 недели
  • Новый сервис с нуля по hexagonal: 1 use case — 1–2 дня, полный модуль из 10+ use cases — 2–3 недели
  • Настройка тестовой инфраструктуры (mocks, in-memory adapters) — 3–5 дней