@lad-tech/nsc-toolkit v1.23.0
nsc-toolkit
Содержание
О библиотеке
nsc-toolkit (NATS service creator toolkit) 一 это набор инструментов для создания сервис-ориентированной архитектуры.
Основная идеология тулкита заключается в установке минимального количества зависимостей, что позволяет создавать сервисы с помощью инструментов кодогенерации на основе простого описания в JSON-формате.
Основным средством коммуникации между сервисами в обычном режиме работы является брокер сообщений NATS. Применяются четыре основных способа:
- Синхронный (
request/reply) через брокер сообщений. Используется, если одному сервису, чтобы продолжать выполнять свою логику, требуются данные из другого сервиса. - События (
pub/sub). События используются, когда сервису необходимо оповестить другие сервисы о произошедшем событии, но кто слушает и обрабатывает эти события, сервису неизвестно. Это дает возможность строить независимые между собой сервисы для создания архитектуры на основе событий 一 Event-driven architecture (EDA). - Web-потоки на основе
HTTP 1.1. Этот метод используется, если сервису требуется передать в другой сервис поток данных. Например, список из миллиона пользователей или любые другие объемные данные, включая бинарные. Тогда брокер выступает только как средство для балансировки нагрузки и не участвует в передаче данных напрямую. Данные передаются с помощью web-потока из одного сервиса в другой через прямое соединение. Метод сервиса в качестве входных параметров может принимать поток данных и отдавать его в качестве ответа. - Jet-streams используют возможности брокера NATS. Jet-streams похожи на
pub/sub, но предоставляют возможность хранить сообщения на самом брокере. Такой тип коммуникации позволяет реализовывать более безопасный способ общения между сервисами на основе событий. События, если на них нет подписчиков на текущий момент, не пропадут, а сохранятся на самом брокере.
Все четыре способа коммуникации между сервисами реализуются средствами библиотеки и описываются в JSON.
Возможности
- Простота и минимальное количество зависимостей за счет снижения уровня вариативности
- Схема взаимодействия
request/reply - Схема взаимодействия
pub/sub - Использование Web-streams
- Использование Jet-streams
- Сквозной таймаут для запросов
- Трассировки
- Межсервисное кеширование
- Валидация входных и выходных параметров методов сервиса на основе JSON Schema.
- Логирование с учетом контекста
- Декораторы для инъекции зависимостей в методы сервиса
- Сервисный http-маршрут для проб, который поднимается вместе со стартом сервиса (доступен
GET [host]/healthcheck HTTP/1.1) - Сворачивание написанных сервисов в монолитное приложение без использования брокера NATS с сохранением всего спектра перечисленных функциональностей. Разные типы архитектуры (микросервисы или монолит) собираются из одной кодовой базы. Это может происходить параллельно в одном пайплайне CI/CD.
Установка
npm i @lad-tech/nsc-toolkitБыстрый старт
Для обзора возможностей библиотеки рекомендуется использовать пример из каталога examples, который состоит из 3-х сервисов. Подробно реализация логики описана в пункте Пример использования.
Чтобы запустить пример, необходимо:
- клонировать репозиторий с библиотекой и установить зависимости:
npm i- дополнительно зайти в каталог
examplesс сервисомHttpGate
cd ./examples/HttpGateи установить там зависимости, так как для реализации HTTP-API используется библиотека Fastify:
npm i- после установки зависимостей можно запустить сервисы.
Список всех зависимостей можно посмотреть тут.
Переменные окружения
DEFAUL_RESPONSE_TIMEOUT一 внешнее ограничение тайм-аута при запросе в секундах. Используется при первоначальном формировании времени тайм-аута в багаже по формулеDate.now() + DEFAULT_RESPONSE_TIMEOUT;OTEL_AGENT一 хост агента по сбору распределенных трассировок.
Основные компоненты библиотеки
Основные компоненты библиотеки хранятся в каталоге src. То, что не перечисленно в описании ниже, недоступно для использования и применяется только во внутреннем устройстве библиотеки.
Для реализации сервисов используются следующие компоненты.
Файл
service.schema.json. Каждый сервис описывается в формате json.Ниже представлена схема описания.
{ "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "properties": { "name": { "type": "string" }, "description": { "type": "string" }, "methods": { "type": "object", "additionalProperties": { "type": "object", "properties": { "action": { "type": "string" }, "description": { "type": "string" }, "options": { "$ref": "#/$defs/options" }, "request": { "type": "object" }, "response": { "type": "object" } }, "required": ["action", "description", "options"] } }, "events": { "type": "object", "properties": { "list": { "type": "object", "additionalProperties": { "type": "object", "properties": { "action": { "type": "string" }, "options": { "type": "object", "properties": { "stream": { "type": "boolean" } }, }, "description": { "type": "string" }, "event": { "type": "object" } }, "required": ["action", "description", "event"] }, }, "streamOptions": { "type": "object", "properties": { "prefix": { "type": "string" }, "actions": { "type": "array", "items": { "type": "object", "properties": { "action": { "type": "string" }, "storage": { "type": "string" }, "retentionPolicy": { "type": "string" }, "discardPolicy": { "type": "string" }, "messageTTL": { "type": "number" }, "duplicateTrackingTime": { "type": "number" }, "replication": { "type": "number" }, "rollUps": { "type": "boolean" } }, "required": ["action"] } } }, "required": ["prefix", "actions"] } }, "required": ["list"] } }, "required": ["name", "description", "methods"], "$defs": { "options": { "type": "object", "properties": { "useStream": { "type": "object", "properties": { "request": { "type": "boolean" }, "response": { "type": "boolean" } } }, "cache": { "type": "number" }, "runTimeValidation": { "type": "object", "properties": { "request": { "type": "boolean" }, "response": { "type": "boolean" } } } } } } }Пояснения к схеме:
name一 название сервиса;description一 описание сервиса;methods一 набор методов для реализации схемыrequest/reply;action一 идентификатор запроса;description一 описание метода;request一 JSON Schema входных данных;response一 JSON Schema выходных данных;options一 настройки метода;useStream一 использование Web-стримов для входных и выходных данных;request一 Web-стрим на входе;response一 Web-стрим на выходе;
timeout一 Таймаут в миллисекундахcache一 кеширование запроса (задается в минутах);runTimeValidation一 использование runtime для валидации параметров;request一 для входных данных;response一 для выходных данных;
events一 набор событий, генерируемый сервисом для реализации схемыpub/sub;list一 массив объектов событий, генерируемых сервисом;action一 идентификатор события;description一 описание события;options一 необязательные настройки события;stream一 булевый флаг, маркирующий, что событие является стримом;
event一 JSON Schema события;
name一 идентификатор события;description一 описание события;event一 JSON Schema события;
streamOptions一 настройки стрима;prefix一 префикс ко всем темам, у которых включена опцияstream: true;actions一 массив объектов настроек стримов;action一 паттерн для тем, на которые будут применяться данные настройки (если указана*, то применяется ко всем событиям с опциейstream: true);storage一 хранилище для сообщений стрима (доступные вариантыfile|memory, по умолчаниюfile);retentionPolicy一 по каким правилам будут удаляться сообщения из стрима (доступные вариантыlimits|interest|workQueue, по умолчаниюlimits);discardPolicy一 определяет, какие сообщения будут отброшены при достижении лимита (доступные вариантыold|new, по умолчаниюold);messageTTL一 срок жизни сообщения (задается в секундах, по умолчанию 2 недели);maxBytes一 максимально возможный лимит стрима по его размеру (если не указывать ограничения по размеру не будет);duplicateTrackingTime一 временной отрезок, на котором работает дедубликация сообщений (задается в секундах, по умолчанию 1 день);replication一 количество реплик стрима (по умолчанию 1);rollUps一 флаг, разрешающий удалить все сообщения из стрима (по умолчаниюtrue).
Файл позволяет описать все возможности создаваемого сервиса:
- синхронные методы;
- генерируемые сообщения;
- настройки стримов для сообщений.
Сервис предполагается создавать из описания с помощью кодогенерации. Используется библиотека для кодогенерации сервиса из описания nsc-cli.
Класс
Service. Чтобы создать сервис, необходимо создать экземпляр этого класса и вызвать методstart.Принимаемые параметры при создании экземпляра:
name一 имя сервиса.brokerConnection一 необязательный параметр для подключения к брокеру NATS. Если его не передать, то будет использоваться внутренняя реализация брокера, и приложение будет собираться в монолит.methods一 массив методов.BaseMethodэто один из базовых классов библиотеки, который необходимо расширить, чтобы создать метод с бизнес-логикой.events一 описание генерируемых событий. Следует передать весь блокeventsизservice.schema.json.cache一 настройки межмикросервисного кеширования.service一 реализация интерфейса для сервиса кеширования;timeout一 время, в рамках которого необходимо ожидать ответа от кеша (задается в миллисекундах).
loggerOutputFormatter一 реализация класса форматера для логов. Для логирования используется библиотека @lad-tech/toolbelt.gracefulShutdown一 настройки по завершению работы сервиса.additional一 массив дополнительных сервисов, которые могут завершать свою работу. Необходим, чтобы сервисы реализовывали нужный интерфейс.timeout一 время, в рамках которого ожидается штатная остановка сервиса.
Публичные методы:
getRootBaggage一 получение корневого багажа для распределенной трассировки. Источником могут служить заголовки http-запроса. Если подобные источники отсутствуют, то создается новый корневой багаж.endRootSpan一 завершение корневогоSpanпоidтрассировки. Необходим для внедрения распределенных трассировок.buildService一 метод, чтобы создать экземпляр клиента. Все используемые при запросах клиенты (сервисы) должны создаваться через этот метод, если необходимо использовать полную функциональность библиотеки.start一 запуск сервиса.stop一 остановка сервиса.
Класс
Client. Чтобы создать клиент для сервиса, необходимо расширить классClientи описать в нем все доступные извне вызовы методов. Для вызова метода используется приватный методCLient.request. Пример создания клиента можно посмотреть тут.Публичные методы:
getListener一 метод получения объектаEventEmitterдля подписки на события сервиса.
Класс
Container. Реализует DI-контейнре. Сам класс не доступен для импорта, а доступен только его экземпляр, что позволяет реализовать шаблон Singlton. В микросервисном варианте контейнер один на сервис. В монолитном варианте использования контейнер один на все приложение. За счет использования объектов Symbol в качестве ключей для привязки зависимостей исключены коллизии при привязках зависимостей в разных частях приложения через один контейнер. Для привязки зависимости к ключу необходимо указать тип зависимости. Это нужно чтобы библиотека могла корректно создать экземпляр зависимости. Всего существуют 3 вида зависимостей:service一 сервис как зависимости.adapter一 класс с набором синхронных или асинхронных методов. Например репозиторий или фасад от стороннего API. Этот тип зависимости можно настроить с помощью необязательного объекта настроек. У опций адаптера могут быть следующие настройки:singlton一 Булевое значение. Если передатьtrueто адаптер станет singlton'ом. Объект будет создан один раз и дальше будет всегда использоваться один экземпляр.init一 Булевое значение. Для того что бы использовать этот флаг адаптер должен реализовывать следующий интерфейс
Если адаптер реализует требуемые методы и при передачи флага{ init: () => Promise<any>; close: () => Promise<any>; }initпри привязки адаптера адаптер можно будет инициализировать через контейнер использую метод контейнераinit. Если к контейнеру было привязано несколько адаптеров, которые необходимо инициализировать, то после вызова метода контейнераinitвсе адаптеры будут инициализированны последовательно. Метод инициализации вернет массив инициализированных экземпляров адаптеров. Каждый адаптер с настройкойinitтакже становится singlton'ом.
constant一 обычный объект. Например, объект с конфигурацией.- Альтернативный синтаксис привязки с учетом вышеизложенной информации
// Простой адаптер container.symbol(key).to.Adapter(InitializableService); // константа container.symbol(key2).to.Constant({ obj: 'value' }); // Синглтон container.symbol(key).to.Singlton(mySingleton); // Сервис с автоинициазлзацией container.symbol(key).to.Initializable(InitializableService); // Сервис container.symbol(key).to.Service(MyService);
```
Публичные методы:bind一 привязать реализацию к ключу.unbind一 отвязать реализацию от ключа.get一 получить реализацию по ключу.getInstance一 получить экземпляр реализации. Для зависимости с типомserviceнельзя получить экземпляр через этот метод поскольку для создания экземпляра сервиса требуется контекст в рамках которого он будет работать. Для зависимости с типомconstantвернется привязанный объект.
- Декораторы. Применение декораторов:
@service:- Если в методе сервиса вызывается метод другого сервиса, то клиент сервиса зависимости следует подключить через декоратор
@service, а к самому классу метода применить декоратор@related. Тогда при вызове метода сервиса не пропадет контекст запроса и можно будет использовать распределенные трассировки. - Инъекция через декоратор
@serviceпозволяет использовать функциональность сборки приложения в монолит.
- Если в методе сервиса вызывается метод другого сервиса, то клиент сервиса зависимости следует подключить через декоратор
@instance:- Используется для инъекции других зависимостей, например, объектов репозиториев. В него можно передать готовый объект с набором асинхронных методов. При вызове этих методов из его логики они будут видны в трассировках. Для получения таких зависимостей рекомендуется использовать DI-контейнеры.
- Для передачи объектов в поток можно использовать встроенные классы
JsonToBufferTransformиBufferToJsonTransform, с помощью которых склейка буфера при передаче и парсинг произойдет в автоматическом режиме
Рекомендации
Библиотека задумана как инструмент, не диктующий жестких правил, однако предлагается следовать следующим рекомендациям.
Использовать кодогенерацию на основе описания сервиса в файле
service.schema.jsonс помощью библиотеки nsc-cli. На основе описания будут автоматически сгенерированы сервис, клиент, методы и все интерфейсы. В результате весь шаблонный код будет написан и можно сразу приступать к написанию бизнес-логики. Для изменения сервиса (например, добавления нового метода, события или параметров запроса) следует в первую очередь изменить файл описанияservice.schema.json, а затем перегенерировать сервис на его основе. Уже написанная логика не пропадет, но все изменения будут учтены в коде.Описывать структуру сервиса. Ниже представлена декларативная структура сервиса следующего вида.
Service/
├── domain/
│ ├── aggregates/
│ │ ├── Aggregate_1/
│ │ │ ├── fixtures/
│ │ │ │ ├── fixture_1.json
│ │ │ │ ├── fixture_1.json
│ │ │ │ └── ...
│ │ │ ├── Aggregate.ts
│ │ │ ├── Aggregate.interface.ts
│ │ │ ├── Aggregate.test.ts
│ │ │ └── index.ts
│ │ └── Aggregate_2/
│ │ ├── fixtures/
│ │ │ ├── fixture_1.json
│ │ │ ├── fixture_1.json
│ │ │ └── ...
│ │ ├── Aggregate.ts
│ │ ├── Aggregate.interface.ts
│ │ ├── Aggregate.test.ts
│ │ └── index.ts
│ └── ports/
│ ├── index.ts
│ └── repository.ts
├── methods/
│ ├── Method_1/
│ │ ├── index.ts
│ │ ├── index.test.ts
│ │ └── ...
│ ├── Method_2/
│ │ ├── index.ts
│ │ ├── index.test.ts
│ │ └── ...
│ └── ...
├── repository/
│ └── index.ts
├── index.ts
├── interfaces.ts
├── service.ts
├── start.ts
├── inversion.types
├── service.schema.json
├── package.json
├── package-lock.json
└── tsconfig.jsonПояснения к структуре:
domain一 каталог, который содержит в себе все классы предметной области сервиса и интерфейсы портов, необходимых для ее работы;aggregates一 каталог с агрегатами предметной области;Aggregate一 каталог агрегата (например,User);Aggregate.ts一 класс агрегата;Aggregate.interface.ts一 интерфейсы агрегата (например, интерфейс параметров для создания экземпляра агрегата или интерфейс представления агрегата);Aggregate.test.ts一 тесты для агрегата;fixtures一 фикстуры для тестирования агрегата;index.ts一 реэкспорт агрегата и его интерфейсов;
ports一 каталог, который содержит интерфейсы для всех необходимых логике портов;repository.ts一 файл с интерфейсом необходимого репозитория для хранения агрегатов (для примера);index.ts一 реэкспорт портов и его интерфейсов;
methods一 каталог с синхронными методами сервиса (генерируется автоматически);Method_1一 каталог метода;index.ts一 класс метода;index.test.ts一 тесты для метода;
repository一 реализация репозитория на основе порта;
index.ts一 файл клиента сервиса (генерируется автоматически);interfaces.ts一 интерфейсы сервиса (генерируется автоматически);service.ts一 реализация сервиса (генерируется автоматически);start.ts一 точка входа для запуска сервиса (генерируется автоматически);inversion.types.ts一 Типы зависимостей используемые в логике сервиса. Типы используются для получения зависимости. Реализация требуемой зависимости привязывается к контейнеру через тип в файле сервиса. Описание встроенных возможностей инверсии зависимостей.service.schema.json一 описание сервиса.
Вся бизнес-логика сконцентрирована в двух местах структуры:
- Агрегаты предметной области. В методах агрегатов содержится чистая бизнес-логика.
- Методы сервиса. Методы содержат часть бизнес-логики сервиса и конкретного метода сервиса, а также служат связующим звеном бизнес-логики агрегата и портов.
Пример использования
Пример использования инструментов тулкита находится в каталоге examples.
Все три сервиса созданы для реализации тестовой логики "Странная сумма":
- Во вход необходимо передать два числа
aиb. - На первом этапе логика сервисов сложит эти два числа между собой. Полученная сумма будет использована в качестве максимального количества чисел из ряда Фибоначчи.
- На последнем этапе, получив последовательность чисел, логика сложит их между собой и вернет ответ.
Описание каталога examples
HttpGate一 cервис предоставляет http api с одним маршрутомPOST /math/weird/sum. В качестве тела запроса необходимо передать json. Например:
{
"a": 5,
"b": 5
}После получения запроса сервис HttpGate синхронно через брокер вызывает сервис Logic и, получив от него ответ, возвращает результат в качестве ответа на запрос.
LogicService一 сервис реализует логику "Странной суммы". Сам сервис ничего не вычисляет, а использует другой сервисMathService:
- Получив два числа от
HttpGate,LogicServiceсинхронно вызывает метод сервисаMath.sum, который складывает два переданных числа. - Затем сервис вызывает метод
Math.Fibonnacci, в который передает результат предыдущего действия и в качестве ответа получает поток с числами Фибоначчи. - Получив поток чисел, сервис передает этот поток в метод
Math.SumStream, который складывает все числа в потоке и в ответе возвращает одно число, которое и является "Странной суммой". - В конце последней операции сервис генерирует событие
Elapsed, в которое передает затраченное на выполнение вычислений время.
MathService一 сервис реализует 3 метода:Sum,FibonacciиSumStream, описанные выше. Помимо этих методов, сервис генерирует также два события:
- При вызове метода
SumStreamсервис генерирует обычное событиеNotify. - После завершения вычислений сервис генерирует событие
Elapsed, в которое передает затраченное на выполнение вычислений время. СобытиеElapsedописано как Jet-stream.
В результате запросов к тестовым сервисам генерируются трассировки следующего вида:


Сворачивание сервисов в монолитное приложение
Библиотека поддерживает возможность сворачивать микросервисы в одно монолитное приложение. Чтобы реализовать эту возможность, необходимо придерживаться рекомендаций.
Для сворачивания в монолит необходимо использовать отдельную точку входа в приложение. Она должна находиться в сервисе, предоставляющем внешнее API к методам приложения.
Логика точки входа в монолитную версию приложения:
- Создается экземпляр сервиса без передачи в него подключения к NATS. В таком случае вместо NATS в качестве брокера будет использована внутренняя реализация брокера на основе встроенного объекта
EventEmitter. Доступ в созданному брокеру можно получить через открытое свойство сервисаbroker. - После создания корневого сервиса необходимо запустить остальные сервисы, передав полученный брокер корневого сервиса. За счет этого все сервисы приложения будут использовать один внутренний брокер. При создании клиентов и при использовании инъекций также будет использован этот брокер. Единый внутренний брокер свяжет дерево сервисов в одно приложение и позволит запустить весь код сервисной архитектуры в монолитном режиме.
Пример точки входа для монолитного режима и скрипт сборки можно посмотреть в каталоге examples: точка входа.
┌───────────┐
│ HTTP_GATE │
└─────┬─────┘
┌─────────┴─────────┐
┌───┴───┐ ┌─────┴─────┐
│ Logic │ │ Service_1 │
└───┬───┘ └─────┬─────┘
│ ┌──────┴───────┐
┌──┴───┐ ┌─────┴─────┐ ┌─────┴─────┐
│ Math │ │ Service_2 │ │ Service_3 │
└──────┘ └───────────┘ └───────────┘Инверсия зависимостей и DI-контейнер
Библиотека реализует возможности по инверсии зависимостей через DI-контейнер. Экземпляр контейнера можно получить импортировав его из библиотеки. Для описания существующих ключей зависимостей рекомендуется использовать отдельный файл inversion.types.ts в корне сервиса. Пример файла. Для внедрения зависимостей используются свойства класса метода или параметры конструктора. Для описания зависимости используется декоратор inject, который можно импортировать из библиотеки. В декоратор необходимо передать символьный ключ из файла inversion.types.ts. Саму привязку реализаций к DI-контейнеру рекомендуется осуществлять в основном файле сервиса service.ts. Пример с глубоко вложенными зависимостями разных типов можно посмотреть в методе. Цепочка внедряемых зависимостей.
┌---------┐ ┌───────────-┐ ┌───────────---┐ ┌---------┐
| GetUser |--->│ Repository |--->| Configurator |--->| Storage |
└---------┘ └─────-─────-┘ └─────-─────---┘ └---------┘1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago