Инженерия ПО
Архитектурные паттерны
Цели урока
- Понимать принцип разделения слоёв в Layered Architecture
- Объяснить разницу между Port и Adapter в Hexagonal Architecture
- Оценить trade-off микросервисов vs монолита для конкретной команды
- Проектировать event-driven взаимодействие между сервисами
- Выбирать архитектурный паттерн исходя из реальных требований
Предварительные знания
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**: бизнес-логика определяет интерфейсы, инфраструктура их реализует. Зависимости направлены **внутрь**, к домену. Домен ни от чего не зависит.
| Layered | Hexagonal |
|---|---|
| Business зависит от Database | Business определяет интерфейс, 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 Broker | Kafka, 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