Перейти к содержимому

Trade-offs мышление, Event Storming и архитектурные принципы

Зачем знать на Middle 3: Tech lead не «знает правильные ответы» — он умеет работать в условиях, где нет правильных ответов, только trade-offs. Каждое решение — компромисс между consistency/availability, latency/throughput, simplicity/flexibility. Event Storming — workshop-техника, которая структурирует понимание сложного domain’а за несколько часов. Архитектурные принципы (KISS, YAGNI, DRY, 12-factor, Conway’s Law) — это «системы координат» для принятия решений.

  1. Концепция: trade-off мышление как основа архитектуры
  2. Глубже: оси trade-offs, Event Storming, принципы
  3. Gotchas: типовые когнитивные ошибки
  4. Real cases: AWS, Netflix, Spotify
  5. Вопросы (20+)
  6. Practice: провести Event Storming, документировать trade-offs
  7. Источники

“All engineering decisions are trade-offs. Architecture is the art of picking which ones.” — Mark Richards

Junior пишет код. Middle пишет код хорошо. Tech lead решает, какой код вообще писать — и обосновывает выбор.

Каждое решение имеет:

  • Что выигрываем.
  • Что теряем.
  • Что нет в данный момент (отложенные последствия).
  • Какие новые проблемы создаём.

«Микросервисы или монолит?»

Не «микросервисы лучше». А:

МонолитМикросервисы
Latency между модулямиns (in-process call)ms (network)
DeployОдна сборкаIndependent deploys
Team scale1 командаМного команд
ТестированиеE2E прощеСложнее (contracts)
Operations1 сервисN сервисов (мониторинг ×N)
ЭволюцияCoupledDecoupled

Trade-off matrix — основной инструмент.

Jeff Bezos: бывают two-way doors (вернуться можно) и one-way doors (нельзя).

  • Two-way: попробовать новый CI-tool. Не понравилось → откатились.
  • One-way: миграция БД с PG на Mongo на проде. Очень сложно откатить.

Правило: для two-way — быстрые решения, эксперимент. Для one-way — обстоятельный анализ, RFC, ADR.


Consistency vs Availability (CAP)

  • Strong consistency (RDBMS, single leader): atomic, но потенциальный downtime при сбое.
  • Eventual consistency (Cassandra, DynamoDB): high availability, но реплики могут расходиться.

Latency vs Throughput

  • Batch processing: high throughput, high latency.
  • Real-time: low latency, нижний throughput per node.

Memory vs CPU

  • Кеширование: меньше CPU (no recompute), больше памяти.
  • Streaming compute: меньше памяти, больше CPU.

Simplicity vs Flexibility

  • Hardcoded config: просто, но негибко.
  • Plugin architecture: гибко, но overhead.

Build vs Buy

  • Build: control, но time и maintenance.
  • Buy: быстро, но lock-in и стоимость.

Now vs Later

  • Quick fix: решает сейчас, накапливает debt.
  • Proper solution: дольше, чище.

Cost vs Quality vs Speed Iron triangle: можно выбрать 2.

Performance vs Maintainability

  • Hand-optimized assembly: быстро, никто не поймёт.
  • Idiomatic code: медленнее, но maintainable.

DRY vs Coupling

  • Слишком DRY: одна абстракция для двух подобных вещей → coupling, изменение «А» ломает «Б».
  • Some duplication: ок, если это decoupled.

Generality vs Concreteness

  • Generic framework: «всё умеет», но сложно использовать.
  • Specific tool: одно дело, но хорошо.

Local optimization vs Global

  • Делаем 1 сервис быстрее на 10% → но это не bottleneck системы. Бесполезная работа.

Когда сравниваешь варианты, делай матрицу:

КритерийВесОпция AОпция BОпция C
Latency5864
Cost3597
Operational complexity4479
Team familiarity4953
Score117122103

Score = Σ(вес × оценка). Не «правда», но структурирует.

Operational cost:

  • Каждый компонент нужно деплоить, мониторить, патчить.
  • 10 микросервисов = 10× работы DevOps.

Cognitive cost:

  • Новый разработчик неделю учит систему.
  • Сложная архитектура → больше bus factor.

Maintenance cost:

  • 5-летнего проекта на 3 фреймворках сложнее эволюционировать.

Right-size: complexity должна быть соразмерна проблеме.

Простая проблема + простая архитектура = ✅
Простая проблема + сложная архитектура = ❌ Overengineering
Сложная проблема + простая архитектура = ❌ Underengineering
Сложная проблема + сложная архитектура = ✅ (но проверь, что сложность нужна)

При принятии решения — оценить риски:

Что может пойти не так?

  • Data loss?
  • Downtime?
  • Performance regression?
  • Security breach?
  • Cost explosion?

Какова вероятность × impact?

Risk matrix:
Low impact High impact
High prob: Watch Mitigate now
Low prob: Accept Plan for it

gRPC vs REST

gRPCREST/JSON
PerformanceВысоко (HTTP/2, binary)Ниже
StreamingBuilt-inСложно (WebSockets, SSE)
EcosystemМеньше toolsОгромный
BrowserНужен grpc-webНативно
DebuggabilityСложнее (binary)Просто (curl)
PolyglotХорошоОтлично

Use case:

  • Внутренние сервисы → gRPC.
  • Public API, browser clients → REST.

Kafka vs RabbitMQ

KafkaRabbitMQ
ThroughputОчень высокий (млн msg/sec)Средний
LatencyНизкая, но не лучшаяОчень низкая
RetentionДолгая (часы/дни/всегда)Только до consume
ReplayДаНет (без extensions)
RoutingПростой (topics)Сложный (exchanges)
SetupСложно (Zookeeper/KRaft)Просто

Use case:

  • Event streaming, аналитика → Kafka.
  • Task queue, command bus → RabbitMQ.
  • Сейчас: NATS — между ними по сложности и фичам.

SQL vs NoSQL

SQL (Postgres)NoSQL (Mongo/Cassandra)
SchemaStrictFlexible
ACIDПолнаяОпционально
ScaleVertical + read replicasHorizontal
JoinsOKПлохо
Maturity40+ years15 лет

Use case:

  • Любая транзакционная система → SQL.
  • Огромный volume, простые queries → NoSQL.
  • Default — Postgres.

Build vs Buy frameworks

Build ownUse OSSBuy commercial
ControlПолныйСреднийМеньше
CostHigh devLow (free)Subscription
CustomizationИдеальноЧерез PRЧерез limits
MaintenanceНа насСообществоVendor

Right answer: для бизнес-логики — build, для infrastructure — use proven OSS, для специфики (data tools, observability) — иногда buy.

В ADR обязательно секция Consequences с + и −. Это и есть trade-offs.

## Decision
Используем Kafka для event bus.
## Consequences
+ Long retention, replay для аналитики.
+ Высокий throughput (важно для CDC).
+ Партиционирование = горизонтальный scale.
− Операционная сложность (KRaft / Zookeeper, partitions).
− Сложнее dead-letter handling, чем в RabbitMQ.
− Сильное обучение команды (3–6 месяцев до уверенности).
## Why not RabbitMQ?
- Нет встроенного retention.
- Streams есть, но молодые.
- Подойдёт для command bus, но мы выбираем event bus.

Event Storming — workshop-техника для быстрого понимания сложного бизнес-domain’а через события.

Метафора: мозговой штурм, где «штурмуем» events на стене.

1. Big Picture — обзорный, для понимания всей системы.

  • Целая компания на стене.
  • Stakeholders, продакты, devs.
  • 2–8 часов.

2. Process Level — детально для одного процесса.

  • Например, «процесс checkout».
  • 1 день.

3. Software Design Level — для конкретного сервиса.

  • Aggregates, commands, events.
  • 1 день+.
🟧 Orange — Events (что случилось в системе)
🟦 Blue — Commands (триггеры)
🟨 Yellow — Aggregates (сущности, владеющие state)
🟪 Pink — External systems
🟩 Green — Read models / views (UI screens)
🟥 Red — Hotspots, конфликты, вопросы
👤 Actor — User / role
🟫 Brown — Policies (правила, реакции на events)

Step 1: Chaotic exploration

  • Каждый участник пишет events которые knows happen в системе.
  • Past tense: “Order Placed”, “Payment Received”, “User Registered”.
  • Стикеры на стену — хаотично, без порядка.
  • 30–60 минут.

Step 2: Timeline organization

  • Сортируем стикеры по timeline (слева направо).
  • Дубликаты убираем.
  • Появляются связи и пробелы.

Step 3: Identify pivotal events

  • Какие events — ключевые поворотные?
  • Между ними — bounded contexts.

Step 4: Adding commands and actors

  • Что вызвало event? (command + actor).
  • “User → [Place Order command] → Order Placed (event)”.

Step 5: Adding policies

  • Между events: что в ответ автоматически происходит?
  • “Order Placed → policy ‘send confirmation email’ → Email Sent”.

Step 6: Identify aggregates

  • Какие сущности владеют state?
  • Order, User, Payment, etc.

Step 7: Bounded contexts

  • Где границы responsibility?
  • “Sales context”, “Fulfillment context”, “Billing context”.
Time →
[Actor: Customer] → 🟦 Place Order
🟨 Order
🟧 Order Placed
🟫 Policy: notify warehouse
🟧 Warehouse Notified
🟨 Shipment ← 🟦 Pick Items (worker)
🟧 Shipment Created
🟧 Order Shipped
🟫 Policy: charge customer
🟧 Payment Charged
🟪 External: Stripe

Из этого:

  • Bounded contexts: Sales, Warehouse, Billing, Notifications.
  • Aggregates: Order, Shipment, Payment.
  1. Shared understanding — все участники видят полную картину.
  2. Bounded contexts — границы для микросервисов.
  3. Aggregates — основные DDD-сущности.
  4. Subdomain discovery — что core, что supporting, что generic.
  5. Hidden complexity — pink/red стикеры (внешние системы, hotspots).

Physical (in-person):

  • Большая стена / окно.
  • Sticky notes (orange, blue, yellow, etc).
  • Sharpie маркеры.

Remote:

  • Miro — самый популярный.
  • FigJam — Figma’s whiteboard.
  • Mural — enterprise.
  • EventModeling.org — специализированный tool.

✅ Когда полезно:

  • Greenfield проект — понять domain.
  • Understanding legacy — реверс-инжиниринг системы.
  • Onboarding — новые члены быстро вкатываются.
  • Bounded context discovery — для микросервисов.
  • Process improvement — найти bottlenecks.

❌ Когда не очень:

  • CRUD-системы с одним workflow.
  • Просто, что добавить новую кнопку.
  • Команда из 2 человек (можно проще).

Event Storming — практическая техника для Domain-Driven Design:

  • Ubiquitous language — events на стене на языке бизнеса.
  • Bounded contexts — выделяются естественно.
  • Aggregates — yellow стикеры.
  • Domain events — основа event-driven архитектуры.

Adam Dymitruk развил Event Storming в Event Modeling:

  • Более структурированно.
  • Включает UI mockups как часть модели.
  • Slice = one user story end-to-end.

Подход: каждый «слайс» = command + event + read model + screens.


«У сервиса должна быть одна причина изменяться».

❌ Bad:

UserService — auth, profile, billing, notifications

Когда меняется billing → меняется UserService → деплой → риск.

✅ Good:

AuthService, ProfileService, BillingService, NotificationService

Самое простое решение, которое работает, обычно правильное.

«Если можешь решить cron’ом — не пиши Kafka».

Не строить инфраструктуру «на будущее, когда понадобится».

❌ “Лучше сразу сделаем kafka, может пригодится”. ✅ “Сейчас 100 RPS, in-memory queue хватает. Когда упрёмся → мигрируем”.

Контекст: YAGNI про функционал, не про архитектуру. Иногда нужно проектировать на будущее (one-way doors).

Не дублировать знание. Но:

  • Не код, а knowledge.
  • Иногда повторение лучше, чем неудачная абстракция.

Sandi Metz: “Duplication is far cheaper than the wrong abstraction”.

Heroku 2011, стандарт cloud-native:

  1. Codebase — один репо на app.
  2. Dependencies — explicit (go.mod).
  3. Config — в env vars.
  4. Backing services — БД, queue как resources, swappable.
  5. Build, release, run — separate stages.
  6. Processes — stateless.
  7. Port binding — self-contained (Go серверы — нативно).
  8. Concurrency — через processes.
  9. Disposability — fast startup, graceful shutdown.
  10. Dev/prod parity — minimize diff.
  11. Logs — stdout, не файлы.
  12. Admin processes — one-off (migrations).

В Go-сервисах — обычно соблюдаются естественно.

  • Containerized — Docker.
  • Dynamically orchestrated — Kubernetes.
  • Microservices-oriented — independent deploys.
  • Observability — metrics, logs, traces, profiles.
  • Resilience — circuit breakers, retries.

“Organizations design systems that mirror their communication structures.” — Melvin Conway, 1967

Следствия:

  • Если у тебя 3 команды — у тебя 3 сервиса.
  • Если команды плохо общаются — модули с плохими интерфейсами.

Inverse Conway Maneuver: спроектируй организацию под архитектуру, которую хочешь.

Пример:

  • Хочешь микросервисы → команды по domain (Sales, Billing).
  • Хочешь монолит → одна команда.
  • Single Responsibility — service has one purpose.
  • Open-closed — extensions via plugins, не модификация core.
  • Liskov substitution — interface compliance.
  • Interface segregation — узкие интерфейсы.
  • Dependency inversion — depend on abstractions.

В Go SOLID часто переписывают как гексагональную архитектуру / clean architecture.

Перед запуском любой системы — спроси:

  • Что если DB упала на 10 минут?
  • Что если 1 worker завис?
  • Что если внешний API таймаутит?
  • Что если deploy выкатил баг?
  • Что если оператор случайно DROP TABLE?

Chaos Engineering (Netflix) — намеренно ломать production, чтобы найти слабости.

Иногда проще-но-неполно > сложнее-но-полно. Unix vs Lisp Machines.

Урок: перфекционизм убивает прогресс. Ship MVP.


Optimize, что легко измерить, не что важно. Latency p99 vs developer experience.

«Мы уже 6 месяцев вложили в этот подход» — не аргумент продолжать, если он плох.

Иногда vendor lock-in — норма (стоит того). Не строй abstraction поверх RDS, чтобы «можно мигрировать на Aurora» — почти никто не мигрирует.

«А если у нас будет 100M users?» — у тебя 100 users. Не строй для 100M.

«Netflix делает chaos engineering, значит и нам надо». У них SRE команда, у тебя — нет.

Тратишь часы на маловажное (цвет logo), не обсуждаешь важное (security).

Слишком много решений за день → плохие решения. Делегируй мелочи.

Сидят только разработчики → фантазии, не реальный domain. Обязательно продакты, аналитики, поддержка.

Каждый event ≠ Kafka message. Event Storming = модель domain’а, не тех-спецификация.

«DRY обязательно!» — иногда нет. Принципы — guidelines, не правила.


Каждая команда ≤ 8 человек (2 пиццы). Каждая team owns свой service. Conway’s Law применён намеренно.

Случайно убивает production instances. Cmd принят, потому что инфра должна быть resilient. Trade-off: cost (incidents) vs benefit (готовность к real failures).

Squads (10 человек), tribes (squads), guilds (interest groups). Conway’s Law формализован.

Каждый проект начинается с дизайн-документа. Trade-offs обсуждаются formal-у, до кода.

Перед запуском нового сервиса — Event Storming workshop. Cross-functional команда (backend, frontend, продакт). Результат — bounded contexts → микросервисы.

Некоторые продукты Microsoft вернулись к более монолитной структуре после микросервисов. Trade-off перевешивает.

GitHub.com — всё ещё в основном Rails monolith. Они сознательно не дробят, потому что для их случая trade-offs против микросервисов.

8 серверов, monolith на ASP.NET, миллиарды requests. Best example «KISS работает».


  1. Почему «все engineering decisions — trade-offs»?
  2. Что такое two-way vs one-way doors (Bezos)?
  3. Опиши trade-off между Consistency и Availability.
  4. Сравни Kafka и RabbitMQ — когда что?
  5. Что такое decision matrix?
  6. Что такое «cost of complexity»?
  7. Почему «duplication > wrong abstraction»?
  8. Что такое Event Storming? Кто автор?
  9. Перечисли цветовое кодирование стикеров в Event Storming.
  10. Опиши 7 шагов Big Picture Event Storming.
  11. Что получаем после Event Storming?
  12. Чем Event Modeling отличается от Event Storming?
  13. Связь Event Storming и DDD?
  14. Что такое Single Responsibility на уровне сервиса?
  15. Что такое 12-factor app? Перечисли 5 факторов.
  16. Что такое Conway’s Law? Inverse Conway?
  17. Что такое YAGNI? Когда не работает?
  18. Что такое DRY и когда не стоит применять?
  19. Что такое Chaos Engineering?
  20. Что такое cargo cult в архитектуре?
  21. Опиши SOLID для микросервисов.
  22. Какие cloud-native принципы CNCF?
  23. Что такое premature optimization?
  24. Как Документировать trade-offs?
  25. Приведи пример решения, где KISS перевесил «правильный» подход.

  1. Собрать команду (продакт + 2–3 dev) на 2 часа.
  2. На Miro/FigJam — Big Picture Event Storming.
  3. Зафиксировать events, commands, aggregates, контексты.
  4. Сравнить с фактической архитектурой — где gaps?
  1. Выбери задачу (например, «выбрать message queue»).
  2. 3 опции (Kafka, RabbitMQ, NATS).
  3. 5 критериев с весами.
  4. Заполни оценки, посчитай.
  5. Запиши ADR с обоснованием.
  1. Возьми реальное архитектурное решение из проекта.
  2. Напиши ADR — особенно Consequences (+ и −).
  3. Покажи коллеге: всё ли понятно, какие trade-offs?
  1. Возьми сервис.
  2. Brainstorm: 10 способов «как это может сломаться».
  3. Каждый сценарий — что произойдёт?
  4. Какие mitigations добавить?
  1. Найди компонент, который redundant.
  2. Например, отдельный Redis, можно ли заменить in-memory?
  3. Запиши: что выиграем (KISS), что потеряем.
  4. Реши: стоит ли упрощать?

  1. Mark Richards, Neal Ford — Fundamentals of Software Architecture (книга)
  2. Neal Ford — Building Evolutionary Architectures
  3. Alberto Brandolini — Introducing EventStorming (книга): https://www.eventstorming.com/book/
  4. Eric Evans — Domain-Driven Design (книга)
  5. Vaughn Vernon — Implementing Domain-Driven Design
  6. Adam Dymitruk — Event Modeling: https://eventmodeling.org/
  7. Joel Spolsky — Things You Should Never Do: https://www.joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/
  8. Sandi Metz — The Wrong Abstraction: https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction
  9. Heroku — 12-factor: https://12factor.net/
  10. Site Reliability Engineering — Google (книга)
  11. Adrian Cockcroft (Netflix) — talks on chaos engineering, microservices
  12. Werner Vogels (Amazon CTO) blog: https://www.allthingsdistributed.com/
  13. Accelerate — Nicole Forsgren et al.
  14. Conway, Melvin — How Do Committees Invent? (1967)
  15. Sam Newman — Building Microservices (2nd edition)
  16. Spotify Engineering — Squad model
  17. Martin Fowler — bliki: https://martinfowler.com/bliki/
  18. Anthropic, OpenAI engineering blogs (для современных кейсов AI-стека)
  19. Software Architecture: The Hard Parts — Mark Richards, Neal Ford
  20. ThoughtWorks Technology Radar: https://www.thoughtworks.com/radar

Резюме. Tech lead — это не про «знать правильные ответы», а про умение работать в условиях неопределённости и trade-offs. Decision matrix, ADR с consequences, two-way vs one-way doors (Bezos) — рабочие инструменты. Event Storming — workshop-техника для domain discovery и bounded contexts. Принципы (KISS, YAGNI, 12-factor, Conway’s Law) — система координат. Самая частая ошибка middle разработчиков — догматизм и over-engineering. Senior/Tech Lead — это про здравый смысл и контекст, а не про следование «правильным паттернам».