Что такое архитектура (и чем она не является)
«Архитектура - это совокупность решений, которые дорого менять позже.» - Ralph Johnson
Архитектура - это не «какой фреймворк мы использовали»: это деталь реализации. Архитектура - это та форма, которую вы ощущаете, перемещаясь по кодовой базе: как разбиты модули, в каком направлении текут зависимости, какой код что может вызывать.
Хорошая архитектура однозначно подсказывает инженеру, где писать новую функциональность. Плохая архитектура каждый раз превращает этот вопрос в спор.
Цена решений
Настоящая цена архитектурного решения проявляется не в момент его принятия, а по мере роста числа зависящих от него решений. Три меры:
- Время отката - сколько недель нужно, чтобы отменить?
- Поверхность распространения - сколько файлов / модулей зависит от этого решения?
- Когнитивная нагрузка - сколько часов нужно новому инженеру, чтобы это понять?
Прежде чем закреплять решение, спросите: если через три месяца я пойму, что оно ошибочно, насколько болезненным будет откат?
Замысел против реализации
Архитектура - это совокупность решений, которые вы задумали; кодовая база - это то, что на самом деле было построено. Разрыв между ними - это архитектурный дрейф, и он тихо растёт до тех пор, пока изменение, которое должно занимать час, не начнёт занимать неделю.
Задача не в том, чтобы один раз нарисовать идеальную диаграмму. Задача в том, чтобы держать замысел и реализацию достаточно близко, чтобы диаграмма по-прежнему предсказывала поведение кода.
Слоистая (n-уровневая)
Классическое разделение: представление → приложение → предметная область → данные. Каждый слой может вызывать только тот, что под ним. Это самая простая для обучения модель - и самая простая для нарушения: один вызов из UI напрямую в слой данных, и слоистость превращается в вымысел.
Используйте её, когда команда небольшая, а предметная область неглубокая. Её слабость в том, что бизнес-логика склонна утекать вверх в контроллеры и вниз в базу данных.
Гексагональная · Порты и адаптеры
Предметная область находится в центре и ничего не знает о внешнем мире. Всё внешнее - база данных, HTTP, очередь сообщений - общается с ней через порты (интерфейсы), которые реализуют адаптеры.
Отдача - тестируемость и заменяемость: в тестах вы можете подменить Postgres на заглушку в памяти или REST на gRPC, не тронув ни строки кода предметной области.
Clean Architecture
Уточнение гексагональной модели: концентрические кольца, где зависимости всегда направлены внутрь, к сущностям предметной области. Фреймворки, интерфейс и базы данных живут на самом внешнем кольце - это детали, а не ядро.
Она даёт вам предметную область, которая компилируется и тестируется без какой-либо инфраструктуры. Цена - церемониальность: больше интерфейсов, больше маппинга. Оправданно для долгоживущих систем и избыточно для CRUD-панели администратора.
DDD - предметно-ориентированное проектирование
DDD - это не фреймворк, это дисциплина словаря. «Aggregate», «bounded context», «anti-corruption layer» - общий язык, чтобы говорить о задаче.
Когда применять DDD?
- Предметная область сложна - её часто путают с технической сложностью.
- Несколько команд работают над одной и той же предметной областью.
- Бизнес и инженеры говорят на разных языках.
DDD подходит не для каждого проекта. Если для CRUD-ориентированной админ-панели он кажется избыточным - вероятно, так и есть.
Большой ком грязи
Никакой различимой архитектуры: всё зависит от всего, состояние разделяется глобально, и единственный способ понять изменение - запустить его. Большинство систем приходят сюда не по решению, а под давлением тысячи дедлайнов.
Ком грязи не переписывают - его удушают. Оберните шов, вынесите одну возможность за чистый интерфейс, повторите. Карта зависимостей AppArch показывает вам, где эти швы уже есть.
Антипаттерн: модуль-бог
«Модуль-бог» - тот, что вызывается отовсюду и содержит всё подряд. Имена вроде Utils, Common, Shared - тревожные признаки.
# правило, которое это обнаруживает - id: GOD-001 max_fan_in: 25 message: "Module imported by >25 files - split or rename"
Решение: разбейте примерно на три части. Первая группа - реальная инфраструктура (logger, config). Вторая - примитивы предметной области (money, id). Третья - скорее всего, скрытый там отдельный модуль.
Циклические зависимости
Модуль A нуждается в B, а B - в A. Теперь ни один из них нельзя понять, протестировать или развернуть по отдельности, а порядок сборки превращается в бросок монеты. Циклы - самый надёжный признак модуля, к которому никто не хочет прикасаться.
Разорвите цикл, вынеся общее понятие в третий модуль, от которого зависят оба, или инвертировав одно из направлений через интерфейс. AppArch помечает циклы в тот момент, когда они появляются в merge request.
Дырявые абстракции
Абстракция протекает, когда для её правильного использования нужно знать, как она реализована, - репозиторий, бросающий SQL-исключения, кэш, вынуждающий вызывающую сторону обрабатывать устаревание данных. Она обещала скрыть деталь и не скрыла.
Либо запечатайте течь - транслируйте ошибки реализации на границе, - либо откажитесь от абстракции. Дырявая хуже, чем никакая: вы платите за уровень косвенности, но не получаете изоляции.
Архитектурное ревью
Ревью - это не шлагбаум, блокирующий слияния; это разговор о том, приближает изменение систему к её задуманной форме или удаляет от неё. Лучшие ревью чаще спрашивают «что это усложнит потом?», чем «правильно ли это сейчас?».
Держите его лёгким и непрерывным. Короткое ревью на каждый значимый merge request лучше, чем ежеквартальный архитектурный совет, который все обходят стороной.
Записи об архитектурных решениях
ADR фиксирует одно решение: контекст, рассмотренные варианты, выбор и его последствия - абзац-другой, версионируемый рядом с кодом. Через полгода он отвечает на единственный важный вопрос: почему это устроено именно так?
Пишите ADR, когда решение принято, а не после. Заменяйте, а не удаляйте - след того, почему вы передумали, так же ценен, как и текущий ответ.
Крупный рефакторинг
Большой рефакторинг, попадающий в код одним гигантским слиянием, - это переписывание под маской, и его откатывают при первой же поломке. Настоящий рефакторинг - это последовательность мелких, сохраняющих поведение шагов, каждый из которых можно выкатить сам по себе.
Добавьте новый путь рядом со старым, переводите вызывающие стороны постепенно, затем удалите старый путь. Тесты - страховочная верёвка; без них вы не рефакторите, а играете в азартную игру.