Обратная совместимость

Обратная совместимость в STOMP.js — способность библиотеки сохранять работоспособность старого клиентского кода после обновления версии. Для WebSocket-клиентов это особенно важно, поскольку STOMP.js часто используется в корпоративных системах, микросервисных платформах, финансовых приложениях, CRM, ERP и realtime-интерфейсах, где обновление фронтенда и брокера сообщений происходит независимо.

Нарушение обратной совместимости обычно приводит к следующим проблемам:

  • изменение API клиента;
  • удаление устаревших методов;
  • изменение поведения reconnect;
  • несовместимость heartbeat-механизмов;
  • изменение сигнатур callback-функций;
  • проблемы сериализации сообщений;
  • несовместимость форматов заголовков;
  • изменение стратегии ACK/NACK;
  • несовместимость с брокерами старых версий.

В экосистеме STOMP.js наиболее критичными считаются переходы:

  • со старого stompjs на @stomp/stompjs;
  • с callback API на Promise/async-ориентированные подходы;
  • с SockJS-конфигураций старого формата;
  • с legacy reconnect API;
  • между STOMP 1.0, 1.1 и 1.2.

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

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

Поколение Особенности
stomp-websocket Старое API, callback-ориентированность
stompjs Упрощённый клиент
@stomp/stompjs Современный TypeScript-first клиент
RxStomp Reactive-обёртка над STOMP.js

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

Stomp.over(socket)

Современные версии делают акцент на классе Client:

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

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

Главная проблема обратной совместимости заключается в том, что старая модель инициализации долгое время считалась стандартом де-факто.


Совместимость старого API Stomp.over

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

Ранее подключение создавалось так:

const socket = new WebSocket('ws://localhost:8080/ws');
const client = Stomp.over(socket);

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

Этот API присутствовал во множестве legacy-проектов.


Современный подход

Новый API:

const client = new Client({
    brokerURL: 'ws://localhost:8080/ws',
    onConnect: () => {
        console.log('Connected');
    }
});

client.activate();

Проблемы миграции

Основные несовместимости:

Старое API Новое API
connect() activate()
disconnect() deactivate()
reconnect_delay reconnectDelay
callback-стиль объектная конфигурация
debug = fn debug: fn

Поддержка STOMP 1.0, 1.1 и 1.2

STOMP.js сохраняет совместимость с несколькими версиями протокола.

Отличия версий

Версия Особенности
STOMP 1.0 нет heartbeat
STOMP 1.1 heartbeat и ACK
STOMP 1.2 улучшенная обработка заголовков

Настройка версий протокола

const client = new Client({
    brokerURL: 'ws://localhost:8080/ws',
    stompVersions: new Versions(['1.0', '1.1', '1.2'])
});

Поддержка нескольких версий позволяет подключаться к старым брокерам без изменения серверной инфраструктуры.


Совместимость с RabbitMQ

Разные версии RabbitMQ STOMP plugin могут по-разному обрабатывать:

  • ACK;
  • heartbeat;
  • бинарные фреймы;
  • content-length;
  • escape-последовательности.

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

Некоторые legacy-конфигурации требуют отключения heartbeat:

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

Причина — старые версии плагина могли некорректно реагировать на heartbeat-кадры.


Совместимость ACK

Старые брокеры иногда поддерживают только:

ack: 'client'

а не:

ack: 'client-individual'

Современный клиент должен учитывать подобные ограничения.


Совместимость с ActiveMQ

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

  • нестандартные destination;
  • ограниченные STOMP-заголовки;
  • устаревшие heartbeat-алгоритмы;
  • нестабильную поддержку WebSocket.

Проблема destination-prefix

Некоторые старые ActiveMQ-конфигурации используют:

/topic/messages

другие:

/jms/topic/messages

Для совместимости приходится поддерживать оба формата.


Поддержка SockJS и legacy WebSocket

Во многих старых проектах WebSocket отсутствовал или блокировался прокси-серверами. Использовался SockJS.


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

const socket = new SockJS('/ws');

const client = Stomp.over(socket);

Современный вариант

const client = new Client({
    webSocketFactory: () => new SockJS('/ws')
});

Основные проблемы совместимости

Проблема Причина
reconnect не работает SockJS создаёт одноразовые соединения
heartbeat ломается транспорт не поддерживает ping
нестабильные disconnect transport fallback
polling transport ограничения старых браузеров

Совместимость со старыми браузерами

Ранние реализации STOMP.js поддерживали:

  • Internet Explorer;
  • старые Android Browser;
  • Safari старых поколений.

Современные версии библиотеки ориентированы на:

  • ES2015+;
  • современные WebSocket API;
  • Promise;
  • TypeScript;
  • современные bundler-системы.

Polyfill-совместимость

Для legacy-браузеров могут использоваться:

import 'core-js/stable';
import 'regenerator-runtime/runtime';

Иногда необходим polyfill WebSocket:

global.WebSocket = require('ws');

Особенно это актуально для старых Node.js-приложений.


Совместимость Node.js

Старые версии STOMP.js изначально ориентировались на браузерную среду.

В Node.js приходилось вручную подключать WebSocket:

import WebSocket from 'ws';

const client = new Client({
    webSocketFactory: () => new WebSocket('ws://localhost:8080/ws')
});

Различия между версиями Node.js

Версия Node.js Особенности
Node 10 проблемы ES-модулей
Node 12 ограниченная поддержка TS
Node 14+ стабильная поддержка
Node 18+ встроенный WebSocket

Совместимость CommonJS и ES Modules

Ранние версии использовали:

const Stomp = require('stompjs');

Современные версии:

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

Проблемы миграции

Основные проблемы:

  • конфликт default export;
  • несовместимость bundler;
  • ошибки transpilation;
  • различия tree-shaking;
  • проблемы Jest.

Пример адаптера совместимости

let ClientLib;

try {
    ClientLib = require('@stomp/stompjs');
} catch (e) {
    ClientLib = window.Stomp;
}

Обратная совместимость reconnect-механизмов

Ранее использовалось свойство:

client.reconnect_delay = 5000;

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

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

Поведенческие изменения reconnect

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

  • могли создавать множественные reconnect-циклы;
  • не очищали timers;
  • некорректно закрывали sockets.

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

  • используют внутренний state machine;
  • контролируют жизненный цикл;
  • предотвращают duplicate reconnect.

Совместимость callback API

Старые версии heavily relied on callbacks.

Старый стиль

client.connect(
    login,
    passcode,
    onConnect,
    onError
);

Новый стиль

const client = new Client({
    connectHeaders: {
        login,
        passcode
    },
    onConnect,
    onStompError
});

Проблемы миграции callback-кода

Основные сложности:

Проблема Причина
потеря this изменение контекста
race condition async activate
reconnect loop другой lifecycle
duplicate subscription повторная инициализация

Совместимость подписок

Старые проекты часто хранили подписки так:

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

Позже:

subscription.unsubscribe();

Современные версии сохранили этот API, что стало важным элементом обратной совместимости.


Совместимость обработки сообщений

Ранние версии STOMP.js работали преимущественно со строками:

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

Современные версии поддерживают:

  • бинарные данные;
  • Uint8Array;
  • ArrayBuffer;
  • content-length;
  • text encoder/decoder.

Проблемы старых систем

Legacy-системы могут:

  • игнорировать content-length;
  • ломаться на бинарных кадрах;
  • неправильно декодировать UTF-8;
  • не поддерживать escape-последовательности STOMP 1.2.

Режимы совместимости heartbeat

Heartbeat — один из наиболее проблемных элементов обратной совместимости.


Старые конфигурации

client.heartbeat.outgoing = 20000;
client.heartbeat.incoming = 20000;

Современные настройки

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

Причины несовместимости heartbeat

Причина Последствие
старый proxy разрыв соединения
медленный broker false disconnect
SockJS polling heartbeat потеря
мобильные сети jitter

Стратегии сохранения обратной совместимости

Адаптерный слой

Часто создаётся промежуточный слой:

class LegacyStompAdapter {
    constructor(url) {
        this.client = new Client({
            brokerURL: url
        });
    }

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

    disconnect(callback) {
        this.client.deactivate().then(callback);
    }
}

Версионирование API

Хорошей практикой считается:

/api/v1/ws
/api/v2/ws

Это позволяет поддерживать старые frontend-клиенты параллельно с новыми.


Feature detection

Вместо проверки версии библиотеки используется:

if (client.activate) {
    client.activate();
} else {
    client.connect();
}

Подобный подход устойчивее к обновлениям.


Поддержка legacy-конфигураций

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

client.debug = null;
client.ws = socket;
client.maxWebSocketChunkSize = 8 * 1024;

Современные версии могут:

  • игнорировать их;
  • выдавать warnings;
  • полностью удалять поддержку.

Deprecation policy

Современные версии STOMP.js используют механизм устаревания API.

Пример:

console.warn(
    'connect() is deprecated. Use activate().'
);

Такая политика позволяет постепенно мигрировать кодовые базы без мгновенного разрушения совместимости.


Проблемы сериализации между версиями

Старые клиенты:

body: JSON.stringify(data)

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

binaryBody: uint8Array

Если backend ожидает исключительно текстовые payload, бинарная передача вызывает несовместимость.


Совместимость interceptor-механизмов

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

client._transmit = function() {
    // custom logic
};

Современные версии скрывают внутреннюю реализацию.

Это нарушает совместимость кастомных расширений.


Проблемы совместимости TypeScript

Ранние проекты использовали JavaScript без типизации.

Современный STOMP.js содержит TypeScript-описания:

const client: Client = new Client();

Типовые проблемы миграции

Проблема Причина
implicit any старый JS-код
incompatible callback строгие типы
nullable socket strict mode
missing headers type интерфейсы

Поддержка старых конфигурационных файлов

В legacy-приложениях встречаются конфигурации:

window.STOMP_CONFIG = {
    url: 'ws://localhost:8080/ws',
    reconnect: true
};

Современные приложения чаще используют:

export default {
    brokerURL: import.meta.env.VITE_WS_URL
};

Для совместимости нередко поддерживаются оба формата одновременно.


Миграция без потери совместимости

Наиболее безопасная стратегия включает:

  1. создание compatibility-layer;
  2. постепенный перевод API;
  3. двойную поддержку connect/activate;
  4. сохранение старых callback;
  5. адаптацию reconnect;
  6. поддержку legacy heartbeat;
  7. поэтапное удаление deprecated API.

Пример полноценного compatibility-wrapper

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

export class CompatibleStompClient {
    constructor(url) {
        this.client = new Client({
            brokerURL: url,
            reconnectDelay: 5000
        });
    }

    connect(headers, onConnect, onError) {
        this.client.connectHeaders = headers;

        this.client.onConn ect = onConnect;

        this.client.onStompEr ror = onError;

        this.client.activate();
    }

    disconnect(callback) {
        this.client.deactivate()
            .then(() => {
                if (callback) {
                    callback();
                }
            });
    }

    subscribe(destination, callback) {
        return this.client.subscribe(
            destination,
            callback
        );
    }

    send(destination, headers, body) {
        this.client.publish({
            destination,
            headers,
            body
        });
    }
}

Такой слой позволяет запускать старый код практически без изменений, одновременно используя современную версию STOMP.js внутри системы.