Почему команда растёт, а фичи выходят медленнее

Wait 5 sec.

Вы меняете сервис проверки паспортов, а вместе с ним приходится переписывать обработку заявки. Условия, по которым заявка проходит дальше, остались прежними, но работы всё равно прибавилось.Почему больше разработчиков не означает меньше работыМы в Centicore вместе с нашим коллегой разбираем, почему с ростом команды разработка может замедляться. Он около пяти лет занимался системной архитектурой крупных систем, а затем перешёл в управление.Например, два корпоративных проекта. В первом работали 50 человек, и команда с трудом завершила работу за год. Во втором людей было втрое меньше, а результат, по его оценке, оказался заметно лучше.Рост команды сам по себе не уменьшает объём изменений, необходимых для каждой фичи. Разберём одну из причин, по которой этот объём растёт: сильную связность модулей внутри сервиса. При сильной связности небольшое изменение затрагивает сразу несколько компонентов. Разработчик меняет нужную логику, затем связанные с ней интеграции, проверяет соседний код и исправляет тесты. Задача растёт, хотя бизнес просил примерно то же, что и раньше. Новый сотрудник получает ту же систему зависимостей. Ему тоже нужно разобраться, что затронет его правка. Дополнительные люди могут взять часть работы, но сам объём изменений от этого не сокращается.Разделение на микросервисы эту проблему само по себе не решает. Даже внутри отдельного сервиса бизнес-правила могут быть тесно связаны с конкретной базой данных или внешним API. Поэтому полезно посмотреть, сколько кода приходится менять из-за одной новой интеграции.Как проверка паспорта затрагивает обработку заявкиВозьмём условную систему, которая проверяет данные клиента перед тем, как передать заявку дальше. В упрощённом примере бизнес-логике нужны два результата: действителен ли паспорт и отсутствует ли человек в нежелательных списках.Допустим, сначала внешний сервис принимает только номер паспорта. Затем команда переходит на другой сервис, которому нужны ещё ФИО и код подразделения. Меняются состав запроса, протокол взаимодействия и формат ответа. При этом условия прохождения заявки остаются прежними. Если вызов провайдера и разбор его ответа находятся прямо в коде обработки заявки, менять придётся этот код. В одном месте оказались две причины для изменений: новые бизнес-правила и новый способ получения данных.Из-за этого задача по замене интеграции затрагивает сценарий целиком. Чтобы ограничить объём правок, нужно отделить получение результата проверки от решения о том, что делать с заявкой.Как отделить бизнес-правила от технических деталейВ чистой и луковой архитектуре бизнес-логика находится в центре, а работа с базами данных, внешними сервисами и пользовательским интерфейсом вынесена наружу. У этих подходов есть различия, но общий для нашего примера принцип один: зависимости исходного кода направлены к бизнес-логике.В модели чистой архитектуры выделяют четыре области:Сущности. Основные бизнес-правила и данные предметной области.Варианты использования. Сценарии приложения, которые организуют работу с сущностями.Адаптеры интерфейсов. Контроллеры, презентеры и шлюзы, которые связывают сценарии с внешним миром и преобразуют данные.Фреймворки и драйверы. Внешние технические средства, включая веб-фреймворк и базу данных.Зависимость здесь означает, что один модуль использует определения другого: например, его интерфейсы или классы. Контроллер может обращаться к сценарию приложения. Сценарий при этом не должен зависеть от конкретного контроллера или реализации доступа к базе.Для внешней операции сценарий использует интерфейс, определённый во внутренней части приложения. Внешний адаптер реализует этот интерфейс. Так сценарий может получить результат проверки паспорта, не обращаясь в своём коде к конкретному провайдеру. При выполнении программы запрос всё равно дойдёт до внешнего сервиса. Правило описывает зависимости кода, а порядок вызовов во время выполнения может быть другим.Поэтому проверить архитектуру можно по конкретному месту: от чего зависит сценарий обработки заявки? Если ему нужен класс клиента определённого API или конкретный модуль доступа к БД, техническая реализация всё ещё влияет на его устройство.Что останется прежним при смене провайдераВернёмся к паспорту. Адаптер получает необходимые данные, собирает запрос к внешнему сервису и преобразует ответ в результат, с которым работает приложение. В нашем примере это те же два признака: паспорт действителен, человек отсутствует в нежелательных списках.При смене провайдера команда пишет новый адаптер. В нём будут другой запрос и другой разбор ответа. Сценарий обработки заявки продолжит получать результат проверки в прежнем виде.Это работает, пока сохраняются бизнес-правила и приложению доступны нужные данные. Если ФИО или код подразделения раньше вообще не собирали, потребуется изменить и получение этих данных. Один адаптер не решит эту часть задачи. А если изменились условия, по которым заявка проходит дальше, правки понадобятся и в бизнес-логике.С базой данных действует тот же принцип. Сценарий обращается к интерфейсу репозитория, а конкретная реализация работает с БД через SQL, ORM или собственный модуль доступа к данным. При переходе с PostgreSQL на Oracle это помогает сохранить код бизнес-правил, если их удалось отделить от особенностей конкретной БД.Практический результат такого разделения виден в составе задачи: при смене внешнего сервиса разработчик меняет интеграцию, а правила обработки заявки остаются прежними, если требования к ним не менялись.Почему на старте получается три дня вместо одногоВ нашем примере простую реализацию можно собрать за день: контроллер принимает запрос, запускает скрипт с прямым SQL, и тот обращается к базе. На вариант с разделением бизнес-логики, интерфейсов и доступа к данным в том же примере уходит три дня.Это условные оценки для объяснения компромисса. Они показывают, откуда берутся дополнительные затраты на старте нового сервиса: команда определяет границы модулей и способы их взаимодействия. Когда структура уже готова, её не приходится заново создавать для каждой следующей задачи.Для короткоживущего MVP, который действительно собираются выбросить, прямой путь может быть оправдан. В системе, которую будут развивать и подключать к новым сервисам, нужно учитывать стоимость следующих изменений.Допустим, после первого релиза меняется интеграция. В одном варианте команда правит адаптер. В другом ей приходится разбираться ещё и с обработкой заявки, потому что детали провайдера встроены в сценарий. Первоначальная экономия времени начинает оборачиваться дополнительной работой.По мере развития системы таких связей может становиться больше. Тогда исправление затрагивает анализ требований, код и тесты сразу нескольких компонентов. Обсуждать архитектуру полезно через этот объём работы: какие изменения ожидаются и где их придётся делать.Как сохранить границы, когда фича нужна вчераНа практике разделение слоёв часто проигрывает срочной задаче. Можно выделить несколько причин: жёсткие дедлайны, меняющиеся требования, нехватку опыта, проблемы с документацией и онбордингом. Под давлением команда выбирает короткий путь, а вернуться к нему позже становится отдельной задачей.В разборе есть три способа, рассказывающие, как с этим работать.Подготовить шаблон сервиса. Заранее задать структуру для сущностей, сценариев и репозиториев, а также интерфейсы между ними. Тогда разработчику будет проще добавить логику в готовую структуру. При этом команде всё равно нужно понимать назначение границ и соблюдать их.Зафиксировать технический долг. Если ради срока пришлось связать сценарий с конкретной интеграцией, описать принятое решение и запланировать рефакторинг. Так временное упрощение останется видимой задачей.Объяснять затраты на конкретных изменениях. Обсудить с бизнесом, что произойдёт при замене сервиса проверки: где потребуется новый запрос, какие части приложения останутся прежними и почему команда тратит время на их разделение сейчас.Последний пункт требует участия тимлида и менеджера. Им нужно связать дополнительные затраты на текущую задачу с понятной будущей работой. В примере с паспортом такой аргумент уже есть: смена провайдера не должна заставлять команду заново разбираться с неизменившимися правилами обработки заявки.С чего начать в существующем проектеНачать можно с зависимостей внутри одного сервиса. Посмотрите, какие сценарии напрямую используют конкретную БД или внешний API и какие правки это уже вызывает. На примере проверки паспорта задача состоит в том, чтобы отделить правила обработки заявки от получения результатов проверки.После такого разделения у следующей замены провайдера должна появиться понятная область изменений. Если бизнес-правила остались прежними, а команде снова приходится переписывать их реализацию, граница между сценарием и интеграцией ещё требует работы.