В этой заметке описана концепция организации производства ПО «Merge Request как транзакция изменения системы, и где в ней возникает eventual consistency». Имеется положительный опыт применения данной концепции на большом общероссийском проекте со сложным процессом производства ПО. Здесь же, в качестве примера, мы будем рассматривать её на большом абстрактном проекте, который описан ниже.

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

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

Используемые термины и их смыслы

Целостное состояние системы – это состояние системы, в котором все её части приведены в соответствие друг с другом. Например, если реализована некая фича, то одновременно реализованы тесты для нее и обновлена проектная документация.

Промежуточное состояние – не целостное, когда не все части системы приведены в соответствие.

Транзакция – абстрактная концепция, которая определяет, что система эволюционирует из одного целостного состояния в другое целостное состояние, а промежуточные состояния не видны\не существуют. Внимание – в этой заметке мы не говорим о типичных транзакциях RDBMS, так как мы обсуждаем не технический вопрос, а вопрос, как организовать производство ПО.

Основная ветка – это ветка, в которой содержатся только целостные состояния системы и от которой ответвляются релизные ветки. На проекте придают этой ветке смысл «хранилище законченных вещей».

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

Программерская документация – это набор md файлов, которые пишутся программистами для программистов и описывают работу компонентов системы на уровне выше отдельных классов\методов, но ниже архитектурной документации.

Усушка БД – процесс удаления накопленной информации из БД с целью сделать БД меньше по размеру. Оперировать огромными БД в процессе интеграционного тестирования непрактично.

Анонимизация БД – процесс замены секретной информации (персональные данные, данные о платежных картах, номера документов) фиктивной информацией с целью опубликовать БД для интеграционных тестов и команды разработки.

Рассматриваемая абстрактная система

Система, которую мы разрабатываем, состоит из нескольких микросервисов, которые общаются через REST API (хоть это и неважно нам), и опираются на реляционные БД.

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

В проекте используется trunk-based development, в роли trunk применяется основная ветка.

Процесс производства этого проекта находится в хорошем состоянии:

  • в кодовой базе есть юнит-тесты, есть нагрузочные тесты и интеграционные тесты всей системы. Все эти тесты удается запускать в каждом пайплайне сборки (после каждого git push) в отдельном кластере интеграционного тестирования.

  • так как тесты запускаются в пайплайнах сборки, то существует автоматический механизм развертывания копии системы, который на вход получает git commit и адрес кластера, куда надо развернуть систему, а на выходе выдает ответ, что система развернута и успешно запущена. Конкретный механизм неважен: это может быть powershell скрипт, helm chart или даже exe файл, но важно понимать, что такой механизм есть.

  • программисты приступают к работе, когда получают и успешно обсуждают аналитические проработки от команды аналитиков.

  • когда merge request (МР) закончен, проводится его code review и он поступает в команду тестирования, где проводится ручное тестирование и написание интеграционных тестов в отдельном МР. Unit тесты пишут программисты внутри исходного МР.

  • после того, как тестировщики заканчивают свои работы, team lead вливает МР в основную ветку. Когда в основной ветке накапливается «достаточно» изменений, release инженер ответвляет от нее релизную ветку.

  • в репозитории проекта также существует документация, которую ведут программисты для программистов («программерская» документация).

Проблема

Итак, процессы производства в хорошем состоянии, зачем что-то менять? С течением времени было обнаружено, что иногда тестировщики забывают написать интеграционные тесты на фичи, которые уже были реализованы программистами и влиты в основную ветку. Программисты иногда забывают обновить программерскую документацию, в результате чего она устаревает. Происходят другие «рассинхроны», и промежуточные состояния становятся видимы в основной ветке. Это нежелательно, потому что от основной ветки в любом момент могут быть ответвлены релизные ветки. Release-инженер не хочет\не может разбираться, является ли текущее состояние в основной ветке целостным.

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

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

Приведем пример из сферы дорожного строительства. Общество не хочет, чтобы автомобили разгонялись выше разрешенной скорости, однако множество средств (например, сотрудники ГИБДД, камеры фиксации) не решают эту проблему. Они решают другую проблему: наказать того, кто нарушил. Но обществу требуется, чтобы скорость не превышали, так как это создает риск серьезных ДТП, в том числе, с вылетом на тротуар и гибелью пешеходов. Это можно обеспечить правильным проектированием дорог, чтобы дороги не представляли собой прямое широкое шоссе, где можно сильно разогнаться. К примеру, можно расставить «островки» по разным краям дороги в шахматном порядке, так, чтобы дорожное полотно было похоже на волнистую линию. При этом любой, кто ускорится выше лимита, рискует просто разбить свою машину. Скорость движения упадет, но на этой дороге больше никто не погибнет. Таким образом, нарушить скоростной режим становится невозможно.

Процессы производства ПО можно организовать по этой же схеме: если Вы не хотите, чтобы какая-то ошибка происходила, спроектируйте процесс производства так, чтобы ее невозможно было совершить. В данной заметке мы обсуждаем одну из техник, с помощью которой это может быть достигнуто: МР как транзакция.

МР как транзакция…

Как и многое другое в ИТ, идея проста: потребуйте, чтобы все связанные с одной фичей изменения, содержались в одном МР. Иными словами мы вводим контракт: МР содержит все связанные изменения и переводит систему из существующего целостного состояние в новое целостное состояние. Как и любой контракт, наш изолирует одно от другого (например, release инженеров от команды разработки).

Например, Вы добавляете в систему новый способ оплатить Ваши услуги. По этой концепции в одном МР изменяется человеко-машинный интерфейс («фронтенд»), бизнес-логика, миграции на БД, документация и тесты разного рода. Получается большой МР. Конечно, это не значит, что каждый член команды должен разбираться во всем проекте и каждый МР делает исключительно один человек. Вы можете создать пустой МР (feature-ветку) от целевой ветки, а уже от неё разные члены команды ответвляют свои dev-ветки, и в них делают свою часть работы, вливая свои dev-ветки в фиче-ветку по окончании работ. Важно только, чтобы в целевую ветку вся работа (feature-ветка) вливалась одномоментно.

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

Зачем это вообще нужно было нашей команде?

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

  • Мы не хотим, чтобы кто-то в нашей команде ежедневно тратил время и следил за тем, что ничего не забыто – документация обновлена, тесты написаны\актуализированы и т.п.

  • По разным причинам, мы хотим ревертить фичи целиком «одним кликом».

  • На нашем проекте, каждый git push в каждой feature ветке тестируется интеграционно, а это требует развернуть копию системы вместе со всеми её БД. Однако, в разных ветках - разные структуры БД из-за новых миграций. Поэтому в нашем репозитории лежит специальная утилита усушки и анонимизации, которая берет существующий бекап с прода, развертывает его в секретной среде, удаляет лишнее (усушка) и анонимизирует всё секретное, и только после этого БД может использоваться для развертывания копии системы и интеграционного тестирования. Если программист забудет обновить эту утилиту в своем МР, то произойдет ошибка запуска интеграционных тестов из-за несоответствия структуры БД программного коду. Это – затраты времени, которых мы хотим избежать.

Вероятно, на Вашем проекте Вы можете придумать иные причины.

Профиты понятны, но каковы минусы?

  • Замедление процессов. В общем случае, точные оценки степени замедления дать невозможно, это зависит от конкретного проекта и обстоятельств, но некоторое замедление точно будет. Надо понять, что важнее: скорость или полнота.

  • Невозможно внедрить на проектах с большим градусом анархии. Если на проекте всё хаотическое, всё горит, всё надо срочно, то говорить о ценности целостного состояния кода + тестов + документации + скриптов развертывания нет смысла. Сосредоточьтесь на очевидных трудностях, а эту концепцию отложите. В конце концов, если человеку оторвало ногу, ему не лечат кариес.

  • Для получения полного профита от этой концепции, процесс производства должен быть автоматизирован в части контроля (см. ниже). Это – затраты.

  • Польза явно видна только на больших долгоживущих проектах со сложным процессом производства.

Внедряем

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

Первым шагом, надо понять, что именно мы включаем в понятие «целостность». Приведенные выше примеры: разного рода документация, разного рода тесты, вспомогательные утилиты (усушки, анонимизации, другие). На Вашем проекте список может быть другим. И, конечно, необходимо помнить о балансе трудозатрат и получаемого профита.

Таким образом, мы хотим, чтобы МР содержал всё, что мы определили важным. Из этого следует, что требуется применять концепцию монорепозитория, ведь нельзя создать МР сразу в два git репозитория. Это значит, документация, утилиты, тесты и пр. должны содержаться в одном git репозитории.

Из этого следует, что все команда разработки (программисты, тестировщики, техписатели, аналитики) должны уметь работать с git. Это может потребовать переучивания или смены инструментов работы. Это необязательно является минусом, например, если техписатели работают с docx файлами и страдают от file hell, то внедрение нового инструмента, например, на md формате с сохранением истории в git, может оказаться благом само по себе.

Переводите людей на новые рельсы постепенно, уже сейчас Вы можете начинать фиксировать пользу и снижение градуса анархии. После того, как Вы успешно всех перевели, появится возможность делать МР-транзакции. Можно начинать экспериментировать. Нет рецепта, который подойдет всем, внедряйте инновации плавно, помогайте людям адаптироваться и дайте им время.

Вряд ли в наивном виде всё заработает чётко. Вероятно, Вы сможете заметить, что МР-транзакции даже появляются, но, всё равно, люди иногда что-то забывают и концепция не работает на 100%. Это совершенно естественно, что люди что-то забывают, это часть нашей природы. Эту проблему можно решать разными методами.

Простейший: добавьте в шаблон МРа набор checkbox, которые будут выставлять сотрудники по мере реализации. Например, «документация обновлена», «интеграционные тесты написаны», и попросите team lead не вливать МР, где не поставлены все галочки.

Если простое решение не сработает в достато��ной степени за несколько месяцев, усложняйте методом автоматизации. Это важный момент: хорошие процессы не создают дополнительной когнитивной нагрузки на команду, хорошие процессы снимают часть когнитивной нагрузки. Если человек знает, что процесс не даст ему совершить какую-то ошибку, то он работает с бОльшим комфортом.

Рассмотрим несколько примеров типичных трудностей, с которыми сталкивается наивный подход:

  • Team lead иногда забывает проверить галочки и вливать нецелостное? Создайте «бота» (сервис), который в каждом новом МРе открывает thread с этими галочками (вместо внесения их в шаблон МРа) и не разрешает этот thread резолвить, если галочки не поставлены. А любой МР с открытым тредом запретите вливать.

  • Галочку документации ставят, обновив документацию работу, но сделав это плохо? Организуйте code review так, чтобы и документация проходила review на соответствие изменениям кода.

  • Галочку ставят, даже не сделав работы? Это не техническая проблема, разбирайтесь в причинах. Может быть галочка поставлена, так как сотрудника подгонял его начальник ради срочности? А может быть, сотрудник просто не разделяет Ваших ценностей и проводит, таким образом, тихую диверсию? Это не технические проблемы, и их техническими методами решить не получится. Решайте эти проблемы менеджерскими средствами, вплоть до увольнения «диверсантов».

И так далее. Правильная автоматизация подобных вещей приносит очень много пользы на масштабе лет.

…Eventual consistency

В некоторых случаях, в эту концепцию невозможно включить то, что Вы хотели бы.

Например: «наши техписатели работают в отдельном подразделении, у них своё начальство и своё планирование работ. Если мы потребуем, чтобы они обновляли наши МР-транзакции, то они смогут это сделать, но через 2 недели, за которые этот МР устареет и его придется актуализировать-ребейзить».

В данном случае, можно провести параллели с концепций из мира разработки «согласованность в конечном счете». Прямо сейчас мы не можем обеспечить целостность, но дизайн построен так, что целостность будет достигнута когда-то позднее.

Как всегда, идея проста: держать МР неделями ради чего-то абсолютно нецелесообразно. Если делать так, то  вреда будет больше, чем пользы. Если возникают такие непреодолимые трудности, то их надо выносить за пределы этой концепции – в данном случае, вливать МР без правок от техписателей.

Но, если таковое наблюдается, то можно ли ослабить эту проблему? Да, Вы можете внести в МР-транзакцию пункт «создать таску техписателям на актуализацию документации по такой-то фиче» и вливать этот МР, точно зная, что таска создана и не будет потеряна. Если Вы уже сделали «бота» (упомянутого выше), то эту проверку можно автоматизировать.

Организационные барьеры – одна из основных проблем, которые вливают на обсуждаемую концепцию. Если вы можете объединить всех причастных под единым руководством, мы рекомендуем это сделать.

Но организационные барьеры не единственная проблема, которая может сломать концепцию. Может быть, техписатель уже под Вашим началом, но он перегружен и возникает та же пауза в 2 недели, просто потому, что у человека очередь задач. Что делать в этой ситуации? Очевидный выход такой же: создаем на него задачу и МР вливаем без его правок. Но тут важно остановиться и понять, что мы видим реальную проблему в производстве – у нас есть узкое место и вместо лечения симптомов, лечить надо проблему: снижать нагрузку на человека или нанимать еще одного. Но это уже другая тема.

Иными словами, если Вы видите, что МР-транзакции задерживаются дольше, чем следует, это симптом. Вникните в ситуацию, определите корневую проблему, и решите проблему радикально, или, если это невозможно, вынесите этого человека (и его вклад в МР-транзакцию) за пределы концепции, создав на него задачу до влития МР-транзакции.

Что в итоге получилось на нашем проекте

Получилось следующее:

  • программерская документация обновляется в рамках МР-транзакции; «бот» автоматически определяет, что изменился код, на который написана документация и требует обновить документацию, хотя, конечно, не проверяет, что она обновлена по существу. Это проверяют ревьюверы.

  • интеграционные тесты всё-таки были вынесены за пределы концепции из-за перегрузки соответствующей части команды тестирования. На них создается задача с целью реализации интеграционных тестов и МР-транзакция вливается без их вклада. Весьма интересным экспериментом выглядел бы процесс производства, когда, сначала делается человеко-машинный интерфейс без логики, потом пишутся красные интеграционные тесты, и только потом программисты наполняют ветку логикой, делая тесты зелеными. Red-Green-Refactoring как он есть. Но эти эксперименты – дело будущего.

  • вспомогательные утилиты (усушки, анонимизации) всегда актуализируются в МР-транзакции и запуск интеграционного тестирования теперь редко приводит к ошибкам несоответствия схемы БД. Это позволяет более эффективно использовать кластер интеграционного тестирования.

  • release инженер уверен, что состояние системы в основной ветке целостное (за вычетом интеграционных тестов) и он может ее развертывать, релизить документацию и changelog. Теперь не происходит задержек, из-за того, что команда разработки что-то не успела доделать и надо подождать с релизом. Принцип: лучше влить позже, но целостное.

Это всё важные, но детали. С высоты птичьего полета, наш процесс разработки ПО стал более структурирован, и более прозрачен для менеджмента, стало меньше хаоса. По отдельности каждый шаг не заметен и, как будто, не является чем-то невероятным. Однако в совокупности результат многократно окупает затраченные на то усилия. С другой стороны, некоторые неприятные паузы все равно существуют, МРы стали полнее, но вливаются немного медленней. В нашем случае, это того стоило.

Минздрав Минцифры предупреждает: принимайте решения взвешенно!