Skip to main content

Структура сервисов 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. Процесс старта

  1. Запускается start(log) родительского класса.
  2. Для каждого имени в optionalServices вызывается host.waitService(optName), но без ожидания.
  3. Для requirements формируются Promise на waitRequire(svcName, log), которые резолвятся после получения сервиса.
  4. Все требования загружаются параллельно (Promise.all).
  5. Загруженные сервисы сохраняются в свойства 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 — произвольные данные,
  • optionsshowUser, httpStatus, logId.

Удобно выбрасывать в методах сервиса.

Config

Глобальная конфигурация, загружаемая при старте приложения. Сервис получает её через this.Config.

Event

Реализация событий с поддержкой:

  • подписки/отписки (on, off),
  • вложенных событий (addSubEvent, removeSubEvent),
  • автоматического вызова onSubscribe/onUnsubscribe при появлении первого/последнего слушателя.

Это позволяет легко комбинировать сервисы, запускать их в разных процессах и обеспечивать надёжность за счёт автоматического перезапуска и отслеживания состояния.