Comments 48
Ну так не все релизят по 5 раз в день.
И длинные ветки тоже не для всех проблема.
И не все используют микросервисы.
Так я об этом и говорю. Нужно тюнить процессы отдельно под объем работ, которые делают команда/команды, и отдельно под релизный цикл.
Быстрые релизы требуют автоматики и вложений в инфраструктуру, долгие — требуют атомарных коммитов, чтобы не было проблем с подмердживанием и в рабочую ветку, и в старые релизы.
К монорепе и декомпозиции, кажется, рано или поздно приходят почти все (а если не пришли — надо просто подождать лет пять), поэтому подстраховаться будет не лишним, но если не очень хочется, можно жить с этим, держа в уме, что при появлении второго репозитория и потребности в оркестрации параллельных релизов GitFlow сломается и будет дергаться в конвульсиях.
Длинные ветки не проблема, если в команде 3 человека, работающие над разными частями. Если команда начала раздуваться — пора переходить на короткоживущие.
Я же как раз ровно об этом пишу.
Имеем параллельные релизы в гитфлоу, штук 5, и пока он не дёргается.
Команда из 5-7 человек все работают в одной репе, но, естественно, стараются, по возможности, разделять, что не отменяет изменения в одном коде разными людьми.
Вот я, если честно, не понимаю, почему нужно переходить на короткие ветки? Модно? Начиная с какого размера команды?
Риск и разрешаемых, и несовместимых конфликтов прямо пропорционален количеству людей, параллельно работающих над проектом, количеству изменений в рамках каждого коммита/ветки, и времени жизни ветки. Тот же google исследовал этот вопрос.
Да, это так. В этом и смысл TBD в целом — он призывает делать максимально атомарные изменения. Добавил метод, создал класс, удалил класс. Шансы конфликтов резко падают.
Мне больше всего нравится объяснение его в этом выступлении — https://www.youtube.com/watch?v=hIW5ynk8HWc.
Не очень понятно как быть с фиксами, котрые должны пойти в несколько релизов.
Признаюсь, видео не смотрел, можете на пальцах рассказать как быть в TBD, если есть 5 активных релизов, условно 1.0, 2.0, 3.0, 3.1, 3.2, транк 4.0 и баг нашли в 1.0. Как фикс проводят, через транк?
Но я не могу себе представить проекты «в вакууме», фичи в которых которые можно перключать флагами. Во всех реальных проектах, в которых я участвовал, подавляющее большинство фич это смесь кода, данных (бд) и внешних (по отношению к коду) систем. Ну там другие сервисы, другие системы вообще, какие-то интеграции со своими схемами, версиями АПИ и прочего. Как в этой ситуации можно простым флагом отключить фичу? Надо откатывать изменения в бд (не только схемы, но и менять данные), менять схемы работы с интеграциями и т.д. и т.п.
Шансы конфликтов резко падают
Эти шансы резкопадают, да, но резко возрастает микроменджмент мелких задач.
Да и много мелких МР говороит о том, что кто-то должен целыми днями сидеть и ревьювить эту кучу мелких изменений… А разработчики должны по КД затягивать эти изменения к себе в ветки.
В такой волне микрозадач как можно сосредоточится на своей задаче?
Если посмотреть доклады Максима Дорофеева (и не только его), то станет понятно, что режим работы с отвлечением каждые (условно) 30мин на 2мин ревью убивает продуктивность на корню.
Да и много мелких МР говороит о том, что кто-то должен целыми днями сидеть и ревьювить эту кучу мелких изменений… А разработчики должны по КД затягивать эти изменения к себе в ветки.
Объем кода-то тот же. Более того, смотреть отдельные изменения проще, чем одно большое разом. Если у вас отсмотр 10 PR на 100 строк занимает сильно больше времени, чем одного на 1000, значит, у вас хреново построен процесс отсмотра PR-ов.
Ну как сказать, контекст-то теряется.
Плюс надо еще затянуть к себе эти изменения 10 раз, плюс 10 потенциальных разруливаний конфликтов…
Все специалисты говорят в один голос, что возврат фокуса внимания, после отвлечения (его еще называют состоянием потока), происходит в период до 25мин.
Получаем, что до 250 минут из рабочего дня просто выкидываем, на (условно) «вспомнить чем занимался».
P.S. В принципе, после объяснений с доклада как они мержатся «чуть ли не каждые 10 мин», становится понятно, зачем обычным приложениям известных компаний нужны сотни и тысячи разработчиков. С таким подходом вдумчиво писать код просто некогда))
Отсмотр 10 PR на 100 строк отвлечёт меня 10 раз за день, вместо одного.
Вам же ясно и четко сказали:
Если у вас отсмотр 10 PR на 100 строк занимает сильно больше времени, чем одного на 1000, значит, у вас хреново построен процесс отсмотра PR-ов.
Выключайте все нотификации, когда работаете. Раз, или два, в день — разгребаете, отвечаете, делаете CR. Количество отвлечений устанавливается вами. 10 коротких PR по-прежнему понять (и простить) в разы проще, чем одного монстра.
Это проще, если эти 10 PR не взаимосвязаны. Ну запушили новый класс или, ещё "лучше" интерфейс. Вроде разумные имена и аргументы у методов, если вне контекста. А смотришь через несколько дней реальное использование — уже не видишь, что изначально было неразумно. А было бы вместе сразу обратил бы внимание, что, например, метод getUserName(id) в сервисе всегда вызывается как getUserName(this.authService.currentUser.id) и уже неоднозначно должен ли он быть в этом сервисе, может в сущности user, а может в сервисе Auth.
Мир неидеален, и для решения подобных проблем люди придумали технический долг и рефакторинг.
Но в сравнении с монстром на 1000 строк — это все равно цветочки, потому что абстракции, рожденные тысячами строк — человеческий мозг просто не сможет охватить. И остается только синтаксис проверять, увы. Или потратить столько же времени, сколько автор кода, но создание полной картины.
trunk-driven в многих ипостасях на самом деле имеет ответ, как делать — "закоммиттил, создал свой PR, отсмотрел хотя бы один-два, начал делать дальше". Эдакие помидорки, но из кода.
Но мы не гугл, к сожалению, и работаем как есть.
Да, не по фен-шую, но проблем с конфликтами страшных не было, видимо, получается делить работу суб-оптимально.
Ваши изменения должны "течь" только в одну сторону — от разработчиков в прод, из прода в основную рабочую ветку им попадать в действительности не нужно.
Оооочень спорное утверждение
А исправление бага потом back merge'ем подтягиваем в основную ветку. По крайней мере, мы. Не вижу ни единой причины не забирать в основную ветку фикс какого-то бага, оставляя его неисправленным.
ОК, а что вы делаете в такой ситуации?
- во время тестирования перед выкладкой был обнаружен баг
- баг исправлен, но из-за того, что в новый коммит, который пока не должен попасть в продакшн, изменил что-то в пределах багфикса, вы вынуждены писать два разных багфикса для прода и мастер-ветки? пусть даже что-то простое, уровня "удалили ненужный аргумент функции"
- обнаруживается еще один баг, и фикс для него как раз можно влить и в мастер, и в релиз?
Тут два варианта.
Либо у вас быстрые релизы, и вы выкатите на следующий день все изменения и так — и спрашивается, зачем латать первый, а значит, это не этот случай,
Либо у вас долгие релизы, и вы не можете просто так вмерджить релиз обратно в мастер, потому что у вас будет конфликтующий коммит.
Тут есть две стратегии, обе не идеальны, это либо cherry-pick из мастер-ветки в релиз, либо двойной PR в мастер-ветку и в релиз-ветку.
Сначала ведутся на хайп, а потом изобретают велосипеды. Всё давно уже придумано, нужно лишь вылезти из информационного пузыря: https://habr.com/ru/post/346066/
Мнение про превосходство монорепы создано и распространяется людьми, у которых явные проблемы с гигиеной кода. Микросервис не должен ни при каких обстоятельствах прекращать поддержку старых версий интерфейса.
И все. С момента осознания этой бесхитростной максимы — всем участникам процесса становится очевидно, что монорепы придумали не очень умные люди, которые привыкли забивать шурупы утюгом.
Есть и другая проблема, которую решают монорепы. Клиенты микросервисов, которые рассчитывают на новую версию его интерфейса. Как её решать без монорепы или какой-то зонтичной репы согласующей версиии клиента и сервиса?
Не, можно, разбивать реализацию на два этапа: сначала имплементировать микросервис с новой версией API, зарелизить его без единого клиента, а потом начинать имплементировать клиентов. Но эта любая простейшая фича типа нового поля в сущности и форме UI, будет зарелизена за два релизных цикла минимум. Может где-то это и подходит, но на моей практике бизнес этого не поймёт. Отчасти микросервисы и вводятся для того, чтобы проще было параллелить работу над одной фичей.
Даже если не осталось ни одного клиента?
Угу. Вы не можете знать, остались клиенты, или нет. У нас несколько клиентов (физических клиентов) держат коннект полтора года уже, потому что им так удобнее, соответственно часть внутренних клиентов использует старое API. Из-за этого приходится и с hot-upgrade заморачиваться, и никогда ничего не ломать.
Как её решать без монорепы или какой-то зонтичной репы согласующей версиии клиента и сервиса? [...] будет зарелизена за два релизных цикла минимум
Не понимаю, какие тут проблемы, если честно, и как монорепа поможет их решить. Да, придется релизнуть один микросервис перед вторым. Так и с монорепой точно так же. Ведете разработку параллельно, в релизе второго указываете зависимость от первого. Чем это отличается от монорепы-то?
Вы не можете знать, остались клиенты, или нет.
От софта зависит. В определенных кейсах можно узнать остались ли клиенты, в других — нельзя (ну, компьютер у клиента со старой версией выключен — но это ведь проблемы клиента, что он спустя год-два не обновился, хотя это строго рекомендуется).
Ну, и вообще — да, по ходу нужны даже не микро-репы, а НАНО-репы — по репке на каждый чих, а потом точно так же как и с микросервисами — страдать от того, что нужно управлять зависимостями между ними
В определенных кейсах можно узнать остались ли клиенты [...]
Вот у меня 100К соединений. Некоторые используют старую версию. Расскажите, как я узнаю, кто именно (предполагается, что у меня руки растут не из жопы, и я не обрываю соединения клиентов при деплоях).
страдать от того, что нужно управлять зависимостями между ними
Не нужно управлять никакими зависимостями, если уметь писать код по-человечески и уметь в версионность. Просто не вырывайте с корнем поддержку прежних версий, и все.
Вы, видимо, не читаете )
Но версию клиент может передавать внутри http пакета в момент начала сессии.
Не нужно управлять никакими зависимостями, если уметь писать код по-человечески и уметь в версионность. Просто не вырывайте с корнем поддержку прежних версий, и все.
Говорит архитектор (с) Я уж не говорю о том, что иногда приходится отбрасывать легаси — но тут действительно есть варианты (или по крайней мере стоит их рассмотреть) как сделать миграцию юзеров максимально безболезненной. И, да, разнообразие программ в мире не ограничивается только вебом.
С монорепой (или аналогичным подходом) мы можем зарелизить их одновременно, в рамках одного коммита. А с довольно стандартным релизным циклом в две недели релиз клиента будет через две недели после релиза сервиса.
Не.
Я предлагаю просто не ломать старые API. Для этого достаточно простой гигиены: иммутабельности структур данных. Никогда не удаляйте поля, и не изменяйте их назначение. Все. Это гораздо проще, чем кажется.
с довольно стандартным релизным циклом в две недели релиз клиента будет через две недели после релиза сервиса
Почему это? Выкатили сервис, потом выкатили клиент, в рамках одного релиза. Мы так каждый день почти делаем, и ничего.
Никогда не удаляйте поля, и не изменяйте их назначение.
Бизнес-требования могут измениться так, что старые структуры данных не просто бесполезны будут, а непонятно что отображающие. Вот была у нас связь 1:1, было в, например, Order (заказ) поле invoice с типом Invoice, но появилось требование иметь возможность несколько равноправных счетов на один заказ, логично заменить invoice на invoices с типом Invoice[] или даже Set. Как тут иммутабельность обеспечить? Бизнес упорно твердит, что все счета равноправны, нельзя какой-то из них выделить как основной, главный, дефолтный и т. п. чтобы хоть какой-то смысл было оставить поле invoice c разумным значением, не ломающим логику клиентов, не знающих что счетов может быть несколько. Более того, если старый клиент покажет только один счёт при фактическом наличии нескольких, это будет не просто плохой UX, а нарушение требований регулятора с последствиями вплоть до банкротства и уголовной отвественности.
Меняются не только представления о том, каким должен быть API технически, меняются и бизнес-модели, процессы для работы с которыми он предназначен.
Почему это? Выкатили сервис, потом выкатили клиент, в рамках одного релиза. Мы так каждый день почти делаем, и ничего.
Где и как вы фиксируете, что в релизе нужно сначала выкатить сервис 2.0.0, а потом клиент 2.0.0, когда перед релизом работают на проде сервис 1.16.5 и клиент 1.37.6? И даже не то что выкатить, а что вообще клиент после коммита 1466ad4 требует сервиса с коммитом не меньше 8668cb5 ещё без присвоенной semver версии, потому что кроме продакшена есть ещё куча dev и test окружений, куда их надо выкатывать до продакшена
Вот была у нас связь 1:1, было в, например, Order (заказ) поле invoice с типом Invoice, но появилось требование иметь возможность несколько равноправных счетов на один заказ, логично заменить invoice на invoices с типом Invoice[] или даже Set. Как тут иммутабельность обеспечить?
проблема в том, что кому-то из бизнеса может понадобиться отображение старых отчетов, в старом формате (а добавив новые поля — ты все сломаешь по понятным причинам, либо придется писать логику отчетов дважды). И придется там делать условный enum или версионирование Invoice'ов — в общем, как обычно — тяп-ляп не прокатывает, надо думать (
Бизнес-требования могут измениться [...]
Могут, и меняются. Поле инвойс обнулится, но не удалится. Тогда старый API будет продолжать работать, отвечая ошибками на любые запросы. Или еще как. Но эта ситуация не самая частая, и каждый случай нужно рассмотреть отдельно; в общем же код прошлого года обязан не падать на сегодняшней базе.
Где и как вы фиксируете [...]
Если все полностью обратно совместимо, то его можно за день до того выкатить. Но вообще у нас есть пайплайн, который следит за графом зависимостей, я не вдавался в детали, девопсы за что-то же получают зарплату. Ориентироваться по номерам коммитов в любом случае плохая практика, все, что куда-то выкатывается через пайплайны — версионируется. Java и BEAM выкатывается релизами (бинарниками), поэтому оно просто забирает последнюю версию, и все. Монолит выкатывается последним. Если не ломать обратныю совместимость от коммита к коммиту — это работает почти всегда.
Если вдруг настоящий форс-мажор с версионностью — ну руками разруливаем, но в последний раз такое было давно, и тогда еще микросервисов практически не было.
в общем же код прошлого года обязан не падать на сегодняшней базе.
Это если бизнес готов платить за это, собственно подобные требования от бизнеса должны исходить, это функциональные требования. Ещё нигде не встречал таких требований, пока нет внешних потребителей API, а просто одна система, разбитая на компоненты. Потом иногда точечно появляются, когда выясняется, что таки потребители API есть внешние по отношению к системе, пускай и внутренние в рамках компании. И вообще интерфейс системы гораздо шире, чем думали разработчики. Например, оказывается, что аналитики уже год берут данные прямо из базы данных и считают её схему функциональным требованием.
Если все полностью обратно совместимо, то его можно за день до того выкатить.
Я указал, что релизный цикл две недели. Выкатили сервис, пускай полностью обратно совместимый — следующее окно для выкатки клиента через две недели.
Это если бизнес готов платить за это [...]
Бизнес тут вообще ни при чем. Это сильно сэкономит время и упростит жизнь в дальнейшем (разумеется, речь про продукт средней и высокой сложности, не про лендинг на аутсорсе). Бизнес не должен лезть в детали реализации, иначе что-то не так с инфраструктурным взаимодействием и разделением ответственности.
Я указал, что релизный цикл две недели.
Ну если вы зачем-то придумали себе такое правило, то, скажем, подождите две недели. Или нарисуйте граф зависимостей и выкатывайтесь по нему.
Это получается функциональное требование обратная совместимость с внешними клиентами. Это бизнесу решать стоит ли его поддержка времени и денег. Как и почти во всём, что прямо сейчас не надо, но упростит жизнь в дальнейшем.
Не то чтобы придумали, просто ресурсов QA не хватает для более частых релизов, или точнее, для отказа от понятия релиза как чего-то важного, на чём надо отдельно фокусироваться, а не каждый мерж-реквест в основную ветку выкатывать на продакшен.
Мне жаль команду разработки, если бизнес вмешивается на таком низком уровне. Какую библиотеку использовать — у вас тоже бизнес решает?
Весь мой код не поперхнется поработать с данными трехлетней давности — и ничего, я не умер, сроки не сорвал, бизнес не разозлил. Иногда достаточно просто захотеть попробовать — и внезапно окажется, что это не так сложно.
Какую библиотеку использовать — у вас тоже бизнес решает?
ВНЕЗАПНО — да, это ДЕЛО бизнеса, т.к. это часть управления рисками. А то вдруг программисты захотят использовать GPL библиотеку в коммерческом продукте, а потом юристы придут и прижмут.
Например — https://linux.slashdot.org/story/19/03/09/0423243/vmware-touts-dismissal-of-linux-gpl-lawsuit
Да даже если и докажешь свою правоту, то кто вернет время/деньги/нервы!
Ну, окей, положим, что разрабы все такие сознательные-сознательные… Но тогда почему они все еще у вас, а не в FAANG? :-D
программисты захотят использовать GPL библиотеку
Этими вопросами занимается, очевидно, CTO, а не программисты.
почему они все еще у вас, а не в FAANG?
Потому что в третьей декаде XXI века очень странно идти в FAANG на идиотский стек, кучу бюрократии и невнятные перспективы. Если вы думаете, что не зовут — зовут еще как. Только даже обещанная вчетверо бо́льшая зарплата не заставит меня пойти на галеру винтиком, и многих моих коллег тоже. Джун один вот ушел, жалеет.
А СТО — не бизнес? :-) Ну, точно он ближе к бизнесу, чем рядовой программист или даже архитектор :-D
точно он ближе к бизнесу, чем рядовой программист или даже архитектор
Конечно. Но это тут ни при чем.
Главное, что он гораздо ближе к рядовому программисту, чем к бизнесу, и (если это хороший CTO) все подобного плана решения принимает на основе экспертного анализа команды разработки, иными словами — на обратную совместимость API сервисов его даже не придется уговаривать.
На каком таком низком уровне? Задача поддержки обратной совместимости больше чем на 1 версию назад, это не чисто техническая задача, которая почти ничего не стоит бизнесу. Это увеличение сроков разработки каждой фичи в пределе в разы, создание каких-то маппингов на каждую версию API или поддерживание паралельных процессов и т. п.
Есть техники, позволяющие уменьшить стоимость поддержки типа не удалять и не переименовывать поля в API, даже если они уже везде переименованы, но это ухудшает систему по другим метрикам, мешает достигать других целей. Есть проекты, где это может или даже должно быть оправдано, прежде всего где есть полностью внешние клиенты, но даже гиганты индустрии не находят ресурсы на поддержку всех версий API за свою историю. Откуда такие ресурсы у относительно небольшого бизнеса?
Никто не говорил про поддержку; бэкпорты не нужны. Нужно просто не ломать ее, и не накатывать новую схему в /v1
, поверх старой. И все.
Пример с гигантами индустрии не очень релевантен, потому что там шире не только выбор разработчиков, но и спектр сервисов для поддержки.
Кроме того, гиганты не заинтересованы в поддержке внешних API, но все без исключения команды разработки заинтересованы в поддержке внутренних API. Чтобы избежать version hell, с чего, собственно, и началась эта ветка.
Монорепозиторий !== микросервисы.
Вот вообще ни разу не равно.
В моем случае — есть 3 разных фронтэнд-проекта, использующих общую кодовую базу. Они имеют разный жизненный цикл и должны деплоиться по-разному, но при этом больше чем наполовину имеют общий код.
И отлаживать этот общий код в отдельных репозиториях было бы безумием чуть более, чем полностью.
Собираем свой flow для git с нуля