You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
messenger/pages/index.tsx

436 lines
30 KiB

import type {NextPage} from 'next'
import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'
import '@fontsource/roboto/300.css';
import '@fontsource/roboto/400.css';
import '@fontsource/roboto/500.css';
import '@fontsource/roboto/700.css';
import {CopyBlock, github} from "react-code-blocks";
import {Table, TableBody, TableRow, TableCell, TableHead} from '@mui/material';
const Home: NextPage = () => {
return (
<div className={styles.container}>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app"/>
<link rel="icon" href="/favicon.ico"/>
</Head>
<main className={styles.main}>
<h1>Trunk-Based Development</h1>
<section>
Модель ветвления в системе контроля версий, в которой разработчики работают над кодом в единственной
ветке под названием trunk. Эта модель позволяет не создавать другие долгоживущие ветки и описывает
технику как именно это делать. Разработчики избегают merge конфликтов при слиянии кода, не ломают
сборку, и живут долго и счастливо
</section>
<section>
<h2>Базовые принципы</h2>
<ul>
<li>Любые коммиты в trunk не должны ломать сборку</li>
<li>Любые коммиты в trunk должны быть маленькими настолько, чтобы review нового кода не занимало
более 10 минут
</li>
<li>Релиз выпускается только на основе trunk</li>
</ul>
</section>
<section>
<h2>GIT flow VS TBD</h2>
<h3>Конфликты</h3>
<Table>
<TableBody>
<TableRow>
<TableCell></TableCell>
<TableCell>GIT</TableCell>
<TableCell>TBD</TableCell>
</TableRow>
<TableRow>
<TableCell>Auto merge</TableCell>
<TableCell>Yes</TableCell>
<TableCell>Yes</TableCell>
</TableRow>
<TableRow>
<TableCell>Manual merge</TableCell>
<TableCell>Yes</TableCell>
<TableCell>Yes</TableCell>
</TableRow>
<TableRow>
<TableCell>Manual merge (интеграция задачи в измененный код)</TableCell>
<TableCell>Yes</TableCell>
<TableCell>No</TableCell>
</TableRow>
</TableBody>
</Table>
</section>
<section>
<h2>Долгоживущие ветки</h2>
Проблемы:
<ul>
<li>Накапливаются конфликты</li>
<li>Код из ветки недоступен для выполнения других задач</li>
<li>Параллельная разработка одного и того же механизма</li>
<li>Рассинхронизация логики кода в разных ветках</li>
<li>Ломается история GIT при конфликтах</li>
<li>Блокируется деплой готовой задачи, если вначале нужно задеплоить другую</li>
<li>Блокировка выполнения второстепенной задачи, если нужен код из другой задачи</li>
</ul>
<p>
При GIT flow долгоживущие ветки - это норма. У нас в проекте были ветки возрастом более года.
Актуализация таких веток занимала несколько дней. А ведь эти ресурсы можно было использовать на разработку новых фич!
</p>
<p>
При TBD если разработка идет непосредственно в master/trunk таких проблем нет - код актуализируется после первого pull запроса.
Разница с local state будет минимальна. В большинстве случаев получим fast forward.
При работе в feature ветке могут возникать проблемы описанные выше. Чтобы минимизировать проблемы, TBD
рекомендует ограничение жизнь ветки в 2 дня.
Это условный интервал, за который не должно накопиться много конфликтов.
Дополнительным положительным фактором является то, что основная часть команды продолжает писать в master, и этот код можно подливать в feature ветку
и тем самым уменьшать количество конфликтов, которые могут появиться в день X. Когда ветка вольется в master.
Важно соблюдать правило 2 дней!
</p>
</section>
<section>
<h2>Разный state БД</h2>
Это порождает ошибки разработки и деплоя.
<ul>
<li>Ошибка генерации миграции. Например, при добавлении новой колонки мы указываем после какой колонки надо добавить новое поле. Но этой колонки может не оказаться на момент деплоя.</li>
<li>Мы не можем просто так взять и добавить колонку с constraint not null так как в других ветках код не будет работать. Это порождает тех. долг, мусорный код и ошибки, которые могут быть спровоцированы не законченным кодом.</li>
<li>Ошибки doctrine. Если добавить новый тип данных doctrine, но код не распространить по всем веткам, то там где его нет, будут ошибки во время калькуляции метаданных.</li>
</ul>
<p>При GIT flow различное состояние бд - это норма</p>
<p>
При следовании TBD эти проблемы исключаются. Изменение структуры бд всегда происходит в мастер.
Разработчикам остается актуализировать ветку и проблема решена.
</p>
</section>
<section>
<h2>Разработка</h2>
<h3>GIT flow</h3>
При GIT flow нужно работать над задачей в отдельной ветке
Задач много, следовательно веток много.
Все ветки различаются. Различается набор сервисов. Различается структура бд.
Для начала разработки нужно переключиться на ветку
Обычно приходится почистить кеш. Применить миграции. Для тестов переинициализировать бд.
Без регламента на мелкие commit`ы, провоцируются ситуации когда вся задача складывается в один commit, что делает историю менее читаемой.
Так же при таком подходе могут накапливаться изменения файлов при разработке.
Когда возникает необходимость переключиться на другую задачу, то нужно спрятать незакомиченные правки (при возвращении к задаче нужно будет снова накатить патч изменений). Этот процесс занимает время и отвлекает от задачи, уменьшает желание уходить с ветки.
<h3>TBD</h3>
При TBD нужно работать мелкими правками. Большую часть времени работаем в мастер.
У всех приблизительно одинаковый набор сервисов, состояние БД.
Небольшие различия в коде, вероятно, даже не повлияют на работоспособность окружения.
Если возникает необходимость переключиться на другую задачу. Нет необходимости уходить в другую ветку и пересобирать окружение. Задачу можно сделать здесь на месте. Закомитить правку и продолжить основную задачу.
</section>
<section>
<h2>Release ready</h2>
<h3>GIT</h3>
Состояние Release ready достигается путем слияния feature ветки в staging и проверке на dev стенде.
По факту мы тестируем измененный код. Отличающийся от master и production.
Что может спровоцировать ряд ошибок.
<h3>TBD</h3>
Состояние Release ready достигается за счет создания release ветки и проверке на dev стенде.
Как и в случае с GIT flow. Мы создаем отдельную ветку и тестируем.
Различия в том что в случае GIT на дев стенде одновременно находится ряд задач в финальной стадии реализации, нуждающиеся в мелких правках.
При TBD на дев стенде будут находиться сразу все задачи принятые в работу в разных состояниях готовности.
В данном случае тестируется ровно тот же код что окажется в production, с теми же feature flags. Это позволяет избежать ряда ошибок и гарантировать что эту ошибку возможно воспроизвести локально, а не искать ветку в рамках которой появилась ошибка.
Есть ряд правил которые минимизируют количество ошибок на дев стенде
<ul>
<li>
<b>Маленькие атомарные комиты.</b>&nbsp;
Позволяют легче воспринимать изменения и снижают риск ошибки.
</li>
<li>
<b>Continuous review</b>&nbsp;
Теперь код не спрятан в отдельной ветке, а доступен всем.
Правки прилетают небольшие и каждый может их изучить.
Тем самым будет происходить слежение за развитием продукта и понимание что меняется
</li>
<li>
<b>Автотесты</b>
</li>
<li>
<b>Парное программирование</b>&nbsp;
Позволяет снизить риск, того что будет допущена ошибка и спроектировать качественное архитектурное решение по задаче
</li>
<li>
<b>Feature flags</b>&nbsp;
Весь не готовый код должен быть скрыт за флагом
</li>
<li>
Сложные задачи по прежнему можно делать в отдельных ветках
Важно <b>декомпозировать</b>&nbsp; задачу так, чтобы работы по ней длились не более 2 дней.
</li>
<li>
<b>Частые деплои.</b>&nbsp;
Уменьшает количество правок которое должно уйти в production. Следовательно и риски что-то поломать.
</li>
</ul>
</section>
<section>
<h2>Схема работы по TBD</h2>
<div className={styles.imageWrapper}>
<Image src={`/trunk.png`} layout={`fill`}/>
</div>
</section>
<section>
<h2>TBD</h2>
<Table>
<TableHead>
<TableRow>
<TableCell>Приемущества</TableCell>
<TableCell>Недостатки</TableCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell>Частые интеграции</TableCell>
<TableCell>Сложность. Нужно понимать SOLID. Увеличивается порог входа в проект</TableCell>
</TableRow>
<TableRow>
<TableCell>Постепенное изменение кода</TableCell>
<TableCell>Нужно делать абстракции вокруг кода который меняется</TableCell>
</TableRow>
<TableRow>
<TableCell>Возможность быстро переключиться на другую задачу</TableCell>
<TableCell>Нужно написать сервисный слой для работы по TBD. Например: как использовать feature flags</TableCell>
</TableRow>
<TableRow>
<TableCell>Всегда актуальный код</TableCell>
<TableCell>Нужно закрывать незаконченный код через флаги, что увеличивает количество кода.</TableCell>
</TableRow>
<TableRow>
<TableCell>Нет конфликтов</TableCell>
<TableCell>Более качественная итоговая архитектура механизма</TableCell>
</TableRow>
<TableRow>
<TableCell>Всегда одна структура бд</TableCell>
<TableCell>Есть вероятность, что в проекте будут оставаться мертвые ветки кода.</TableCell>
</TableRow>
<TableRow>
<TableCell>Уменьшение тех. долга, за счет работы с актуальной версией кода.</TableCell>
<TableCell>Нужно убирать использование флагов когда код ушел в прод</TableCell>
</TableRow>
<TableRow>
<TableCell>Нужно покрывать код тестами, легче вносить изменения.</TableCell>
<TableCell>Нужно покрывать код тестами, чтобы гарантировать работоспособность.</TableCell>
</TableRow>
<TableRow>
<TableCell></TableCell>
<TableCell>Чаще будем встречать push reject</TableCell>
</TableRow>
</TableBody>
</Table>
</section>
<section>
<h2>Branch by Abstraction</h2>
Можно выделить следующие этапы:
<ul>
<li>Выделить интерфейс для заменяемой функциональности.</li>
<li>Заменить прямой вызов реализации в клиенте на обращение к интерфейсу.</li>
<li>Создать новую реализацию, которая реализует интерфейс.</li>
<li>Подменить старую реализацию на новую.</li>
<li>Удалить старую реализацию.</li>
</ul>
</section>
<section>
<h2>Пример, реализации задачи</h2>
<p>Имеем механизм отправки писем</p>
<div className={styles.code}>
<CopyBlock
text={`...
public function send(EmailSender $sender, array $data): void
{
$sender->send($data);
}
...`}
language={`php`}
showLineNumbers={false}
startingLineNumber={1}
theme={github}
/>
</div>
<p>Выделяем интерфейс</p>
<div className={styles.code}>
<CopyBlock
text={`interface SenderInterface {
public function send(array $data): void;
}
`}
language={`php`}
showLineNumbers={false}
startingLineNumber={1}
theme={github}
/>
</div>
<p>Интегрируем его в старый механизм</p>
<div className={styles.code}>
<CopyBlock
text={`EmailSender implements SenderInterface`}
language={`php`}
showLineNumbers={false}
startingLineNumber={1}
theme={github}
/>
</div>
<p>Заменяем прямой вызов на обращение через интерфейс</p>
<div className={styles.code}>
<CopyBlock
text={`public function send(SenderInterface $sender, array $data): void
{
$sender->send($data);
}`}
language={`php`}
showLineNumbers={false}
startingLineNumber={1}
theme={github}
/>
</div>
<p>Делаем новую реализацию</p>
<div className={styles.code}>
<CopyBlock
text={`SmsSender implements SenderInterface`}
language={`php`}
showLineNumbers={false}
startingLineNumber={1}
theme={github}
/>
</div>
<p>Заменяем вызов старого механизма на новый</p>
<div className={styles.code}>
<CopyBlock
text={`
public __construct(EmailSender $emailSender, SmsSender $smsSender) {
$this->emailSender = $emailSender;
$this->smsSender = $smsSender;
}
public function execute() {
feature(FeatureEnum::UseSmsSender)
? $this->send($this->smsSender, [])
: $this->send($this->emailSender, []);
}`}
language={`php`}
showLineNumbers={false}
startingLineNumber={1}
theme={github}
/>
</div>
<p>либо сразу через DI</p>
<div className={styles.code}>
<CopyBlock
text={`
services:
--- App\\Service\\SenderInterface: '@App\\Service\\EmailSender'
+++ App\\Service\\SenderInterface: '@App\\Service\\SmsSender'
`}
language={`yaml`}
showLineNumbers={false}
startingLineNumber={1}
theme={github}
/>
</div>
<p>и в коде будет так</p>
<div className={styles.code}>
<CopyBlock
text={`
public __construct(
private EmailSender $sender
) {
}
public function execute() {
$this->send($this->sender, [])
}`}
language={`php`}
showLineNumbers={false}
startingLineNumber={1}
theme={github}
/>
</div>
</section>
<section>
<h2>Feature Flags</h2>
<p>Суть подхода в том чтобы обернуть код в if с проверкой пред. установленной опции.
Например,</p>
<div className={styles.code}>
<CopyBlock
text={`if (feature(FeatureEnum::UseClickhouse)) {
// код
}`}
language={`php`}
showLineNumbers={false}
startingLineNumber={1}
theme={github}
/>
</div>
<p>или</p>
<div className={styles.code}>
<CopyBlock
text={`feature(FeatureEnum::UseClickhouse)
? // новая логика
: // старая логика
`}
language={`php`}
showLineNumbers={false}
startingLineNumber={1}
theme={github}
/>
</div>
<p>
Все флаги описываются в enum, что позволяет быстро отследить все места в проекте где флаг используется.
Для применения флагов на проекте, было реализовано следующее решение:
</p>
<ul>
<li>Сначала берутся значения из .env файлов</li>
<li>Далее проверяются значения в переменных окружения, например: если явно передать значение в docker</li>
<li>В конце проверяется значение в сессии, что позволяет быстро включить/выключить флаг и проверить функционал.</li>
</ul>
<p>Для удобного управления флагами и мониторинга состояния активности флага, на коленке была собрана служебная страница</p>
<div className={styles.imageWrapper}>
<Image src={`/feature_flags_page.png`} layout={`fill`}/>
</div>
<p>Правил с флагами будет два:</p>
<ul>
<li>После того, как функциональность полностью протестирована и стабильно работает, флаг нужно удалить.</li>
<li>Мест в коде, где идет ветвление по одному и тому же feature флагу, должно быть минимальное количество.</li>
</ul>
</section>
<section>
<h2>Декомпозиция задач и Short-lived ветки </h2>
Все ветки кода, кроме главной, должны иметь короткий срок жизни, максимум несколько дней.
Этого можно добиться за счет мелкой декомпозиции: ветка будет небольшой, если она решает небольшую задачу.
Правильная постановка задач на этапе планирования играет очень важную роль.
Можно, например, придерживаемся принципа декомпозиции задач INVEST.
Декомпозиция по INVEST определяет, каким должен быть пользовательский сценарий (user story):
<ul>
<li>Independent независимый</li>
<li>Negotiable написанный понятным языком</li>
<li>Valuable несущий ценность</li>
<li>Estimable поддающийся оценке</li>
<li>Small компактный, не более 40 часов разработки</li>
<li>Testable тестируемый в широком смысле</li>
</ul>
Каждую из планируемых задач нужно «прогнать» по всем этим пунктам. Если по результатам выпадает хотя бы один из них, задачу нужно декомпозировать заново.
</section>
</main>
<footer className={styles.footer}>
Powered by&nbsp;<b>Rinsvent</b>
</footer>
</div>
)
}
export default Home