Структура сервисов Morphcluster
Базовый класс Service
Service — это основа для всех сервисов. Каждый сервис обладает:
-
именем (
name) — обычно совпадает с именем класса; -
схемой (
schema) — описывает запросы, события и метаданные; -
логгером (
Logger) — изолированным экземпляромLogger; -
конфигурацией (
Config) — глобальной конфигурацией приложения; -
состояниями —
starting,started,private(локальный/приватный),remote(виртуальный).
Жизненный цикл
[создание] → host.addService(service) → host.startService(service)
→ service.start(log)
→ service.started = true
→ service.stop()
→ service.started = false
При возникновении ошибки во время старта хост автоматически попытается перезапустить сервис через 5 секунд.
Важные свойства
| Свойство | Тип | Описание |
|---|---|---|
name |
string |
Имя сервиса (по умолчанию имя класса) |
started |
boolean |
Запущен ли сервис в данный момент |
starting |
boolean |
Находится ли в процессе запуска |
private / local |
boolean |
Сервис не публикуется в глобальной системе |
remote |
boolean |
Виртуальный сервис, не добавляется в глобальную систему |
clients |
ServiceClient[] |
Клиенты, через которые сервис обращается к другим сервисам |
timers |
Timer[] |
Периодические задачи |
triggers |
Trigger[] |
Обработчики событий других сервисов |
senders |
ChannelSender[] |
Каналы для публикации данных |
aggregators |
ChannelAggregator[] |
Агрегаторы данных из каналов |
Схема сервиса
Схема задаётся через объект ServiceSchema и определяет публичный контракт сервиса.
import Service from './service.js';
class MyService extends Service {
constructor() {
super();
this.addRequest({
name: 'ping',
description: 'Проверка связи',
anonymous: true
});
this.addEvent({
name: 'onUpdate',
description: 'Событие обновления данных'
});
}
async ping(params, workspace, log) {
return { pong: true };
}
}
RequestSchema — описание запроса
| Поле | Тип | Описание |
|---|---|---|
name |
string |
Имя запроса (обязательное) |
description |
string |
Описание |
request |
object |
JSON-схема входящих параметров |
response |
object |
JSON-схема ответа |
anonymous |
boolean |
Доступен без аутентификации |
needAdmin |
boolean |
Требует прав администратора |
noLogs |
boolean |
Не логировать запрос |
http |
string |
HTTP-метод (если сервис публикуется через HTTP) |
EventSchema — описание события
| Поле | Тип | Описание |
|---|---|---|
name |
string |
Имя события |
description |
string |
Описание |
structure |
object |
Структура передаваемых данных |
Таймеры и триггеры
Таймер (Timer) — периодическая задача.
this.timers.push(new Timer('cleanup', 60000, async (log) => {
// очистка каждые 60 секунд
}));
Триггер (Trigger) — реакция на событие другого сервиса.
this.triggers.push(new Trigger('onDataChanged', async (log, data) => {
// обработать событие
}));
// во время старта нужно подключить триггер к событию
trigger.connect(targetService.getEvent('onDataChanged'));
Оба компонента автоматически инициализируются при старте сервиса (им присваиваются serviceName и logger).
Каналы (ChannelSender / ChannelAggregator)
- ChannelSender — позволяет сервису публиковать данные в именованный канал.
- ChannelAggregator — собирает данные от нескольких сервисов в одном канале, вызывая событие при изменении.
Пример использования:
this.senders.push(new ChannelSender('metrics'));
// где-то в коде
this.senders[0].data = { cpu: 50 };
this.aggregators.push(new ChannelAggregator('metrics'));
this.aggregators[0].onDataChanged.on((ws, allData) => {
// allData — массив { serviceName, data }
});
Объявление зависимостей
class MyService extends ServiceRequire {
constructor(host) {
super(host);
this.requirements = ['Database', 'Cache']; // жёсткие зависимости
this.optionalServices = ['Metrics']; // опциональные, не блокируют старт
}
}
3.2. Процесс старта
- Запускается
start(log)родительского класса. - Для каждого имени в
optionalServicesвызываетсяhost.waitService(optName), но без ожидания. - Для
requirementsформируются Promise наwaitRequire(svcName, log), которые резолвятся после получения сервиса. - Все требования загружаются параллельно (
Promise.all). - Загруженные сервисы сохраняются в свойства
this[svcName].
Состояние загрузки можно отследить через requirementsStatus().
ServiceClient — клиент к сервису
Клиент позволяет сервису обращаться к другому сервису, не заботясь о его физическом расположении (локальный или удалённый).
Создание клиента
Клиент создаётся на основе схемы целевого сервиса:
const client = new ServiceClient({
name: 'Auth',
requests: [{ name: 'admin' }],
events: [{ name: 'onLogin' }]
});
this.clients.push(client);
Использование
-
Вызов запроса — через
client.requestHandlers[i](log, params). Обычно клиент автоматически связывается с глобальным сервисом (см. GlobalServices). -
Подписка на событие —
client.eventHandlers[i].on(callback).
Метод waitConnect() возвращает Promise, который разрешится после подключения клиента к реальному сервису.
Логирование
Каждый сервис получает экземпляр Logger с предустановленными service и host. Логгер поддерживает:
- Дочерние логи (
createWriter,attachWriter) для операций внутри сервиса. - Автоматический захват ошибок (
writeException,wrap,createWrapped). - Бэкенды: консоль, файл, воркер (через
LoggerBackendWorker).
Пример использования внутри сервиса:
async myMethod(params) {
const log = this.Logger.createWriter('MyMethod', { params });
try {
// работа
log.close();
} catch (e) {
log.writeException(e);
}
}
Пример создания собственного сервиса
import { ServiceRequire, Timer } from '@morphcluster/core';
export default class ReportService extends ServiceRequire {
constructor(host) {
super(host);
this.requirements = ['Database'];
this.optionalServices = ['Notification'];
this.addRequest({
name: 'generate',
description: 'Сгенерировать отчёт',
request: {
type: 'object',
properties: {
from: { type: 'string' },
to: { type: 'string' }
}
}
});
this.timers.push(new Timer('dailyReport', 86400000, this.dailyReport.bind(this)));
}
async start(log) {
await super.start(log);
// здесь Database уже доступен через this.Database
}
async generate(params, workspace, log) {
const data = await this.Database.query(/* ... */);
return { report: data };
}
async dailyReport(log) {
log.write('Starting daily report');
// логика отчёта
}
}
Вспомогательные компоненты
ComplexError
Обёртка над Error с дополнительными полями:
-
name— тип ошибки, -
payload— произвольные данные, -
options—showUser,httpStatus,logId.
Удобно выбрасывать в методах сервиса.
Config
Глобальная конфигурация, загружаемая при старте приложения. Сервис получает её через this.Config.
Event
Реализация событий с поддержкой:
- подписки/отписки (
on,off), - вложенных событий (
addSubEvent,removeSubEvent), - автоматического вызова
onSubscribe/onUnsubscribeпри появлении первого/последнего слушателя.
Это позволяет легко комбинировать сервисы, запускать их в разных процессах и обеспечивать надёжность за счёт автоматического перезапуска и отслеживания состояния.