Проблема выбора

Когда решаю новую задачу, часто задаюсь вопросом, а какие паттерны я мог бы применить? Как построить архитектуру?

Нужно ли здесь разделение на слои? Требуется ли отдельный модуль или это просто часть общей логики? Какой уровень абстракции приемлем? Писать ли тесты? Использовать ли DTO, Command, ValueObject, Read Model и Write Model, прочие крутые штуки?

Принцип соразмерности

Со временем я вывел такой принцип:

Архитектура должна быть соразмерна решаемой задаче.

Сложность архитектуры должна соответствовать сложности задачи. Это я называю соразмерностью.

Для простой задачи подойдёт самая простая архитектура, для задачи средней сложности — архитектура средней сложности, а для самых сложных задач придётся продумать хорошую архитектуру, отражающую сложность задачи.

Если ошибаемся

Если выбрать сложную архитектуру для простой задачи, мы тратим время зря. Сначала на разработку, потом ещё и на поддержку.

Со мной периодически бывает, когда я переоценю сложность задачи и делаю сложно там, где можно проще.

Если ловлю себя на этом, то последним шагом, после того как всё уже сделано, упрощаю архитектуру. Выкидываю лишние абстракции и ненужные прослойки. Перемещаю код. Уменьшаю количество классов, интерфейсов, методов.

В среднем упрощение кода занимает не более получаса. Если код чистый и наработан навык рефакторинга, то процесс рефакторинга быстр и безопасен.

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

Перейти на более высокий уровень архитектуры будет несложно, если код содержится в чистоте. Лучше выбрать наиболее простой вариант и перейти на более сложный когда это потребуется.

Примеры

Мы подбираем архитектуру под задачу и это очевидно, но какую именно выбрать?

По какому правилу определить, соответствует ли архитектура решаемой задаче?

Я выбираю такую архитектуру, которая позволяет реализовать задачу наиболее быстро. Минимальной сложности.

1. Автоматизация рутины

Нужно автоматизировать небольшое количество рутинных действий, выполняемых вручную.

Например — деплой на сервер, запуск тестов, сборка билда, обновление SSL-сертификатов.

Реализация: баш скрипт, мейк файл

Уровень архитектуры: линейное выполнение программы.

Запуск команды → результат.

2. Простое приложение

Нам требуется больше гибкости и удобства?

Возможно нам нужно чтобы код обслуживал какие-то внешние сервисы?

Так как приложение простое, оно может быть реализовано быстро, за дни или недели, силами единственного разработчика.

Реализация: консольная утилита или сервис (api) на Node.js

Уровень архитектуры: продедурный код

Подключаю ORM если нужно работать с базой.

Объединяю связанные процедуры в модули. Переиспользую модули.

К этому же уровню архитектуры относятся простейшие сайты, как мой генератор паролей.

3. Типичное приложение

Это коммерческий проект или что-то более серьёзное. Продукт.

Нам скорее всего потребуется вносить много изменений в приложение, развивать его.

Возможно над разработкой будет работать команда в несколько человек.

Реализация: приложение на фреймворке

Уровень архитектуры: ООП, монолит

Использую принципы чистого кода, чистой архитектуры.

По месту использую Read Model, Service Locator, Value Object.

Стараюсь держать контроллеры и прочую обвязку тонкими, а основной код как можно теснее группировать в компоненты.

4. Приложение энтерпрайз

Крупный продукт, разработка которого может выполняться несколькими командами одновременно.

Например, массовый сервис наподобие Яндекс.Еды, нишевая платформа для онлайн-бизнеса как Геткурс или мощное сервисное приложение для бизнеса как AmoCRM.

Реализация: приложение на фреймворке

Уровень архитектуры: DDD, TDD, BDD, модульный монолит

Здесь уже возможны варианты, могут быть и слои, домены DDD и другие хай-левел конструкции. Но основным отличием будут тесты. Тесты требуются для того, чтобы при нарастающей сложности проект не развалился.

Также тесты способствуют грамотному планированию архитектуры. Плохой код сопротивляется тестам. Мы могли бы написать плохой код, но мы не можем его протестировать. Поэтому если есть жёсткое правило тестируемости кода, то тесты просто вынуждают грамотно выстроить архитектуру и написать хороший код.

Разделение на контексты, модули позволяют держать сложность проекта под контролем и обеспечить независимое и одновременное развитие модулей. Одна команда работает над одним модулем, другая над другим и никто никому не мешает.