Великі зміни (заміна ORM-шару, платіжного провайдера, пошукового рушія, переписування ключового модуля) в окремій гілці Git на тижні - класична пастка: гілка відстає від main, злиття стає болісним, а результат не перевірений у продакшені до самого кінця.
Branch by Abstraction - «гілкування» всередині коду замість гілки в Git:
- ввести абстракцію перед частиною, що змінюється, і перевести на неї всіх клієнтів - поки з єдиною реалізацією, старою:
interface SearchEngine
{
public function search(SearchQuery $query): SearchResults;
}
final class DatabaseSearch implements SearchEngine { /* наявний код */ }
- поступово писати нову реалізацію поруч - у
main, маленькими комітами, з тестами:
final class MeilisearchSearch implements SearchEngine { /* нова */ }
- перемикати клієнтів на нову реалізацію - через конфігурацію чи feature flag, частинами:
$this->app->bind(SearchEngine::class, fn () => Feature::active('new-search')
? app(MeilisearchSearch::class)
: app(DatabaseSearch::class));
- видалити стару реалізацію, а за потреби - і саму абстракцію, якщо вона більше не потрібна.
Переваги:
- код завжди в робочому стані і постійно інтегрується - немає великого злиття;
- зміни потрапляють у продакшен поступово і можуть бути вимкнені миттєво;
- паралельна робота: інша команда продовжує розробку, не чекаючи на завершення рефакторингу;
- перевірка на реальних даних: нову реалізацію можна ввімкнути для частини користувачів чи запускати «в тіні» й порівнювати результати.
Інструменти:
- feature flags - у Laravel - Pennant (
Feature::active()), з поступовим ввімкненням для відсотка користувачів; - контейнер - перемикання реалізацій без змін у клієнтах;
- тести на контракт абстракції - однаковий набір тестів для старої й нової реалізацій.
Що може піти не так:
- абстракція, «зліплена» зі старої реалізації, - нова не вкладається в неї. Абстракцію варто проєктувати від потреб клієнтів, а не від методів старого класу;
- прапорці, які ніхто не прибирає - після завершення переходу їх треба видалити разом зі старим кодом;
- дані: якщо реалізації по-різному зберігають дані, потрібна міграція чи подвійний запис на перехідний період.
Пов'язане: Strangler Fig - та сама ідея на рівні систем і маршрутизації запитів, Branch by Abstraction - на рівні коду всередині застосунку.
Докладніше в документації: Мартін Фаулер: Branch By Abstraction