Инженерия ПО

Архитектурные паттерны

Цели урока

  • Понимать принцип разделения слоёв в Layered Architecture
  • Объяснить разницу между Port и Adapter в Hexagonal Architecture
  • Оценить trade-off микросервисов vs монолита для конкретной команды
  • Проектировать event-driven взаимодействие между сервисами
  • Выбирать архитектурный паттерн исходя из реальных требований

Предварительные знания

  • Requirements and Specifications

Amazon, 2001: разработчики тратят 70% времени на координацию, монолит из миллионов строк неуправляем. Безос приказал: «Все команды через API. Нарушителей - уволить.» Это решение привело к AWS и микросервисной революции. Но Netflix, перейдя на 700+ микросервисов, изобрёл Chaos Engineering - инструмент для выживания в распределённом аду. Shopify при этом делает 150 млрд на монолите. Архитектура - всегда компромисс.

  • **Shopify**: монолит Ruby on Rails, обслуживающий 150 млрд+ оборота. Доказательство, что монолит может масштабироваться
  • **Netflix**: 700+ микросервисов, Chaos Monkey убивает production-сервисы каждый день - и всё работает
  • **Uber**: начали с монолита, мигрировали на микросервисы при 1000+ инженеров - когда боль координации стала невыносимой
  • **Банки**: event sourcing для аудита транзакций - каждая операция записана навсегда

Манифест Безоса и рождение API-first

В 2002 году Jeff Bezos отправил всем командам Amazon внутренний меморандум: все данные и функциональность должны быть доступны только через API. Нет прямого доступа к БД, нет прямых интеграций. Кто нарушит - уволен. Этот документ, известный как "Bezos API Mandate", стал основой для Amazon SOA, а потом и AWS. В 2011 году бывший сотрудник Amazon Стив Йегги случайно опубликовал пересказ этого меморандума в Google+. Пост разошёлся по всему интернету и стал каноническим описанием микросервисной философии.

Layered Architecture

Весь код приложения в одном файле на 50 000 строк. HTTP-запросы, бизнес-логика, SQL - перемешано. Изменение формата ответа API ломает запись в базу данных. Это не гипотеза - это реальное состояние legacy-кода во многих компаниях. **Layered Architecture** - первый шаг к порядку: разделить код на **слои с чёткой ответственностью**.

Ключевое правило: **зависимости идут только вниз**. Presentation знает о Business, Business знает о Data Access, но не наоборот. Это позволяет менять базу данных, не трогая контроллеры.

MVC (Model-View-Controller) - вариация layered architecture для UI-приложений. Model = данные и бизнес-логика, View = отображение, Controller = связующее звено. Rails, Django, Spring MVC построены на этом паттерне.

ПлюсыМинусы
Простота пониманияЖёсткая связанность между слоями
Разделение ответственностиИзменение в одном слое каскадирует вверх/вниз
Легко начать проектBusiness Layer привязан к Data Access
Знакомо большинству разработчиковСложно тестировать без БД

Главная проблема layered architecture - Business Layer зависит от Data Access. Бизнес-логика привязана к конкретной базе данных. Миграция с PostgreSQL на MongoDB требует переписывания бизнес-слоя.

В layered architecture контроллер напрямую вызывает SQL-запрос к базе данных. Что нарушено?

Hexagonal Architecture (Ports & Adapters)

Layered architecture привязывает бизнес-логику к инфраструктуре. Алистер Кокбёрн в 2005 году предложил **Hexagonal Architecture** (она же Ports & Adapters): **домен в центре, инфраструктура снаружи**. Бизнес-логика не знает, откуда пришёл запрос (HTTP? CLI? Тест?) и куда идут данные (PostgreSQL? MongoDB? In-memory?).

Port - интерфейс, описывающий КОНТРАКТ. «Нужно сохранять заказы» (порт), а не «Нужен PostgreSQL» (реализация). Adapter - конкретная реализация порта. PostgresOrderRepository и InMemoryOrderRepository - два адаптера одного порта.

Ключевой принцип - **Dependency Inversion**: бизнес-логика определяет интерфейсы, инфраструктура их реализует. Зависимости направлены **внутрь**, к домену. Домен ни от чего не зависит.

LayeredHexagonal
Business зависит от DatabaseBusiness определяет интерфейс, Database реализует
Тесты требуют БДТесты используют in-memory adapter
Смена БД = переписать всёСмена БД = новый adapter
Просто начатьБольше boilerplate вначале
Подходит для CRUDПодходит для сложного домена

Hexagonal architecture особенно полезна, когда бизнес-логика сложная и долгоживущая. Для простого CRUD-приложения layered architecture может быть достаточна - не усложнять без причины.

В hexagonal architecture OrderService импортирует `import { Pool } from 'pg'`. Что нарушено?

Microservices Architecture

2001 год. Монолит Amazon вырос настолько, что команды тратили **70% времени на координацию** вместо написания кода. Джефф Безос издал знаменитый приказ: все команды общаются через API, никаких прямых вызовов, нарушителей - уволить. Так родилась философия микросервисов. Но потом Netflix перешёл на 700+ микросервисов и столкнулся с распределёнными системами - и изобрёл Chaos Engineering.

Microservice - небольшой, независимо деплоящийся сервис, отвечающий за одну бизнес-область (bounded context). У каждого сервиса своя база данных, свой API, свой lifecycle.

Netflix - канонический пример: 700+ микросервисов. Рекомендации, стриминг, биллинг, поиск - каждый сервис независим. Если сервис рекомендаций упал, пользователь всё равно может смотреть фильмы.

ПлюсыМинусы
Независимый деплой каждого сервисаDistributed systems: network latency, partial failures
Масштабирование конкретного сервисаData consistency между сервисами - СЛОЖНО
Свобода выбора технологийМониторинг, трейсинг, дебаг - кратно сложнее
Изоляция отказовИнфраструктура: service discovery, API gateway, message broker
Маленькие команды, быстрые решенияМежсервисное тестирование - ад

Микросервисы - не upgrade монолита. Это trade-off: сложность кода меняется на сложность инфраструктуры. Для команды из 3 человек микросервисы - почти всегда overhead.

Стартап из 4 разработчиков строит MVP. CTO предлагает сразу разбить на 12 микросервисов «чтобы масштабироваться в будущем». Это хорошая идея?

Event-Driven Architecture

В микросервисной архитектуре сервисы общаются через API: OrderService вызывает PaymentService, потом NotificationService, потом InventoryService. Каждый вызов - синхронный, каждый - потенциальная точка отказа. NotificationService не отвечает 30 секунд - весь заказ висит? **Event-Driven Architecture** решает иначе: вместо прямых вызовов сервисы **публикуют события**, заинтересованные подписываются.

Event - факт, который уже произошёл: OrderCreated, PaymentCompleted, UserRegistered. Событие **неизменяемо** (immutable) - его нельзя отменить, можно только опубликовать компенсирующее событие (OrderCancelled).

Продвинутые паттерны event-driven архитектуры:

ПаттернОписаниеКогда использовать
Event BusПростая шина: publish/subscribeВнутри одного сервиса или монолита
Message BrokerKafka, RabbitMQ - гарантия доставкиМежду микросервисами
CQRSРазделение моделей чтения и записиРазные нагрузки на read и write
Event SourcingХранить не состояние, а последовательность событийАудит, финансы, отмена операций

Event-driven architecture добавляет **eventual consistency**: данные между сервисами синхронизируются не мгновенно. Пользователь создал заказ, но на странице «Мои заказы» его ещё нет (ивент не обработан). Это требует продуманного UX.

Microservices всегда лучше monolith. Монолит - это legacy, от которого нужно избавляться.

Monolith-first - рекомендуемый подход (Мартин Фаулер). Хорошо структурированный монолит проще, дешевле и быстрее для маленьких команд. Микросервисы оправданы при масштабе Netflix/Amazon.

Микросервисы добавляют огромную accidental complexity: service discovery, distributed tracing, межсервисная аутентификация, eventual consistency, network failures. Shopify - монолит на 150 млрд долларов оборота. Basecamp - монолит. Stack Overflow обслуживает миллионы пользователей монолитом на нескольких серверах. Архитектура подбирается под реальные проблемы, а не под хайп.

OrderService создаёт заказ и синхронно вызывает NotificationService для отправки email. NotificationService упал. Что произойдёт с заказом?

Ключевые идеи

  • Layered: Presentation → Business → Data Access. Просто, но бизнес-логика привязана к инфраструктуре
  • Hexagonal: Ports & Adapters. Домен в центре, инфраструктура снаружи. Dependency Inversion. Тестируемость без БД
  • Microservices: независимые сервисы, свои БД, API contracts. Масштабирование команд, но distributed systems hell
  • Event-Driven: события вместо прямых вызовов. Loose coupling, но eventual consistency
  • Безос заставил Amazon перейти на API - и это работает для 1.5 млн сотрудников. Но Shopify делает 150 млрд на монолите. Архитектура подбирается под проблему, а не под моду

Куда дальше?

Архитектурные паттерны - скелет системы. Теперь наполним его практиками:

  • Что такое Software Engineering — Фундамент: зачем нужна архитектура в инженерном процессе
  • Требования и спецификации — Требования определяют выбор архитектуры (NFR → architecture)

Вопросы для размышления

  • Какую архитектуру использует проект, над которым сейчас идёт работа? Она подходит для текущего масштаба?
  • При построении e-commerce для 100 пользователей, а через год - для 100 000: с чего начать?
  • Назовите пример accidental complexity в проекте. Какой архитектурный паттерн мог бы её уменьшить?

Связанные уроки

  • se-01 — Фундамент: зачем нужна архитектура в инженерном процессе
  • se-02 — Требования (NFR) определяют выбор архитектуры
  • se-04 — Design patterns реализуют архитектурные решения на уровне кода
  • ds-01 — Микросервисы - это распределённые системы с CAP-проблемами
  • ds-02 — Event-driven + eventual consistency = AP в CAP-теореме
  • se-08 — API Design - контракты между микросервисами
  • db-04-cap
Архитектурные паттерны

0

1

Войти