Несовместимость версий

Несовместимость версий в экосистеме STOMP.js возникает из-за одновременного изменения нескольких компонентов:

  • API самой библиотеки;
  • поведения WebSocket-клиентов;
  • реализации STOMP-брокеров;
  • изменений в системе модулей Javascript;
  • различий между браузерной и серверной средой;
  • перехода между поколениями STOMP-протокола.

Особенно часто проблемы появляются при миграции:

  • со старого stompjs на @stomp/stompjs;
  • с CommonJS на ES Modules;
  • с SockJS-реализаций на native WebSocket;
  • с STOMP 1.0/1.1 на STOMP 1.2;
  • с устаревших браузерных сборок на современные bundler-системы.

Эволюция библиотеки

Исторически существовало несколько несовместимых реализаций:

Библиотека Статус Особенности
stomp-websocket устарела старый API
stompjs устарела callback-подход
@stomp/stompjs актуальна TypeScript, modern API
@stomp/rx-stomp современная RxJS-интеграция

Старые проекты часто используют:

const client = Stomp.client(url);

Новые версии используют:

import { Client } from '@stomp/stompjs';

const client = new Client({
    brokerURL: 'ws://localhost:15674/ws'
});

Попытка переноса кода без адаптации почти всегда приводит к ошибкам совместимости.


Несовместимость API

Удаление Stomp.client()

Старый API:

const client = Stomp.client(url);

Новый API:

const client = new Client({
    brokerURL: url
});

В старых приложениях часто встречается код:

client.connect(headers, onConnect);

В новых версиях используется:

client.onConn ect = () => {
    console.log('Connected');
};

client.activate();

Метод connect() либо отсутствует, либо работает иначе.


Изменение жизненного цикла соединения

Старый подход

client.connect({}, () => {
    console.log('Connected');
});

Новый подход

client.onConn ect = () => {
    console.log('Connected');
};

client.activate();

Теперь библиотека использует:

  • события;
  • внутренний state machine;
  • автоматическое переподключение;
  • асинхронную активацию.

Старый код, ожидающий синхронного поведения, начинает работать нестабильно.


Несовместимость reconnect-механизмов

В старых версиях реконнект часто реализовывался вручную:

function connect() {
    client.connect({}, connectCallback, errorCallback);
}

В новых версиях встроен параметр:

const client = new Client({
    reconnectDelay: 5000
});

Проблемы возникают, когда:

  • старый reconnect остаётся в коде;
  • новый reconnect включён одновременно;
  • оба механизма создают параллельные подключения.

Типичный результат:

  • дублирование подписок;
  • множественная доставка сообщений;
  • утечки памяти;
  • лавинообразный рост WebSocket-соединений.

Несовместимость heartbeat

В ранних версиях heartbeat иногда отключался по умолчанию.

Современный API:

const client = new Client({
    heartbeatIncoming: 4000,
    heartbeatOutgoing: 4000
});

Старые брокеры могут:

  • не поддерживать heartbeat;
  • некорректно отвечать;
  • разрывать соединение.

Некоторые legacy STOMP-серверы работают только при:

heartbeatIncoming: 0,
heartbeatOutgoing: 0

Различия STOMP 1.0, 1.1 и 1.2

STOMP 1.0

Не поддерживает:

  • heartbeat;
  • ACK-режимы нового типа;
  • улучшенные headers;
  • NACK.

STOMP 1.1

Добавляет:

  • heartbeat;
  • negotiation;
  • улучшенные acknowledgements.

STOMP 1.2

Изменяет:

  • обработку заголовков;
  • escape-последовательности;
  • правила ACK;
  • работу с повторяющимися headers.

Современный STOMP.js ориентирован преимущественно на STOMP 1.2.


Проблемы ACK между версиями

Старые брокеры:

ack: 'client'

Новые реализации могут ожидать:

ack: 'client-individual'

Поведение radically меняется:

Режим Поведение
client ACK пачкой
client-individual ACK по одному сообщению

При несовместимости появляются:

  • повторные доставки;
  • зависшие unacked-сообщения;
  • переполнение очередей;
  • бесконечные redelivery.

Изменения формата сообщений

Некоторые версии STOMP.js автоматически сериализуют JSON:

client.publish({
    destination: '/topic/test',
    body: JSON.stringify(data)
});

Старый код мог использовать:

client.send('/topic/test', {}, data);

Если data — объект:

{
    id: 1
}

то старые версии могут отправить:

[object Object]

Новые версии ожидают строку.


Проблемы CommonJS и ES Modules

Старый импорт

const Stomp = require('stompjs');

Новый импорт

import { Client } from '@stomp/stompjs';

Возможные ошибки:

TypeError: Client is not a constructor

или:

Cannot use import statement outside a module

Причины:

  • несовместимый bundler;
  • неправильный tsconfig;
  • конфликт Babel;
  • смешивание CommonJS и ESM.

Проблемы Webpack и Vite

Старые версии STOMP.js ориентировались на:

  • Webpack 3;
  • global namespace;
  • browser globals.

Современные версии работают через:

  • tree-shaking;
  • ESM;
  • strict exports.

Из-за этого появляются ошибки:

Module not found

или:

process is not defined

Конфликты SockJS и native WebSocket

Старые реализации:

const socket = new SockJS(url);
const client = Stomp.over(socket);

Новые версии:

const client = new Client({
    brokerURL: url
});

Если брокер не поддерживает WebSocket напрямую:

WebSocket connection failed

Некоторые старые инфраструктуры требуют исключительно SockJS.


Изменение Stomp.over()

Ранее:

const client = Stomp.over(socket);

Современные версии могут использовать:

webSocketFactory: () => new SockJS(url)

Пример:

const client = new Client({
    webSocketFactory: () => new SockJS(url)
});

Старый код становится несовместимым без переписывания.


Несовместимость TypeScript-типов

Старые версии:

  • практически не имели типизации;
  • использовали any;
  • не проверяли структуру сообщений.

Современный STOMP.js активно использует TypeScript.

Появляются ошибки:

Property 'body' does not exist

или:

Type 'undefined' is not assignable

Особенно часто проблемы возникают после обновления TypeScript.


Изменение callback-модели

Старые версии:

client.connect(headers, connectCallback, errorCallback);

Новые версии:

client.onConn ect = frame => {};
client.onStompEr ror = frame => {};

Старые обработчики больше не вызываются автоматически.


Несовместимость обработки ошибок

Ранее ошибки часто игнорировались:

client.debug = null;

Теперь библиотека использует:

client.onWebSocketError
client.onStompError
client.onDisconnect

Отсутствие новых обработчиков делает диагностику крайне сложной.


Конфликты версий брокеров

Разные брокеры реализуют STOMP по-разному:

Брокер Особенности
RabbitMQ частичная специфика STOMP
ActiveMQ свои расширения
Apollo устаревшая реализация
HornetQ legacy STOMP
Artemis современная реализация

Некоторые старые серверы:

  • не поддерживают STOMP 1.2;
  • требуют специальных заголовков;
  • работают только с SockJS;
  • некорректно обрабатывают heartbeat.

Проблемы заголовков

STOMP 1.2 требует escaping заголовков.

Например:

header:value\:test

Старые брокеры могут не понимать escaping.

Следствие:

  • потеря заголовков;
  • повреждение metadata;
  • ошибки маршрутизации.

Конфликты кодировок

Некоторые старые версии:

  • использовали Latin-1;
  • не поддерживали UTF-8 корректно;
  • неправильно вычисляли content-length.

Современные версии строже работают с бинарными данными.

Появляются проблемы:

Malformed frame

или:

Invalid UTF-8 sequence

Несовместимость бинарных сообщений

Старый STOMP.js ориентировался в основном на текст.

Новые версии поддерживают:

binaryBody: uint8Array

Но старые брокеры:

  • могут повреждать бинарные payload;
  • ожидать text-frame;
  • ломать content-length.

Изменение debug API

Старый подход:

client.debug = console.log;

Новый подход всё ещё поддерживает debug, но поведение логирования изменилось.

Некоторые старые интеграции:

  • парсят debug-строки;
  • строят monitoring;
  • анализируют transport-level события.

После обновления формат логов может измениться.


Проблемы React-приложений

Старые примеры создавали клиента напрямую:

const client = new Client(...);

В React 18 Strict Mode эффекты могут запускаться дважды.

Результат:

  • двойное подключение;
  • дублирование подписок;
  • множественные reconnect.

Legacy-код особенно уязвим к этой проблеме.


Несовместимость Node.js-версий

Современный STOMP.js использует:

  • современные Promise API;
  • async internals;
  • новые WebSocket-реализации.

Старые версии Node.js:

  • не поддерживают нужные возможности;
  • требуют polyfill;
  • конфликтуют с ESM.

Конфликты browser polyfill

Старые версии могли зависеть от:

global
process
Buffer

Современные bundler-системы больше не добавляют polyfill автоматически.

Особенно часто это проявляется в Vite.


Ошибки миграции

Типичный неудачный перенос:

const client = Stomp.client(url);

client.connect({}, () => {
    client.subscribe('/topic/test', message => {
        console.log(message.body);
    });
});

Попытка обновления:

const client = new Client({
    brokerURL: url
});

client.activate();

client.subscribe('/topic/test', callback);

Ошибка:

subscribe is not a function

Причина — подписка возможна только после onConnect.

Корректный вариант:

client.onConn ect = () => {
    client.subscribe('/topic/test', message => {
        console.log(message.body);
    });
};

client.activate();

Стратегии безопасной миграции

Пошаговое обновление

Опасно:

STOMP.js + Webpack + React + Broker

обновлять одновременно.

Безопаснее:

  1. обновить STOMP.js;
  2. протестировать reconnect;
  3. проверить ACK;
  4. проверить heartbeat;
  5. протестировать подписки;
  6. обновить bundler;
  7. обновить брокер.

Проверка совместимости брокера

Перед обновлением важно проверить:

Возможность Проверка
STOMP 1.2 поддерживается ли
heartbeat работает ли
binary frames поддерживаются ли
SockJS требуется ли
ACK modes какие доступны

Использование адаптерного слоя

При больших legacy-проектах часто создаётся wrapper:

class LegacyStompAdapter {
    connect(headers, callback) {
        this.client.onConn ect = callback;
        this.client.activate();
    }
}

Это позволяет:

  • постепенно переносить код;
  • не переписывать всю систему сразу;
  • изолировать несовместимости.

Проблемы обратной совместимости

Даже при одинаковом API поведение может отличаться:

Поведение Старая версия Новая версия
reconnect ручной автоматический
subscriptions sync async
disconnect мгновенный graceful
heartbeat off on
errors silent strict

Именно behavioral compatibility чаще всего вызывает production-инциденты.


Диагностика несовместимости

Характерные симптомы:

Соединение не устанавливается

Connection closed

Подписки не работают

No messages received

Дублирование сообщений

Message delivered multiple times

Ошибки handshake

STOMP protocol error

Проблемы heartbeat

Connection timeout

Наиболее опасные сценарии несовместимости

Двойной reconnect

Вызывает лавинообразное создание соединений.

Изменение ACK semantics

Может привести к потере сообщений.

Некорректный heartbeat

Вызывает случайные disconnect.

Конфликт SockJS/WebSocket

Полностью ломает transport layer.

ESM/CommonJS mismatch

Приложение перестаёт собираться.


Практика version pinning

Для production-систем часто фиксируют версии:

{
  "@stomp/stompjs": "7.0.0"
}

а не:

{
  "@stomp/stompjs": "^7.0.0"
}

Это предотвращает неожиданные breaking changes.


Semantic Versioning и STOMP.js

Не все breaking changes исторически сопровождались major-version bump.

Особенно в старых поколениях библиотек встречались:

  • скрытые изменения поведения;
  • изменения reconnect;
  • новые timeout;
  • изменения handshake.

Поэтому обновление даже minor-версии требует тестирования.


Integration testing при обновлении

Критически важно тестировать:

  • reconnect;
  • broker restart;
  • network interruption;
  • duplicate delivery;
  • ACK;
  • binary payload;
  • UTF-8;
  • heartbeat;
  • массовые подписки;
  • memory leaks.

Без integration-тестов несовместимости часто проявляются только в production.