Ruby Backend Engineering: как строят системы, которым доверяют

- -
- 100%
- +
Проблема возникает, когда модель начинает знать о вещах, выходящих за пределы её собственных данных. Модель Application не должна знать о том, как работает очередь уведомлений. Модель Procedure не должна знать, какой формат ответа ожидает JSON API. Модель User не должна знать о деталях платёжной системы.
Рабочая граница, которую я использую: в ActiveRecord-модели живут операции, которые полностью определяются данными самой модели. Области видимости, проверки данных (формат, наличие обязательных полей), атрибуты и вычисляемые свойства (full_name = first_name + last_name), простые ассоциации. Всё остальное — в service objects, domain objects или другой структуре за пределами модели.
Конкретные сигналы о нарушении этой границы: модель имеет инициализацию зависимостей (например, инициализацию HTTP-клиента). Модель вызывает другую модель не через ассоциацию. Метод модели принимает параметры, которые имеют смысл только в контексте HTTP-запроса. Метод модели отправляет уведомления или пишет в лог. Как только хотя бы один из этих сигналов появляется — метод нужно выносить.
Один из показательных примеров из практики: в цифровой платформе для тендерных процедур была модель Bid (ставка на торгах). Она содержала метод для проверки, является ли ставка ведущей. Этот метод запрашивал текущую лучшую ставку по процедуре. Казалось бы, логично: ставка знает о своей процедуре через ассоциацию. Но этот метод постепенно оброс дополнительной логикой: учёт ценовых шагов, проверка времени подачи, особые правила для отдельных типов процедур. В итоге получился метод в модели Bid, который неявно зависел от всей бизнес-логики проведения торгов. Рефакторинг показал: эта логика должна жить в BidEvaluationService.
3.6. Как моделировать бизнес-процессы в Rails-приложении
Бизнес-процесс в отличие от отдельной операции имеет продолжительность и состояние. Процесс газификации дома длится месяцами: заявка подана, документы проверены, технические условия выданы, работы выполнены, подключение произведено. Процесс закупки: от публикации до подписания договора. Арбитражный процесс: от подачи иска до решения.
Как это моделировать в Rails? Несколько подходов, которые я использовал.
Первый подход: машина состояний на модели (рассматривалась выше). Хорошо работает для линейных или относительно простых нелинейных жизненных циклов. Проблема: при росте сложности граф состояний становится трудночитаемым.
Второй подход: явный объект процесса, который знает о текущем шаге и умеет переходить к следующему:
Мы использовали именно второй подход — явный объект процесса с записью шагов. Это позволяло при обращении пользователя точно показывать, на каком этапе находится его заявка, сколько дней прошло на каждом этапе и когда ожидается следующий шаг. Пользователи, которые следят за статусом газификации своего дома, ценят такую прозрачность.
Правильно смоделированный бизнес-процесс в коде — это не только техническое решение. Это способ зафиксировать договорённости между разработчиками и бизнесом о том, как работает система. Когда бизнес-аналитик читает код и видит шаги, которые он сам описывал в спецификации — это признак хорошей абстракции.
Монолит не является синонимом технического долга. Это архитектурная форма с реальными преимуществами, особенно для команд среднего размера, работающих над сложной предметной областью. Условие успеха, не размер системы, а наличие чётких доменных границ, явных инвариантов и правильно организованных слоёв ответственности.
Критические правила бизнеса должны защищаться на нескольких уровнях. Жизненный цикл сущностей должен быть смоделирован явно, а не разбросан по условным конструкциям. Транзакции — инструмент защиты, а не деталь реализации. Граница между ActiveRecord и бизнес-логикой требует активного соблюдения, а не пассивного ожидания.
Нетривиальный вывод, к которому я пришёл после лет работы с производственными системами: качество доменной модели напрямую влияет на то, как быстро команда реагирует на изменения в бизнесе. Плохо смоделированный домен означает, что любое изменение требования превращается в рефакторинг нескольких слоёв. Хорошо смоделированный домен означает, что изменение бизнес-правила отражается в коде именно там, где оно логически находится.
Следующая глава посвящена API-first архитектуре — тому, как выстроить контракт между backend и его потребителями так, чтобы он оставался стабильным, задокументированным и предсказуемым при изменениях системы.
ГЛАВА 4. API-FIRST АРХИТЕКТУРА НА RAILS
Когда разработчик говорит «я написал API», он часто имеет в виду набор эндпоинтов, которые возвращают JSON. Это необходимое условие, но не достаточное. API — это прежде всего контракт. Публичное соглашение о том, как выглядят запросы, что означают ответы, как обрабатываются ошибки и что происходит при изменениях.
Именно к этому пониманию я пришёл после того, как мы переводили систему электронных торгов и закупок с монолитной структуры Rails back + Rails front на схему Rails API + Nuxt. Пока API обслуживает только один фронтенд в рамках одной команды — вы можете позволить себе некоторую небрежность: поменяли поле, предупредили коллегу. Но когда API становится интерфейсом между несколькими системами, изменения без версионирования и документации начинают стоить реальных денег и нервов.
Конец ознакомительного фрагмента.
Текст предоставлен ООО «Литрес».
Прочитайте эту книгу целиком, купив полную легальную версию на Литрес.
Безопасно оплатить книгу можно банковской картой Visa, MasterCard, Maestro, со счета мобильного телефона, с платежного терминала, в салоне МТС или Связной, через PayPal, WebMoney, Яндекс.Деньги, QIWI Кошелек, бонусными картами или другим удобным Вам способом.



