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
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'
});
🤝 Вклад в развитие
Мы приветствуем вклад в развитие проекта! Пожалуйста:
- Форкните репозиторий
- Создайте ветку для новой функции (
git checkout -b feature/amazing-feature
) - Зафиксируйте изменения (
git commit -m 'Add amazing feature'
) - Отправьте в ветку (
git push origin feature/amazing-feature
) - Откройте Pull Request
📄 Лицензия
MIT License - см. файл LICENSE для деталей.
📞 Поддержка
- 🐛 Баги: GitHub Issues
- 💬 Вопросы: GitHub Discussions