«Мы должны уметь менять брокер сообщений» — эту фразу произносит каждый архитектор, и почти ни одна кодовая база реально этого не умеет. Причина всегда одна: абстракция потекла. Бизнес-логика узнала форму 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 контрактов на всю платформу
Это не только про шины событий. Каждая системная граница оформлена контрактом:
- ILLM —
chat(),stream(),chatWithTools(). Провайдеры: OpenAI, Anthropic, локальные модели или маршрут через KB Labs Gateway для централизованного управления - ICache — ключ-значение с TTL, сортированные множества, атомарный
setIfNotExists. In-memory для dev, Redis для продакшена - IStorage — чтение/запись/листинг файлов с опциональным стримингом. Локальная ФС, S3 или кастом
- IVectorStore — поиск/upsert/удаление с поддержкой фильтров. Qdrant, Chroma или любая векторная БД
- IEmbeddings — текст в векторы, единично и батчами. OpenAI, Cohere, локальные модели
- ISQLDatabase — запросы/транзакции с параметризованным SQL. SQLite идёт по умолчанию
- ILogger — структурированное логирование с дочерними логгерами. Включён адаптер Pino
Все 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.