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

Из чего состоит гексагональная архитектура

2.0 Middle🔥 171 комментариев
#Архитектура и паттерны

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

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

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

Гексагональная архитектура

Гексагональная архитектура (Hexagonal Architecture или Ports and Adapters) — это архитектурный паттерн, разработанный Алистаром Кокберном. Он направлен на создание слабо связанных, тестируемых и независимых от внешних зависимостей систем. Несмотря на название, количество слоев не имеет отношения к числу 6 — гексагон символизирует универсальность со всех сторон.

Основная идея

Центральная идея: приложение не должно зависеть от внешних систем. Вместо этого внешние системы присоединяются через порты и адаптеры.

┌────────────────────────────────────┐
│         Web UI (Adapter)           │
│         Mobile UI (Adapter)        │
│         CLI (Adapter)              │
└────────────┬───────────────────────┘
             │
        ┌────▼─────────────────────┐
        │      PORTS (interfaces)  │
        └────────────────────────┬─┘
             │
    ┌────────▼──────────────────┐
    │   APPLICATION CORE        │
    │   (Business Logic)        │
    └────────┬──────────────────┘
             │
        ┌────▼─────────────────────┐
        │      PORTS (interfaces)  │
        └────────────────────────┬─┘
             │
┌────────────┴───────────────────────┐
│ Database (Adapter)                │
│ Email Service (Adapter)           │
│ Payment Service (Adapter)         │
└────────────────────────────────────┘

Основные компоненты

1. Application Core (Ядро приложения)

Это сердце системы, которое содержит всю бизнес-логику, независимую от деталей реализации.

// domain/User.js — Entity
class User {
  constructor(id, email, name) {
    this.id = id;
    this.email = email;
    this.name = name;
  }
  
  isValidEmail() {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(this.email);
  }
}

// application/CreateUserUseCase.js
class CreateUserUseCase {
  constructor(userRepository, emailService) {
    this.userRepository = userRepository;
    this.emailService = emailService;
  }
  
  async execute(userData) {
    const user = new User(null, userData.email, userData.name);
    
    if (!user.isValidEmail()) {
      throw new Error("Invalid email");
    }
    
    const savedUser = await this.userRepository.save(user);
    await this.emailService.sendWelcome(savedUser.email);
    
    return savedUser;
  }
}

Главное: Ядро не знает, как хранятся данные или как отправляются письма.

2. Ports (Порты)

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

Входящие порты (Driving Ports) — как внешние системы используют приложение:

// ports/CreateUserPort.js
class CreateUserPort {
  async execute(userData) {
    throw new Error("Must be implemented");
  }
}

// Реальная реализация в ядре
class CreateUserUseCase extends CreateUserPort {
  // ...
}

Исходящие порты (Driven Ports) — как приложение общается с внешними системами:

// ports/UserRepositoryPort.js
class UserRepositoryPort {
  async save(user) {
    throw new Error("Must be implemented");
  }
  
  async findById(id) {
    throw new Error("Must be implemented");
  }
}

// ports/EmailServicePort.js
class EmailServicePort {
  async sendWelcome(email) {
    throw new Error("Must be implemented");
  }
}

3. Adapters (Адаптеры)

Адаптеры реализуют порты и создают мосты между ядром и внешними системами.

Входящие адаптеры (как пользователи общаются с приложением):

// adapters/web/UserController.js
class UserController {
  constructor(createUserUseCase) {
    this.createUserUseCase = createUserUseCase;
  }
  
  async create(req, res) {
    try {
      const user = await this.createUserUseCase.execute(req.body);
      res.json({ success: true, user });
    } catch (error) {
      res.status(400).json({ error: error.message });
    }
  }
}

// adapters/cli/UserCLI.js
class UserCLI {
  constructor(createUserUseCase) {
    this.createUserUseCase = createUserUseCase;
  }
  
  async run(args) {
    const user = await this.createUserUseCase.execute({
      email: args.email,
      name: args.name
    });
    console.log(`User created: ${user.name}`);
  }
}

Исходящие адаптеры (как приложение общается с внешними системами):

// adapters/database/PostgresUserRepository.js
const { UserRepositoryPort } = require("../../ports");

class PostgresUserRepository extends UserRepositoryPort {
  constructor(db) {
    super();
    this.db = db;
  }
  
  async save(user) {
    const result = await this.db.query(
      "INSERT INTO users (email, name) VALUES ($1, $2) RETURNING *",
      [user.email, user.name]
    );
    return result.rows[0];
  }
  
  async findById(id) {
    const result = await this.db.query(
      "SELECT * FROM users WHERE id = $1",
      [id]
    );
    return result.rows[0];
  }
}

// adapters/email/GmailEmailService.js
const { EmailServicePort } = require("../../ports");

class GmailEmailService extends EmailServicePort {
  constructor(gmailAPI) {
    super();
    this.gmailAPI = gmailAPI;
  }
  
  async sendWelcome(email) {
    await this.gmailAPI.send({
      to: email,
      subject: "Welcome!",
      body: "Thank you for signing up"
    });
  }
}

// Альтернативная реализация для смс
class TwilioEmailService extends EmailServicePort {
  constructor(twilioAPI) {
    super();
    this.twilioAPI = twilioAPI;
  }
  
  async sendWelcome(email) {
    await this.twilioAPI.sendSMS({
      to: email,
      body: "Welcome!"
    });
  }
}

4. Dependency Injection (Внедрение зависимостей)

Адаптеры и порты соединяются через инъекцию зависимостей:

// main.js или bootstrap
const db = new PostgreSQL();
const userRepository = new PostgresUserRepository(db);

const gmailAPI = new GmailAPI();
const emailService = new GmailEmailService(gmailAPI);

const createUserUseCase = new CreateUserUseCase(
  userRepository,
  emailService
);

const userController = new UserController(createUserUseCase);

// Позже, если нужна другая реализация
const mockRepository = new InMemoryUserRepository();
const mockEmailService = new InMemoryEmailService();

const testUseCase = new CreateUserUseCase(
  mockRepository,
  mockEmailService
);

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

1. Независимость от фреймворков

// Приложение работает так же с Express, Fastify или другим фреймворком
const controller = new UserController(createUserUseCase);

// Express
app.post('/users', controller.create.bind(controller));

// Fastify
fastify.post('/users', controller.create.bind(controller));

2. Легко тестировать

// Тест без базы данных и почты
class InMemoryUserRepository extends UserRepositoryPort {
  async save(user) {
    return { ...user, id: 1 };
  }
}

class MockEmailService extends EmailServicePort {
  async sendWelcome(email) {
    // Do nothing
  }
}

const useCase = new CreateUserUseCase(
  new InMemoryUserRepository(),
  new MockEmailService()
);

test('should create user', async () => {
  const user = await useCase.execute({
    email: 'test@example.com',
    name: 'John'
  });
  expect(user.id).toBeDefined();
});

3. Легко заменять реализации

// Было: PostgreSQL
const repository = new PostgresUserRepository(db);

// Стало: MongoDB
const repository = new MongoUserRepository(db);

// Бизнес-логика работает одинаково
const useCase = new CreateUserUseCase(repository, emailService);

4. Четкая структура проекта

src/
  domain/               # Entities, Value Objects
    User.js
    Role.js
  application/          # Use Cases
    CreateUserUseCase.js
    DeleteUserUseCase.js
  ports/                # Interfaces
    UserRepositoryPort.js
    EmailServicePort.js
  adapters/
    web/                # HTTP Controllers
      UserController.js
    database/           # Database implementations
      PostgresUserRepository.js
    email/              # Email implementations
      GmailEmailService.js
      TwilioEmailService.js
    cli/                # CLI handlers
      UserCLI.js

Когда использовать

Используй гексагональную архитектуру для:

  • Крупных, долгоживущих проектов
  • Систем, которые должны взаимодействовать с разными БД или сервисами
  • Когда нужна высокая тестируемость
  • Команд, работающих параллельно на разных адаптерах

Не используй, если:

  • Простое CRUD приложение
  • Макет, MVP
  • Один разработчик работает недолго

Заключение

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