Вопрос проверяет, умеете ли вы проектировать контрактно-ориентированный сервис: согласовать интерфейсы, учесть ограничения интеграций, обеспечить совместимость, надёжность и управляемость.
Начните с описания клиентов и интеграций: кто вызывает сервис, кто поставляет данные, какие протоколы и ограничения. Затем зафиксируйте контракты: схемы запросов/ответов, ошибки, версии, SLA, идемпотентность. Для каждой внешней системы определите стратегию надёжности: таймауты, ретраи с ограничением, circuit breaker и деградацию. Продумайте модель данных и потоков: что синхронно, что асинхронно, где нужен кэш и предрасчёт. В конце заложите наблюдаемость и договоритесь о правилах изменений (versioning, backward compatibility).
Сервис с несколькими интеграциями почти всегда “ломается” не в бизнес-логике, а на границах: контрактах, версиях, таймаутах и несовпадающих ожиданиях разных клиентов.
Сначала составьте список участников и их роли:
Клиенты (web/mobile/внутренние сервисы): какие операции им нужны, какой SLA
Внешние/смежные системы: что отдаём им и что получаем от них
Источник истины: где “правильные данные” для каждой сущности
Результат:
перечень интеграций
набор критичных сценариев end-to-end
Дальше фиксируете контракты так, чтобы они жили годами:
API: эндпоинты/методы, схемы, обязательные/опциональные поля
Ошибки: коды, причины, что должен делать клиент
Версионирование: правила добавления полей без поломки клиентов
Определение: Backward compatibility — изменения, которые не ломают старых клиентов (например, добавили новое поле, но старые клиенты его игнорируют).
Практика:
добавлять поля можно почти всегда
удалять/переименовывать поля — только через версию/миграцию клиентов
контракт должен быть формализован (OpenAPI/Proto), иначе “устные договорённости” разъедутся
Для каждой внешней системы заранее определите поведение при сбоях.
Минимальный набор:
timeout на каждый вызов
retry только для безопасных операций и с ограничениями
circuit breaker (или аналог) чтобы не долбить падающую зависимость
деградация: что возвращаем клиенту, если часть данных недоступна
Пример: ограниченный ретрай с проверкой контекста (коротко, без лишнего):
func retry(ctx context.Context, n int, fn func(context.Context) error) error {
var err error
for i := 0; i < n; i++ {
if ctx.Err() != nil {
return ctx.Err()
}
err = fn(ctx)
if err == nil {
return nil
}
// sleep/jitter можно добавить; опущено ради краткости
}
return err
}
Ключевое решение: что делаем “в запросе”, а что — “в фоне”.
Признаки, что лучше асинхронно:
зависимость медленная или нестабильная
данных много, нужно агрегировать
требования по latency жёсткие
Инструменты:
события/очереди для обновлений и предрасчётов
материализованные представления (готовые ответы/части ответов)
кэширование, если чтения повторяются
Чтобы интеграции не “падали” из-за релиза соседей:
договориться о правилах релизов и версий
контрактные тесты (consumer-driven), хотя бы на ключевые ручки
deprecation policy: срок жизни старой версии/поля
С несколькими системами важно видеть цепочку целиком:
correlation id / request id между сервисами
метрики по каждой зависимости (latency, error rate, timeouts)
трассировка для поиска “узкого места”
Проектирование интеграционного сервиса — это в первую очередь про контракты, совместимость и надёжность на границах: таймауты/ретраи/деградация, плюс чёткие правила версионирования и наблюдаемость end-to-end.