Контрактные адаптеры: меняем Kafka на NATS не трогая бизнес-логику

← All posts

«Мы должны уметь менять брокер сообщений» — эту фразу произносит каждый архитектор, и почти ни одна кодовая база реально этого не умеет. Причина всегда одна: абстракция потекла. Бизнес-логика узнала форму consumer group Kafka, или exchange bindings RabbitMQ, или visibility timeout SQS.

KB Labs строится на тезисе, что эта проблема решаема — не хитрее абстракциями, а жёсткой дисциплиной вокруг того, где проходит граница.

Граница: контракты и адаптеры

Каждая внешняя зависимость в KB Labs доступна через контракт — TypeScript-интерфейс, который описывает возможность, а не реализацию. Адаптер реализует ровно один контракт для ровно одного провайдера.

// Контракт (то, что видит ваш код)
interface IEventBus {
  publish<T>(topic: string, event: T): Promise<void>;
  subscribe<T>(topic: string, handler: (event: T) => Promise<void>): Promise<Unsubscribe>;
}
// Адаптер A: in-memory (dev/тесты)
class InMemoryEventBus implements IEventBus { ... }
// Адаптер B: Redis Pub/Sub (продакшен)
class RedisEventBus implements IEventBus { ... }
// Адаптер C: ваша собственная реализация NATS/Kafka/RabbitMQ
class NatsEventBus implements IEventBus { ... }

12 контрактов на всю платформу

Это не только про шины событий. Каждая системная граница оформлена контрактом:

Все 12 контрактов собираются в PlatformContainer. Плагины получают инфраструктуру через platform.llm, platform.cache, platform.storage — никогда через прямые импорты адаптеров.

Правило зависимостей, которое всё держит

Контракты живут в @kb-labs/contracts (Layer 0). Адаптеры — в adapters/ (Layer 2). Плагины — в plugins/ (Layer 3).

Layer 0: core/ (контракты, типы, рантайм)
Layer 1: sdk/ shared/
Layer 2: adapters/ (реализуют контракты)
Layer 3: plugins/ (потребляют контракты)
 
Правило: зависимости текут строго вниз.
Плагин НИКОГДА не импортирует адаптер.
Адаптер НИКОГДА не импортирует плагин.
Оба импортируют контракты.

Это правило принудительно соблюдается графом зависимостей воркспейса. Если плагин попытается добавить @kb-labs/adapter-redis в свои dependencies — сборка сломается.

Как замена работает на практике

Слой IPlatformGateway добавляет ещё одну возможность: IPC-проксирование. Когда плагин работает в воркер-процессе или контейнере, его вызов platform.cache.get() не идёт в Redis напрямую — он проходит через gateway RPC, который включает контекст выполнения (tenant ID, trace ID, auth token). Хост-процесс разрешает адаптер и выполняет вызов.

Замена провайдера означает изменение конфигурации адаптера на уровне хоста. Воркеры, плагины и воркфлоу никогда об этом не узнают.

Цена и выгода

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

Выгода: любая команда может написать свой Kafka, RabbitMQ или NATS адаптер прямо сегодня. Точка расширения открыта, а не в роадмапе. Это не обещание — это implements IEventBus.