parent
66308fe472
commit
3e4f36f972
@ -0,0 +1,4 @@ |
||||
build: |
||||
yarn export
|
||||
deploy: |
||||
scp -r ./out/* rinsvent@188.225.77.88:/home/rinsvent/dev/gateway_data/static/tbd
|
@ -1,72 +1,435 @@ |
||||
import type { NextPage } from 'next' |
||||
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 className={styles.title}> |
||||
Welcome to <a href="https://nextjs.org">Next.js!</a> |
||||
</h1> |
||||
|
||||
<p className={styles.description}> |
||||
Get started by editing{' '} |
||||
<code className={styles.code}>pages/index.tsx</code> |
||||
</p> |
||||
|
||||
<div className={styles.grid}> |
||||
<a href="https://nextjs.org/docs" className={styles.card}> |
||||
<h2>Documentation →</h2> |
||||
<p>Find in-depth information about Next.js features and API.</p> |
||||
</a> |
||||
|
||||
<a href="https://nextjs.org/learn" className={styles.card}> |
||||
<h2>Learn →</h2> |
||||
<p>Learn about Next.js in an interactive course with quizzes!</p> |
||||
</a> |
||||
|
||||
<a |
||||
href="https://github.com/vercel/next.js/tree/canary/examples" |
||||
className={styles.card} |
||||
> |
||||
<h2>Examples →</h2> |
||||
<p>Discover and deploy boilerplate example Next.js projects.</p> |
||||
</a> |
||||
|
||||
<a |
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app" |
||||
className={styles.card} |
||||
> |
||||
<h2>Deploy →</h2> |
||||
<p> |
||||
Instantly deploy your Next.js site to a public URL with Vercel. |
||||
</p> |
||||
</a> |
||||
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> |
||||
Позволяют легче воспринимать изменения и снижают риск ошибки. |
||||
</li> |
||||
<li> |
||||
<b>Continuous review</b> |
||||
Теперь код не спрятан в отдельной ветке, а доступен всем. |
||||
Правки прилетают небольшие и каждый может их изучить. |
||||
Тем самым будет происходить слежение за развитием продукта и понимание что меняется |
||||
</li> |
||||
<li> |
||||
<b>Автотесты</b> |
||||
</li> |
||||
<li> |
||||
<b>Парное программирование</b> |
||||
Позволяет снизить риск, того что будет допущена ошибка и спроектировать качественное архитектурное решение по задаче |
||||
</li> |
||||
<li> |
||||
<b>Feature flags</b> |
||||
Весь не готовый код должен быть скрыт за флагом |
||||
</li> |
||||
<li> |
||||
Сложные задачи по прежнему можно делать в отдельных ветках |
||||
Важно <b>декомпозировать</b> задачу так, чтобы работы по ней длились не более 2 дней. |
||||
</li> |
||||
<li> |
||||
<b>Частые деплои.</b> |
||||
Уменьшает количество правок которое должно уйти в 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 <b>Rinsvent</b> |
||||
</footer> |
||||
</div> |
||||
</main> |
||||
|
||||
<footer className={styles.footer}> |
||||
<a |
||||
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app" |
||||
target="_blank" |
||||
rel="noopener noreferrer" |
||||
> |
||||
Powered by{' '} |
||||
<span className={styles.logo}> |
||||
<Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} /> |
||||
</span> |
||||
</a> |
||||
</footer> |
||||
</div> |
||||
) |
||||
) |
||||
} |
||||
|
||||
export default Home |
||||
|
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 54 KiB |
Loading…
Reference in new issue