Переписывать монолит с нуля — затея интересная, но статистически провальная. Много таких проектов не доживают до продакшена. Есть способ лучше: паттерн Strangler Fig, который позволяет постепенно вытаскивать функционал из монолита, не останавливая бизнес.
Сегодня разберём, как это работает на примере Битрикса.
Что такое Strangler Fig
Мартин Фаулер подсмотрел идею у фикуса-душителя — растения, которое обвивает дерево-хозяина и постепенно его замещает. В архитектуре это означает: не выбрасываем монолит, а строим новую систему вокруг него, откусывая функционал кусочками.
Три этапа:
Transform — выносим часть логики в микросервис
Coexist — старое и новое работают параллельно
Eliminate — убираем дублирующийся код из монолита
Главное преимущество — возможность отката на любом этапе. Что-то по��ло не так? Возвращаем трафик в монолит, разбираемся спокойно.
Подход хорош для Битрикса, где кодовая база часто представляет собой смесь стандартных модулей, кастомных компонентов и интеграций, накопленных за годы.
Прокси-слой: точка принятия решений
Первое, что нужно — прослойка между клиентами и бэкендом. Она решает, куда направить запрос: в монолит или микросервис.
Простейший вариант на nginx:
map $request_uri $backend {
default bitrix_monolith;
~^/api/v2/orders order_service;
~^/api/v2/inventory inventory_service;
~^/api/v2/notifications notification_service;
}
server {
location / {
proxy_pass http://$backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}Добавление нового сервиса — одна строчка в конфиге. Откат — удаление этой строчки. Никакого деплоя кода, изменения применяются за секунды через nginx -s reload.
Можно использовать и другие решения: Envoy, Traefik, Kong — принцип тот же. Выбор зависит от того, что уже используется в инфраструктуре.
Битриксовые вебхуки (/rest/{user_id}/{token}/...) на первых этапах лучше оставить в монолите. Трогать их имеет смысл, когда основная миграция завершена.
Входящие вебхуки: вызываем Битрикс из микросервисов
Битрикс24 предоставляет REST API через входящие вебхуки. Создаем вебхук в админке выбираем нужные права, получаем URL вида https://your.bitrix24.ru/rest/1/abc123xyz/.
Дальше дергаем методы, добавляя их к URL: .../crm.deal.list, .../crm.contact.add и так далее.
Пару нюансов:
Rate limiting. Битрикс ограничивает частоту запросов. На бесплатных тарифах около 2 запросов в секунду, на платных лимиты выше, но всё равно есть. Без троттлинга быстро упрётесь в ошибку QUERY_LIMIT_EXCEEDED.
class BitrixClient:
def __init__(self, webhook_url: str):
self.base_url = webhook_url.rstrip("/")
self._last_request = 0.0
def _throttle(self):
elapsed = time.monotonic() - self._last_request
if elapsed < 0.5:
time.sleep(0.5 - elapsed)
self._last_request = time.monotonic()Retry с экспоненциальным backoff. Даже с троттлингом Битрикс иногда возвращает ошибки при пиковой нагрузке. Повторяем запрос с нарастающей задержкой:
def call(self, method: str, params: dict = None):
url = f"{self.base_url}/{method}"
for attempt in range(3):
self._throttle()
response = httpx.post(url, json=params or {}, timeout=30)
data = response.json()
if data.get("error") == "QUERY_LIMIT_EXCEEDED":
time.sleep(2 ** attempt) # 1s, 2s, 4s
continue
if "error" in data:
raise BitrixAPIError(data["error"], data.get("error_description"))
return data.get("result")
raise BitrixAPIError("RETRY_EXHAUSTED", "Max retries exceeded")Батчинг. Битрикс позволяет отправлять до 50 команд за один HTTP-запрос через метод batch. Обновление 200 сделок займёт 4 запроса вместо 200.
def batch(self, commands: dict) -> dict:
"""
commands = {
"deal1": ("crm.deal.get", {"ID": 1}),
"deal2": ("crm.deal.get", {"ID": 2}),
}
"""
cmd = {alias: f"{method}?{urlencode(params)}"
for alias, (method, params) in commands.items()}
result = self.call("batch", {"halt": 0, "cmd": cmd})
return result.get("result", {})Параметр halt: 0 означает продолжать выполнение даже при ошибке в одной из команд. Если нужно остановиться при первой ошибке — ставим halt: 1.
Исходящие вебхуку
Когда в Битриксе происходит событие (создали сделку, изменили статус, добавили комментарий), он может отправить POST-запрос на указанный URL. Настраивается в админке: выбираешь событие, указываешь endpoint.
Это позволяет микросервисам реагировать на изменения в реальном времени, не опрашивая Битрикс постоянно.
Пвроблемы, которые нужно решить:
Дубликаты. Битрикс иногда отправляет одно событие дважды — при сетевых проблемах, таймаутах, или просто потому что так получилось. Без дедупликации получите двойные записи.
Решение — хранить обработанные события в Redis с TTL:
@app.post("/webhooks/bitrix/deal")
async def handle_deal_webhook(request: Request):
body = await request.body()
payload = parse_webhook(body)
# Дедупликация
event_key = f"bitrix:event:{payload.event}:{payload.data['ID']}"
if await redis.exists(event_key):
return {"status": "duplicate", "message": "Already processed"}
await redis.setex(event_key, 3600, "1") # TTL 1 час
# Обработка по типу события
if payload.event == "ONCRMDEALADD":
await handle_deal_created(payload.data)
elif payload.event == "ONCRMDEALUPDATE":
await handle_deal_updated(payload.data)
return {"status": "ok"}Потери. Если сервис недоступен в момент события — оно потеряно навсегда. Битрикс не повторяет отправку, не ведёт очередь.
Решение — периодическая сверка критичных сущностей. Раз в час (или чаще для важных данных) сравниваем состояние в Битриксе и микросервисе:
async def sync_deals():
last_sync = await redis.get("deals:last_sync") or "2020-01-01"
deals = bitrix.call("crm.deal.list", {
"filter": {">DATE_MODIFY": last_sync},
"select": ["ID", "STAGE_ID", "OPPORTUNITY"]
})
for deal in deals:
await process_deal_update(deal)
await redis.set("deals:last_sync", datetime.now().isoformat())Безопасность. Любой, кто узнает URL вебхука, может отправить фейковое событие. Битрикс подписывает запросы — обязательно проверяйте подпись:
def verify_bitrix_signature(request: Request, body: bytes, secret: str) -> bool:
signature = request.headers.get("X-Bitrix-Signature", "")
expected = hmac.new(
secret.encode(),
body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)Дополнительно можно ограничить IP-адреса, с которых принимаются вебхуки — Битрикс публикует список своих серверов.
Anti-Corruption Layer: изоляция моделей
ACL — прослойка, которая переводит данные между моделями монолита и микросервисов. Без неё код быстро превращается в такую вот кашу из битриксовых полей, разбросанных по всему проекту.
Пример проблемы: в Битриксе статус сделки — строка "PREPARATION", сумма — строка "15000.00", дата — в формате "DD.MM.YYYY". В микросервисе хочется нормальный enum, Decimal и datetime.
Адаптер инкапсулирует все преобразования:
class BitrixDealAdapter:
STATUS_MAP = {
"NEW": OrderStatus.NEW,
"PREPARATION": OrderStatus.PROCESSING,
"PREPAYMENT_INVOICE": OrderStatus.AWAITING_PAYMENT,
"EXECUTING": OrderStatus.IN_DELIVERY,
"WON": OrderStatus.DELIVERED,
"LOSE": OrderStatus.CANCELLED,
}
@classmethod
def to_order(cls, deal: dict) -> Order:
return Order(
id=f"BX-{deal['ID']}",
external_id=deal["ID"],
status=cls.STATUS_MAP.get(deal["STAGE_ID"], OrderStatus.UNKNOWN),
total=Decimal(deal.get("OPPORTUNITY", "0")),
created_at=cls._parse_date(deal.get("DATE_CREATE")),
customer_id=deal.get("CONTACT_ID"),
)
@classmethod
def from_order(cls, order: Order) -> dict:
reverse_map = {v: k for k, v in cls.STATUS_MAP.items()}
return {
"STAGE_ID": reverse_map.get(order.status, "NEW"),
"OPPORTUNITY": str(order.total),
}
@staticmethod
def _parse_date(date_str: str) -> datetime | None:
if not date_str:
return None
return datetime.strptime(date_str, "%d.%m.%Y %H:%M:%S")Вся логика преобразования в одном месте.
Бонусом адаптер легко покрывается юнит-тестами отдельно от интеграционных тестов с реальным Битриксом.
Стратегия миграции
Порядок выноса функционала важен. Начинать с ядра бизнес-логики — рискованно: высокая цена ошибки, много зависимостей.
Рекомендуемый порядок:
Аналитика и отчёты. Только чтение данных через batch-запросы. Падение сервиса аналитики не влияет на основной бизнес — просто отчёты показывают данные часовой давности.
Уведомления. Email, SMS, push-уведомления. Вебхуки Битрикса триггерят сервис нотификаций. Потеря уведомления неприятна, но на самом деле не так критично,заказ-то создан и обрабатывается.
Интеграции с внешними системами. Доставка (СДЭК, Boxberry), платёжные системы, склады. Битрикс сообщает о событии, микросервис взаимодействует с внешним API, результат пишется обратно в Битрикс.
Каталог товаров. Синхронизация, кеширование, поиск. Здесь уж�� двусторонний обмен данными и вопросы консистентности.
Заказы и CRM. Ядро. Браться только когда набита рука на предыдущих этапах и есть уверенность в инфраструктуре.
Каждый этап — отдельный деплой с возможностью отката. Между этапами могут проходить недели или месяцы.
Пару проблем
Два источника правды. Данные есть и в Битриксе, и в микросервисе. Какие актуальные?
Нужно определить «владельца» для каждого типа данных. Например: статусы заказов — микросервис (он знает про доставку), данные клиента — Битрикс (там работают менеджеры).
Таймауты и circuit breaker. Любое решение, вне зависимости от архитектуры, может медленно отвечать при сложных запросах или под нагрузкой.
from circuitbreaker import circuit
@circuit(failure_threshold=5, recovery_timeout=60)
def call_bitrix(method: str, params: dict):
return client.call(method, params)После 5 ошибок подряд circuit breaker размыкается и сразу возвращает ошибку, не дожидаясь таймаута. Через 60 секунд пробует снова.
Логирование и отладка. Когда что-то идёт не так, нужно понимать, что именно. Логируйте все запросы и ответы, всегда и везде.
Идемпотентность. Операции должны быть безопасны для повторного выполнения. Получили дубль вебхука? Повторили запрос после таймаута? Результат должен быть тот же, что и при первом выполнении.
Итак, основные компоненты интеграции:
Прокси-слой для маршрутизации трафика между монолитом и микросервисами
Входящие вебхуки для вызова Битрикса из микросервисов (с троттлингом, retry и батчингом)
Исходящие вебхуки дл�� получения событий из Битрикса (с дедупликацией и верификацией)
Anti-Corruption Layer для изоляции доменных моделей
Начинайте с периферии (аналитика, уведомления), двигайтесь к ядру постепенно. Каждый этап должен быть обратимым.
Не пытайтесь мигрировать всё за месяц!

Если Битрикс24 для вас — не «админка», а платформа, стоит разложить его по слоям. Курс «Разработчик Битрикс24» про практику: развёртывание, события, модули и REST-приложения, бизнес-процессы и оптимизация — чтобы уверенно работать с legacy и кастомизациями без хаоса.
Чтобы узнать больше о формате обучения и познакомиться с преподавателями, приходите на бесплатные демо-уроки:
9 февраля в 20:00. «Бизнес-процессы в Битрикс24, написание своих кастомных действий». Записаться
19 февраля в 20:00. «Битрикс24 + MAX: разработка чат-ботов и автоматизация коммуникаций». Записаться
