← Назад к вопросам

Какие абстракции предполагает гексагональная архитектура?

2.0 Middle🔥 111 комментариев
#JavaScript Core

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI4 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Абстракции гексагональной архитектуры (Ports & Adapters)

Гексагональная архитектура, также известная как Ports & Adapters, предлагает строгий набор абстракций для отделения бизнес-логики приложения от внешних зависимостей (UI, базы данных, сторонние API). В центре внимания — ядро приложения (Domain Layer), которое остается чистым и независимым.

Ключевые абстракции

1. Доменный слой (Core)

Это ядро системы, содержащее бизнес-логику, сущности, правила и процессы. Он не должен знать о внешнем мире — никаких прямых ссылок на HTTP, базы данных или файловые системы.

// Пример сущности (Entity) в доменном слое
class Order {
  private id: string;
  private items: OrderItem[];
  private status: OrderStatus;

  constructor(id: string, items: OrderItem[]) {
    this.id = id;
    this.items = items;
    this.status = OrderStatus.PENDING;
  }

  // Бизнес-метод
  public calculateTotal(): number {
    return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
  }

  // Доменное правило
  public canBeCancelled(): boolean {
    return this.status === OrderStatus.PENDING;
  }
}

2. Порт (Port)

Порт — это абстракция, представляющая контракт или интерфейс, через который ядро взаимодействует с внешним миром. Это "окно" из ядра наружу.

  • Входящие порты (Input Ports) — определяют, как внешние компоненты (например, UI) могут взаимодействовать с ядром. Это интерфейсы для сервисов приложения.
  • Исходящие порты (Output Ports) — определяют, как ядро может взаимодействовать с внешними системами (например, для сохранения данных). Это интерфейсы для репозиториев, клиентов API.
// Пример исходящего порта (интерфейс репозитория)
interface OrderRepository {
  save(order: Order): Promise<void>;
  findById(id: string): Promise<Order | null>;
  findAll(): Promise<Order[]>;
}

// Пример входящего порта (интерфейс сервиса приложения)
interface OrderService {
  createOrder(items: OrderItem[]): Promise<Order>;
  cancelOrder(orderId: string): Promise<void>;
  getOrderTotal(orderId: string): Promise<number>;
}

3. Адаптер (Adapter)

Адаптер — это конкретная реализация порта, которая соединяет ядро с конкретной внешней технологией или системой. Адаптер знает о деталях внешнего мира, но ядро его не знает.

  • Входящие адаптеры (Input Adapters) — вызывают входящие порты. Например: контроллер REST API, компонент React/Vue, обработчик CLI команды.
  • Исходящие адаптеры (Output Adapters) — реализуют исходящие порты. Например: репозиторий для MongoDB, клиент для внешнего платежного API, сервис для отправки email.
// Пример исходящего адаптера (реализация репозитория для MongoDB)
class MongoOrderRepository implements OrderRepository {
  private collection: Collection;

  constructor(db: Db) {
    this.collection = db.collection('orders');
  }

  async save(order: Order): Promise<void> {
    const doc = { _id: order.id, ...order.toJSON() };
    await this.collection.insertOne(doc);
  }

  async findById(id: string): Promise<Order | null> {
    const doc = await this.collection.findOne({ _id: id });
    return doc ? Order.fromJSON(doc) : null;
  }
}

// Пример входящего адаптера (контроллер Express.js)
class OrderController {
  private orderService: OrderService;

  constructor(orderService: OrderService) {
    this.orderService = orderService;
  }

  async handleCreateOrder(req: Request, res: Response): Promise<void> {
    const items = req.body.items;
    const order = await this.orderService.createOrder(items);
    res.status(201).json(order);
  }
}

Как абстракции работают вместе

  • Ядро определяет бизнес**-интерфейсы (Порты)**.
  • Внешний мир предоставляет конкретные Адаптеры, реализующие эти интерфейсы.
  • Инъекция зависимостей (DI) связывает их: адаптер внедряется в ядро через его порт, но ядро не знает о конкретной реализации.

Преимущества такого разделения абстракций

  • Тестируемость: Ядро можно полностью протестировать в изоляции с использованием mock-адаптеров.
  • Свобода замены технологий: Чтобы перейти от MongoDB к PostgreSQL, нужно лишь создать новый адаптер PostgresOrderRepository, реализующий тот же OrderRepository. Ядро не меняется.
  • Защита от "распространения знаний": Детали базы данных или HTTP не проникают в бизнес-логику.
  • Фокусировка на бизнес-правилах: Разработчики ядра могут работать, не погружаясь в детали инфраструктуры.

В итоге, гексагональная архитектура предлагает мощные абстракции — Порты и Адаптеры — которые создают четкую, поддерживаемую границу между чистым доменом и изменчивым внешним окружением, делая приложение устойчивым к изменениям технологического стека.