Открыть закрытое: почему vendor lock-in — это архитектурный выбор, а не судьба

← All posts

Каждая зависимость от платформы — это ставка. Ставка на то, что этот вендор окажется правильным выбором через два года — по цене, по возможностям, по надёжности. Большинство ставок стареют плохо.

Стандартный совет — «выбирайте мудро». Берите вендора с лучшей репутацией, крупнейшей экосистемой, самым стабильным API. Добротный совет. И — недостаточный.

Настоящая проблема — связность, не выбор

Vendor lock-in возникает не потому, что выбрали не того вендора. Он возникает потому, что бизнес-логика выучила форму его API. Промпты знают формат сообщений OpenAI. Запросы знают синтаксис фильтров Pinecone. Воркфлоу знают модель активностей Temporal.

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

Контракты как структурное решение

Бизнес-логика никогда не должна ссылаться на конкретные типы вендора. Она должна ссылаться на контракт — интерфейс, описывающий что нужно, а не как это реализовано.

// С привязкой — бизнес-логика знает об OpenAI
import OpenAI from 'openai';
const res = await openai.chat.completions.create({
  model: 'gpt-4o', messages, tools
});
 
// Без привязки — бизнес-логика знает о контракте
import type { ILLM } from '@kb-labs/contracts';
const res = await llm.chat({ messages, model: 'default' });

Слой адаптеров переводит контракт в то, что требует вендор. Меняешь адаптер, а не логику.

Не 2 контракта, а 12

Мы применяем этот паттерн не только к LLM. KB Labs определяет 12 ключевых контрактных интерфейсов по всей поверхности платформы:

Каждый адаптер реализует ровно один контракт. Каждый плагин ссылается только на контракты, никогда на адаптеры. Граница принудительно соблюдается графом зависимостей: плагины зависят от @kb-labs/contracts, никогда от @kb-labs/adapter-*.

Агрегат IPlatformAdapters

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

# Один конфиг, одна замена
adapters:
  llm: '@kb-labs/adapter-openai'      # → меняем на anthropic, local и т.д.
  cache: '@kb-labs/adapter-redis'      # → меняем на in-memory для dev
  storage: '@kb-labs/adapter-fs'       # → меняем на S3 для прода
  vectorStore: '@kb-labs/adapter-qdrant'

Тест, который мы применяем

Каждая новая абстракция проходит один вопрос: может ли разработчик заменить эту зависимость, не трогая бизнес-логику? Если нет — абстракция течёт.

Реальная цена есть. Больше интерфейсов для поддержки. Больше кода адаптеров. Иногда — абстракция, которая не идеально ложится на причудливую API-поверхность вендора.

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

К этой свободе мы и движемся. Не свобода от вендоров — свобода выбирать их по достоинствам, в любое время.