07 сент. 2025 г.·6 мин. чтения

Карта границ сервисов: остановите непреднамеренную связанность в коде

Создайте карту границ сервисов, чтобы задокументировать домены, владение данными и зависимости — так команды будут вносить изменения без непреднамеренной связности.

Карта границ сервисов: остановите непреднамеренную связанность в коде

Почему непреднамеренная связанность появляется снова и снова

Непреднамеренная связанность — это когда части приложения зависят друг от друга без чьего‑то явного намерения. На практике это проявляется как небольшие изменения, которые превращаются в неожиданные простои, запутанные баг-репорты и долгие релизные дни.

Вы видите это, когда кто‑то трогает одну фичу, а др. три начинают «шататься». Правка в прайсинге блокирует регистрацию. Изменение шаблона письма ломает сброс пароля. Обновление правила оформления заказа делает админ‑панель нестабильной. Ничто в коде не делает такие связи очевидными, поэтому люди узнают о них тяжёлым путём.

Частая причина — общий код, который растёт, потому что так удобно. Одна утилита, одна таблица в БД или один «общий» сервис становятся местом, куда все добавляют «ещё чуть‑чуть». Со временем это место превращается в тугой узел. Дело не в том, что команда неосторожна — кодовая база незаметно обучает всех связывать вещи вместе.

Как правило, ситуация усугубляется, когда в код вносят изменения всё больше людей. Новички не знают скрытых правил и копируют существующие паттерны. Под давлением они выбирают кратчайший путь: переиспользовать функцию, взять данные из уже существующей таблицы, вызвать внутренний endpoint, потому что так работает. Каждое быстрое исправление тянет ещё одну невидимую нить между частями системы.

Неопределённое владение множит проблему. Когда некто не может чётко сказать «этот сервис владеет письмами клиентам» или «этот модуль отвечает за правила прайса», ревью превращаются в гадание. Люди либо утверждают изменения, которые не до конца понимают, либо блокируют релизы на всякий случай. В любом случае доставка становится рискованной.

Пара признаков, что вы живёте с непреднамеренной связанностью:

  • Баги появляются далеко от места, где было внесено изменение
  • Релизы требуют много ручного тестирования «на всякий случай»
  • Команды спорят, где должна жить логика
  • Вы избегаете рефакторов, потому что всё кажется хрупким
  • Исправления требуют нескольких ревью из‑за неясного владения

Что такое карта границ сервисов (простыми словами)

Карта границ сервисов — это простая схема вашей системы, отвечающая на три вопроса: какие домены существуют (большие области ответственности), кто владеет какими данными и кто кого вызывает. Думайте о ней как о помеченном эскизе кодовой базы, а не о стене диаграмм.

Хорошая карта обычно включает основные домены (Accounts, Billing, Projects), какие данные каждый домен владеет (таблицы, документы, ключевые сущности), зависимости между доменами (API вызовы, события, общие библиотеки) и точки интеграции с внешним миром (платежи, электронная почта, хранилище).

Что это не: полная переработка архитектуры или идеальный план микросервисов. Вы можете сделать карту границ для монолита, набора serverless-функций или полуготового прототипа. Цель — ясность, а не «идеальная архитектура».

Реальная ценность — в решениях, которые она позволяет принимать. С картой вы можете ответить на вопросы:

  • Где должна жить новая фича?
  • Кто владеет этими данными?
  • Если мы это изменим, что ещё сломается?
  • Что нам изолировать в первую очередь, чтобы снизить риск?

Так вы останавливаете непреднамеренную связанность: перестаёте допускать, чтобы новая работа пересекала границы незаметно.

Конкретный пример: просят «добавить приглашения в команду». Без карты один PR может затронуть аутентификацию, профили пользователей, отправку писем и проверки биллинга с записями в общей БД. С картой можно удержать изменение в пределах: приглашения относятся к Accounts, письма идут через одну точку интеграции, а правила биллинга остаются в Billing.

Это полезно не только инженерам. Основатели используют карту, чтобы понять, почему «малые изменения» занимают больше времени, чем ожидалось. Агентства, которые принимают запутанную кодовую базу, используют её, чтобы планировать работу, не усугубляя проблемы.

Что собрать перед началом

Карта границ полезна лишь настолько, насколько хороши исходные данные. Перед тем как рисовать коробки и стрелки, соберите базовые факты, чтобы картировать реальное поведение, а не мнения.

1) Определите область (чтобы карта была полезной)

Выберите границу, которая соответствует решениям, которые вам нужно принять дальше. Если цель — остановить появление новой связанности, не всегда нужно покрывать всю систему в первый день.

Типичные области:

  • Один репозиторий
  • Одна продуктовая область
  • Вся система
  • Один рискованный поток (auth, billing, onboarding)

Запишите область в одном предложении. Это сокращает споры.

2) Соберите входные данные, которые показывают, как система реально работает

Ищите все способы, которыми запускается код, и все места, где данные пересекают границу. Сложите вместе:

  • Точки входа: маршруты, endpoints, вебхуки
  • Работу вне запросов: фоновые задачи, очереди, cron, планировщики
  • Внешние интеграции: платежи, почта, аналитика, хранилище, провайдеры auth
  • Хранилища данных: БД, кэши, бакеты файлов (высокоуровневые таблицы и сущности)
  • Текущий «клей»: общие библиотеки, общая конфигурация, общие админ-скрипты

Держите всё на высоком уровне. Вам не нужны все колонки каждой таблицы, достаточно, чтобы было видно владение и точки пересечений.

Формат и ограничение по времени

Выберите формат, который команда будет реально обновлять: фото белой доски, документ с заголовками или простой инструмент для диаграмм.

Ограничьте время, чтобы не растягивать процесс. Хорошая начальная точка — 60–90 минут на сбор входных данных, затем одна встреча‑дозаполнение, чтобы закрыть пробелы.

Найдите домены и реальные границы

Начинайте с того, что пользователи делают, а не с того, как устроен репозиторий. Хорошая карта обычно начинается с возможностей, которые люди описывают простым языком: регистрация, вход, управление биллингом, поиск, отправка сообщений, админ‑функции. Это подсказки о реальных доменах.

Простой тест: если часть системы можно описать одним предложением без технических слов, скорее всего это домен. Если нужно три предложения и диаграмма, возможно, это два домена, которые склеились.

Группируйте по смыслу, а не по папкам

Имена папок часто отражают историю, а не назначение. Вместо этого ищите бизнес‑существительные и глаголы в коде, тикетах и UI‑метках. Затем группируйте компоненты, маршруты, джобы и таблицы по смыслу.

Напишите строгое одно‑предложное назначение для каждого домена:

  • «Accounts отвечает за регистрацию, вход и настройки идентификации.»
  • «Billing управляет планами, счетами, платежами и статусом подписки.»
  • «Messaging обрабатывает беседы, доставку и статус прочтения.»

Если вы начинаете добавлять «и ещё», вы нашли проблему с границей.

Отделяйте общие утилиты от общей бизнес‑логики

Общий форматтер дат — это нормально. Общие «правила подписки», используемые в трёх местах, — это источник связности.

Быстрая проверка при обнаружении общего кода:

  • Чистые хелперы (форматирование, логирование, HTTP‑клиент) можно шарить.
  • Правила (кто может вернуть деньги, что считается активным) должны принадлежать одному домену.
  • Если два домена нуждаются в одном и том же правиле, экспортируйте его через API или событие, а не общий файл.

Отмечайте «горячие точки»: части, которые меняются каждую неделю, ломают тесты или вызывают «малые» PR, затрагивающие много файлов. Эти места обычно означают смешанные ответственности.

Документируйте владение данными, чтобы изменения перестали сталкиваться

Сделайте изменения менее рискованными
Превратите хрупкий прототип в стабильное приложение с проверенными исправлениями и более чёткими правилами владения.

Большинство проблем связанности начинается с простой ошибки: две части системы записывают одни и те же данные. Сначала это кажется быстрее, но каждое изменение превращается в переговоры.

Начните с выбора одного домена (Accounts, Billing, Content) и перечислите данные, которые он владеет. Будьте конкретны. «Данные пользователей» — расплывчато. «Таблица users, таблица password_reset_tokens и файлы profile_images» — достаточно ясно, чтобы люди могли следовать этому.

Потом пропишите одно правило и придерживайтесь его: владелец пишет, все остальные читают через границу. Эта граница может быть API, событием/сообщением или представлением только для чтения, которым владеет хозяин. Технология важна меньше, чем дисциплина.

Простой шаблон для каждого домена:

  • Владеемые данные (таблицы, коллекции, файлы/бакеты)
  • Правило записи (кто может писать и как)
  • Путь чтения (API, событие, представление для отчётов, экспортная задача)
  • Чувствительные поля (PII, токены аутентификации, платёжные данные, секреты)
  • Ясный владелец (название команды или роль)

Общие таблицы — горячий источник связанности. Если вы находите таблицу, которую «все используют» (обычно users, subscriptions, permissions, audit_logs), не принимайте это как норму. Отметьте её как общую, назначьте реального владельца и согласуйте план перехода. Иногда план звучит как «сначала перенаправить записи за API владельца, а таблицу разделить позже». Это само по себе останавливает накопление новой связанности.

Чувствительные данные требуют дополнительного внимания. Если токены auth, хэши паролей или платёжные детали хранятся в нескольких местах, получите баги безопасности и путанные исправления. Один владелец должен контролировать хранение и доступ, а другие домены должны запрашивать нужные данные, не копируя секреты.

Картируйте зависимости и точки интеграции

После того как вы определили домены, следующая проблема связанности обычно прячется в связях между ними. Карта полезна только если показывает, как код действительно общается с другим кодом.

Начните с прямых вызовов: HTTP‑маршруты, внутренние импорты модулей, которые пересекают границу, SDK‑клиенты для платежей или почты и общие библиотеки, которые тихо становятся «бог‑пакетом».

Затем зафиксируйте и косвенные вызовы. Флоу регистрации может поставить в очередь задачу, триггерить webhook и через время запускать планировщик, который обновляет биллинг. Ничего не падает сразу, но зависимость реальна.

Для каждой точки интеграции отметьте:

  • Направление: кто от кого зависит
  • Контракт: схема API, имя события, payload вебхука, таблица/представление‑граница
  • Сокращения: прямое чтение БД, общие переменные окружения, общий auth middleware
  • Тип триггера: синхронный запрос, асинхронная задача, webhook, планировщик
  • Владелец: кто чинит, когда ломается

Контракты — это здоровая связанность. Сокращения — непреднамеренная. «Просто импортируй модель user» или «просто прочитай ту таблицу» работает, пока не прилетит рефактор и всё не сломается.

Наконец, пометьте рискованные цепочки. Ищите фаны‑ауты, где одно действие затрагивает много сервисов и сбои каскадят. Пример: один запрос «Create Project» вызывает auth, пишет в две БД, постит в Slack, запускает onboarding job и шлёт аналитику. Это трудно тестировать и просто сломать.

Как по шагам создать карту

Выведите в продакшн ваш AI-прототип
Есть прототип Lovable, Bolt, v0, Cursor или Replit, который падает в продакшене? Мы можем это исправить.

Карта границ полезна только если остаётся простой. Нарисуйте минимальную схему, которая помогает людям не допускать изменений, тихо связывающих части системы.

Шаг 1: Нарисуйте домены как коробки

Начните с чистой страницы и нарисуйте 4–8 коробок. Назовите каждую так, как люди говорят о бизнесе, а не как организован репозиторий.

Под каждой коробкой напишите одно предложение о том, какой результат она обеспечивает. Например: «Billing создаёт счета и фиксирует платежи.» Если вы не можете сказать это в одном предложении, коробка, вероятно, слишком большая.

Шаг 2: Прикрепите к каждой коробке владеемые данные

Для каждой коробки перечислите данные, которыми она владеет простыми словами и коротко: «Invoices», «Payment methods», «User profiles», «Projects», «API keys».

Владеемые данные означают, что только этот домен может их записывать. Остальные читают их только через согласованный путь (вызов, представление или экспортируемое событие).

Шаг 3: Добавьте стрелки для вызовов и событий

Нарисуйте стрелки между коробками для каждого взаимодействия, которое вы знаете: API‑вызовы, события, фоновые задачи, планировщики — всё, что перемещает данные.

Если вы работаете с AI‑сгенерированным прототипом, не доверяйте структуре папок. Найдите реальные взаимодействия, просканировав внутренние HTTP‑вызовы, прямой доступ к БД из чужих модулей, запланированные задачи и обработчики webhook.

Шаг 4: Пронумеруйте каждую стрелку тем, что обменяется

На каждой стрелке напишите то, что передаётся, а не то, как это закодировано. Хорошие метки: «Create invoice», «Fetch user profile», «Payment succeeded event», «Sync project status». Смысл должен оставаться устойчивым, даже если код меняется.

Шаг 5: Отметьте красные флаги на карте

Отметьте паттерны, которые создают непреднамеренную связанность:

  • Общие записи (два домена обновляют одни и те же данные)
  • Циклические вызовы (A вызывает B, а B вызывает A)
  • Скрытые cron‑задачи, которые обновляют данные «со стороны»
  • Одна коробка, которая знает всё (god service)
  • Копирование данных в нескольких местах без единого источника правды

Шаг 6: Пропишите 3–5 правил границ, которым вы будете следовать

Добавьте короткую заметку рядом с картой с правилами, которые люди запомнят:

  • Только владеющий домен пишет свои данные.
  • Междоменные чтения проходят через определённый API или представление, а не через прямой доступ к БД.
  • События описывают факты («Payment succeeded»), а не команды («Update billing»).
  • Никаких циклических зависимостей.
  • Планировщики живут в домене, на который они влияют, и отображаются на карте.

Пример: превращаем запутанный прототип в чёткие домены

Представьте распространённый прототип: регистрация и вход, страница профиля пользователя, экран биллинга и админ‑панель. Всё работает в демо, но каждое изменение ломает что‑то ещё.

Частая корневая проблема — одна перегруженная таблица users, к которой обращаются все фичи. Код логина пишет в неё. Админка редактирует её напрямую. Billing хранит статус плана там же. Настройки профиля живут там же. Простое «добавить колонку» превращается в рискованную миграцию, затрагивающую четыре части приложения и множество общих хелперов.

Карта границ делает скрытую связанность видимой и заменяет её чётким владением.

Что меняет карта границ

Вы можете сначала оставить ту же базу данных, но перестать считать её общим ведром. Определите несколько доменов и сделайте каждого ответственным за свои данные:

  • Auth владеет учётными данными и сессиями (хэши паролей, OAuth‑связи, refresh tokens).
  • Profiles владеет пользовательскими настройками (display name, preferences, выборы уведомлений).
  • Billing владеет деньгами и правами (customers, invoices, история планов, статус платежей).
  • Admin владеет модерацией и внутренними инструментами (flags, audit_logs, назначения ролей).

Затем сделайте правило явным: другие домены не пишут в таблицы, которыми они не владеют. Если Billing нужно знать «активен ли пользователь?», оно спрашивает Auth или читает явное представление прав, контролируемое Billing.

Замена прямых записей в БД базовым API‑границам

В запутанном прототипе страница биллинга могла бы напрямую обновлять users.plan = 'pro'. После картирования границ Billing вызывает, например, Billing.createSubscription(userId, planId) и обновляет свои записи. Если Auth должен применять ограничения доступа, он запрашивает текущее право через небольшой, определённый интеграционный интерфейс.

Чтобы новое изменение не вернуло связанность, установите ожидания для запросов фич:

  • Добавляйте поля только в домене‑владельце.
  • Если нужны данные из другого домена, добавляйте read API, а не общую запись.
  • Избегайте «ещё одной колонки в users», если Auth действительно этим не владеет.

Частые ошибки, которые поддерживают связанность

Снизьте связанность без перестройки
Предложим минимальные изменения, которые сократят связанность без полного переписывания.

Непреднамеренная связанность обычно не происходит из‑за небрежности. Она возникает, потому что команды выбирают границы, которые кажутся аккуратными на вид, но продолжают выпускать фичи, не проверяя, соответствуют ли границы реальности.

Одна ловушка — считать дерево папок моделью доменов. Многие кодовые базы (особенно сгенерированные AI) группируют файлы по требованиям фреймворка, а не по бизнес‑смыслу. Если ваша карта повторяет текущие папки, вы можете задокументировать бардак вместо того, чтобы найти чистые швы.

Другая ловушка — разделение слишком рано и на мелкие сервисы. Это может казаться прогрессом, но если вы не утряслили владение данными, вы создаёте больше вызовов, больше режимов отказа и больше споров. Сначала настройте границы данных, затем решите, что стоит деплоить отдельно.

Ошибки, которые повторяются часто:

  • Использование имён пакетов как границ, хотя одна фича затрагивает три «домена» на практике
  • Деление на много сервисов до соглашения о владении данными и правилах
  • Разрешение нескольким доменам постоянно писать в одну таблицу
  • Картирование только request‑response API и забывание джобов, cron‑задач, очередей и webhook'ов
  • Отношение к карте как к одноразовому результату воркшопа вместо живого документа

Картирование зависимостей также проваливается, когда игнорирует тихие пути. Пример: страница Billing вызывает payments API, но ночная задача обновляет счета, а webhook помечает платежи как завершённые. Если их нет на карте, люди вносят изменения, которые выглядят безопасно в UI, но ломают учёт утром.

Быстрый чеклист и следующие шаги

Используйте это как проверку интуитивно после черновика карты. Если вы не можете ответить на пункт одним предложением, скорее всего там прячется непреднамеренная связанность.

Быстрый чеклист

  • У каждого набора данных есть один ясный владелец, остальные читают через согласованный интерфейс
  • Нет циклических зависимостей между сервисами или модулями
  • Контракты записаны (даже короткой заметкой): что ожидает вызов, что возвращает, что означают ошибки
  • Точки интеграции видны: очереди, webhook'и, cron‑задачи, общие библиотеки, прямой доступ к БД
  • Влияние изменений предсказуемо: можно назвать, что нужно изменить при смене поля или правила

Простой способ сохранить карту полезной: относитесь к ней как к коду. Когда кто‑то добавляет новую зависимость, новую таблицу или интеграцию, обновляйте карту в том же пул‑реквесте. Если это кажется тяжело, держите обновления лёгкими: скриншот или короткий абзац часто хватает.

Следующие шаги, которые реально уменьшают связанность

Выберите одну цель рефактора по риску, а не по удобству. Лучший первый объект обычно там, где небольшое изменение вызывает простои, уязвимости или повторяющуюся переделку.

  • Выберите наиболее рискованную точку связанности (общие таблицы, логика auth, использующаяся везде, или god service)
  • Пропишите одно правило границы, которое вы будете обеспечивать на следующей неделе (например: «никакой сервис не пишет в таблицы другого сервиса»)
  • Добавьте одну маленькую проверку границы, чтобы поломки проявлялись рано (тест контракта, валидация схемы или простой guard)

Если ваша кодовая база была сгенерирована AI и быстро запуталась, границы на бумаге часто не совпадают с реальным поведением в коде. Если хотите второй взгляд, FixMyMess (fixmymess.ai) предлагает бесплатный аудит кода и может помочь выявить скрытые зависимости, конфликты владения данными и проблемы безопасности перед большим ребилдом.

Часто задаваемые вопросы

Как выглядит непреднамеренная связанность в реальном приложении?

Непреднамеренная связанность — это когда две части приложения зависят друг от друга так, как никто не планировал и не документировал. В итоге «малое» изменение в одной области вызывает баги или сбои в другой, а связь между ними не видна в коде или дизайне.

Почему общий код превращается в очаг связанности?

Общая библиотека становится магнитом для разной логики: каждый добавляет «ещё одну вещь», потому что это уже работает. Со временем такое общое место превращается в узел, от которого зависят многие фичи, и безопасно изменять его становится сложно и медленно.

Как решить, какие «домены» включать в карту?

Начните с того, что пользователи делают и какие результаты система должна обеспечивать, а не с структуры папок. Если область можно описать одним простым предложением без технических терминов, это хороший кандидат в домены; если вы постоянно добавляете «и ещё», скорее всего это два домена, склеенных вместе.

Какого размера должна быть первая карта границ сервисов?

Опишите область одним предложением и держите карту в рамках, которые можно завершить за одну рабочую сессию. Хороший дефолт — один репозиторий или один рискованный поток (auth, биллинг или онбординг), потому что именно там обычно скрываются зависимости, приводящие к проблемам.

Что значит «владение данными» и почему это важно?

Владение данными означает: у домена есть данные, которые он может записывать, а все остальные читают через согласованный интерфейс. Простой дефолт: «владелец пишет; остальные просят» — через внутренний API, событие или представление только для чтения, которым владеет хозяин.

Что делать, если разные части приложения пишут в одну таблицу?

Когда несколько частей приложения пишут в одну и ту же таблицу, каждое изменение превращается в переговоры и миграции становятся опасными. Назначьте одного владельца для этого набора данных и сначала перенаправляйте новые записи через границу этого владельца; позже можно разделить таблицу.

Какие зависимости команды обычно забывают картировать?

Не забывайте о путях без запроса-ответа: фоновые задачи, планировщики, очереди и обработчики webhook. Они часто являются причиной «в интерфейсе всё работает», но данные ломаются позже, потому что эти потоки обходят обычные проверки.

Как отличить здоровую зависимость от рискованного сокращения?

Здоровая зависимость — это понятный контракт вроде «создать счёт» или «платёж выполнен», с известным владельцем и ожидаемыми входом и выходом. Сокращение — это обход границы: прямое чтение чужой БД, импорт внутренних моделей из другого домена и тому подобное — всё это тихо связывает команды.

Какие правила границ стоит написать рядом с картой?

Коротко и применимо: 3–5 правил, которые люди помнят при ревью. Самые полезные по умолчанию: только владеющий домен пишет свои данные, междоменные чтения идут через определённый интерфейс, и нет циклических зависимостей — эти три правила предотвращают большую часть новых связей.

Как не дать карте устареть после первой воркшоп-сессии?

Обновляйте карту при добавлении новой зависимости, таблицы или интеграции — так же, как вы обновляете код, когда меняется поведение. Если кодовая база сгенерирована AI или кажется хрупкой, внешний аудит может быстро выявить скрытые связности и проблемы безопасности; FixMyMess может просмотреть репозиторий и найти конфликты владения и рискованные цепочки зависимостей.