JSPM

servicelink

1.0.1
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • 0
  • Score
    100M100P100Q18976F
  • License MIT

Production-ready микросервисная библиотека для RabbitMQ с Circuit Breaker, валидацией и graceful shutdown

Package Exports

  • servicelink
  • servicelink/index.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (servicelink) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

🐰 ServiceLink

Production-ready микросервисная библиотека для RabbitMQ

npm version License: MIT Node.js

ServiceLink - это простая, но мощная библиотека для создания микросервисов на основе RabbitMQ. Она включает в себя Circuit Breaker, валидацию входных данных, graceful shutdown и другие production-ready возможности.

✨ Основные возможности

  • 🚀 Простота использования - минимальная конфигурация, максимальная функциональность
  • Circuit Breaker - защита от каскадных отказов
  • 🛡️ Валидация - проверка входных параметров и состояний
  • 🔄 Автопереподключение - устойчивость к сбоям сети
  • 📊 Мониторинг здоровья - встроенные health checks
  • 🛑 Graceful Shutdown - корректное завершение работы
  • 📝 TypeScript Ready - полная поддержка типов
  • 🎯 RPC & Events - синхронные вызовы и асинхронные события

📦 Установка

npm install servicelink

или

yarn add servicelink

🚀 Быстрый старт

const ServiceLink = require('servicelink');

// Создание сервиса
const userService = new ServiceLink({
    serviceName: 'user-service',
    host: 'localhost',
    port: 5672,
    username: 'admin',
    password: 'admin',
    debug: true
});

// Отправка события
await userService.send('email-service', 'user-registered', {
    email: 'user@example.com',
    name: 'Иван Иванов'
});

// Обработка событий
await userService.listen('user-updated', async (message, context) => {
    console.log('Пользователь обновлен:', message);
});

// RPC вызов
const result = await userService.call('email-service', 'send-email', {
    to: 'user@example.com',
    subject: 'Добро пожаловать!'
});

// Обработка RPC
await userService.handle('get-user', async (params) => {
    return { id: params.id, name: 'Иван Иванов' };
});

// Graceful shutdown
await userService.shutdown();

📚 Подробная документация

Конфигурация

const service = new ServiceLink({
    // Обязательные параметры
    serviceName: 'my-service',        // Имя сервиса
    
    // Подключение к RabbitMQ
    host: 'localhost',                // Хост RabbitMQ
    port: 5672,                       // Порт RabbitMQ
    username: 'guest',                // Имя пользователя
    password: 'guest',                // Пароль
    // ИЛИ
    url: 'amqp://guest:guest@localhost:5672', // URL подключения
    
    // Настройки производительности
    prefetchCount: 10,                // Количество необработанных сообщений
    reconnectTimeout: 5000,           // Время между попытками переподключения (мс)
    maxReconnectAttempts: 5,          // Максимальное количество попыток переподключения
    
    // Circuit Breaker
    failureThreshold: 5,              // Порог количества ошибок
    resetTimeout: 30000,              // Время до попытки восстановления (мс)
    
    // Отладка
    debug: false                      // Включить debug сообщения
});

События (Fire-and-Forget)

Отправка асинхронных событий между сервисами:

// Отправка события
const result = await service.send('target-service', 'event-name', {
    data: 'payload'
}, {
    errorHandler: (error) => {
        console.log('Circuit breaker активирован:', error.message);
        return null;
    }
});

// Обработка событий
await service.listen('event-name', async (message, context) => {
    console.log('Получено событие:', message);
    console.log('Метаданные:', context); // { id, from, topic, timestamp }
});

RPC (Request-Response)

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

// RPC вызов
try {
    const result = await service.call('target-service', 'method-name', {
        param1: 'value1',
        param2: 'value2'
    }, 10000); // таймаут 10 секунд
    
    console.log('Результат:', result);
} catch (error) {
    console.error('RPC ошибка:', error.message);
}

// Обработка RPC запросов
await service.handle('method-name', async (params, context) => {
    // Валидация параметров
    if (!params.id) {
        throw new Error('ID обязателен');
    }
    
    // Бизнес-логика
    const data = await database.findById(params.id);
    
    return { success: true, data };
});

Circuit Breaker

Защита от каскадных отказов:

// Получение состояния
const state = service.getCircuitBreakerState();
console.log('Circuit Breaker:', state);
// { state: 'CLOSED', failures: 0, lastFailureTime: null }

// Ручной сброс
service.resetCircuitBreaker();

// Использование с обработчиком ошибок
await service.send('unstable-service', 'test', data, {
    errorHandler: (error) => {
        // Сервис недоступен, используем fallback
        return handleFallback(data);
    }
});

Мониторинг здоровья

const health = await service.health();
console.log(health);

// Результат:
{
    status: 'healthy',
    service: 'my-service',
    connected: true,
    circuitBreaker: { state: 'CLOSED', failures: 0 },
    timestamp: 1640995200000
}

Graceful Shutdown

// Обработка сигналов завершения
process.on('SIGINT', async () => {
    console.log('Получен SIGINT, завершаем работу...');
    await service.shutdown();
    process.exit(0);
});

process.on('SIGTERM', async () => {
    console.log('Получен SIGTERM, завершаем работу...');
    await service.shutdown();
    process.exit(0);
});

🏗️ Архитектурные паттерны

Микросервисная архитектура

// user-service.js
const userService = new ServiceLink({
    serviceName: 'user-service',
    host: process.env.RABBITMQ_HOST
});

// Обработка регистрации пользователя
await userService.handle('register', async (params) => {
    const user = await createUser(params);
    
    // Уведомляем другие сервисы
    await userService.send('email-service', 'user-registered', {
        userId: user.id,
        email: user.email
    });
    
    await userService.send('analytics-service', 'user-created', {
        userId: user.id,
        timestamp: Date.now()
    });
    
    return { success: true, userId: user.id };
});

Event Sourcing

// event-store-service.js
const eventStore = new ServiceLink({
    serviceName: 'event-store'
});

await eventStore.listen('*', async (event, context) => {
    // Сохраняем все события
    await saveEvent({
        eventId: context.id,
        eventType: context.topic,
        source: context.from,
        payload: event,
        timestamp: context.timestamp
    });
});

CQRS Pattern

// command-service.js (запись)
const commandService = new ServiceLink({
    serviceName: 'command-service'
});

await commandService.handle('update-user', async (command) => {
    // Выполняем команду
    await updateUser(command);
    
    // Публикуем событие
    await commandService.send('query-service', 'user-updated', {
        userId: command.userId,
        changes: command.changes
    });
});

// query-service.js (чтение)
const queryService = new ServiceLink({
    serviceName: 'query-service'
});

await queryService.listen('user-updated', async (event) => {
    // Обновляем read-модель
    await updateReadModel(event.userId, event.changes);
});

🛠️ Рабочие примеры

E-commerce система

// order-service.js
const orderService = new ServiceLink({
    serviceName: 'order-service',
    host: 'rabbitmq.local'
});

// Создание заказа
await orderService.handle('create-order', async (params) => {
    const { userId, items } = params;
    
    // Проверяем наличие товаров
    const availability = await orderService.call('inventory-service', 'check-availability', {
        items
    });
    
    if (!availability.available) {
        throw new Error('Товары недоступны');
    }
    
    // Создаем заказ
    const order = await createOrder(userId, items);
    
    // Резервируем товары
    await orderService.call('inventory-service', 'reserve-items', {
        orderId: order.id,
        items
    });
    
    // Уведомляем о создании заказа
    await orderService.send('payment-service', 'order-created', {
        orderId: order.id,
        amount: order.total
    });
    
    return order;
});

// inventory-service.js
const inventoryService = new ServiceLink({
    serviceName: 'inventory-service'
});

await inventoryService.handle('check-availability', async (params) => {
    const available = await checkStock(params.items);
    return { available };
});

await inventoryService.handle('reserve-items', async (params) => {
    await reserveStock(params.orderId, params.items);
    return { success: true };
});

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

// notification-service.js
const notificationService = new ServiceLink({
    serviceName: 'notification-service',
    debug: true
});

// Слушаем все события пользователей
await notificationService.listen('user-registered', sendWelcomeEmail);
await notificationService.listen('order-created', sendOrderConfirmation);
await notificationService.listen('payment-failed', sendPaymentAlert);

async function sendWelcomeEmail(message) {
    const { email, name } = message;
    
    // Отправляем email через внешний сервис
    const result = await notificationService.call('email-service', 'send-template', {
        to: email,
        template: 'welcome',
        variables: { name }
    });
    
    console.log('Welcome email отправлен:', result);
}

🚨 Обработка ошибок

// Валидация входных данных
await service.handle('create-user', async (params) => {
    if (!params.email || !params.name) {
        throw new Error('Email и имя обязательны');
    }
    
    if (!isValidEmail(params.email)) {
        throw new Error('Неверный формат email');
    }
    
    return await createUser(params);
});

// Обработка ошибок сети с Circuit Breaker
try {
    const result = await service.call('external-service', 'api-call', data);
} catch (error) {
    if (error.name === 'CircuitBreakerError') {
        // Сервис недоступен, используем кеш или fallback
        return getCachedResult(data);
    }
    throw error;
}

⚡ Производительность

Настройка для высоких нагрузок

const service = new ServiceLink({
    serviceName: 'high-load-service',
    prefetchCount: 100,              // Больше сообщений в буфере
    reconnectTimeout: 1000,          // Быстрое переподключение
    maxReconnectAttempts: 10,
    failureThreshold: 10,            // Более толерантный Circuit Breaker
    resetTimeout: 60000
});

Мониторинг метрик

// Простой мониторинг
setInterval(async () => {
    const health = await service.health();
    const cbState = service.getCircuitBreakerState();
    
    console.log(`Health: ${health.status}, CB: ${cbState.state}, Failures: ${cbState.failures}`);
}, 30000);

🧪 Тестирование

// test/service.test.js
const ServiceLink = require('servicelink');

describe('ServiceLink', () => {
    let service;
    
    beforeEach(() => {
        service = new ServiceLink({
            serviceName: 'test-service',
            host: 'localhost'
        });
    });
    
    afterEach(async () => {
        await service.shutdown();
    });
    
    test('должен отправлять и получать сообщения', async () => {
        let receivedMessage = null;
        
        await service.listen('test-event', async (message) => {
            receivedMessage = message;
        });
        
        await service.send('test-service', 'test-event', { test: true });
        
        // Ждем обработки
        await new Promise(resolve => setTimeout(resolve, 100));
        
        expect(receivedMessage).toEqual({ test: true });
    });
    
    test('должен выполнять RPC вызовы', async () => {
        await service.handle('test-method', async (params) => {
            return { result: params.input * 2 };
        });
        
        const result = await service.call('test-service', 'test-method', { input: 5 });
        
        expect(result).toEqual({ result: 10 });
    });
});

🔧 Конфигурация для production

Docker Compose

version: '3.8'
services:
  rabbitmq:
    image: rabbitmq:3-management
    environment:
      RABBITMQ_DEFAULT_USER: admin
      RABBITMQ_DEFAULT_PASS: password
    ports:
      - "5672:5672"
      - "15672:15672"
      
  user-service:
    build: ./user-service
    environment:
      RABBITMQ_HOST: rabbitmq
      RABBITMQ_USER: admin
      RABBITMQ_PASS: password
    depends_on:
      - rabbitmq

Переменные окружения

const service = new ServiceLink({
    serviceName: process.env.SERVICE_NAME,
    host: process.env.RABBITMQ_HOST || 'localhost',
    port: parseInt(process.env.RABBITMQ_PORT) || 5672,
    username: process.env.RABBITMQ_USER || 'guest',
    password: process.env.RABBITMQ_PASS || 'guest',
    debug: process.env.NODE_ENV === 'development'
});

🤝 Вклад в развитие

Мы приветствуем вклад в развитие проекта! Пожалуйста:

  1. Форкните репозиторий
  2. Создайте ветку для новой функции (git checkout -b feature/amazing-feature)
  3. Зафиксируйте изменения (git commit -m 'Add amazing feature')
  4. Отправьте в ветку (git push origin feature/amazing-feature)
  5. Откройте Pull Request

📄 Лицензия

MIT License - см. файл LICENSE для деталей.

📞 Поддержка