Перейти до основного вмісту

Емітер

інформація

Документ "Емітер" відповідає за роботу з ws запитами. На відміну від http протоколу, ws протокол не накладає строгих обмежень на протокол взаємодії, що надалі створює проблеми, оскільки різні підходи можуть по різному реалізовувати такий протокол.

X-Fiber реалізовує власну структуру даних взаємодії, описуючи деталі події, тип події та обробника цієї події.

Архітектура

Робота з подіями має два напрямки - прослуховування подій та генерація подій.

Прослуховування

При необхідності опису слухачів в предметній області необхідно створити документ "Емітер" з переліком необхідних слухачів подій.

Документ "Емітер" повинен бути зареєстрований в документі "Реєстр" предметної області, який в свою чергу, має бути зареєстрований в відповідному сервісі, включеному до бізнес-схеми. Це гарантує, що при запуску ядра обчислень будуть завантажені всі слухачі емітера. При виклику та виконанні конкретного обробника запиту ядро обчислень надасть актуальний знімок структури бізнес-схеми, де вже наявний опис події разом з його обробником.

emitter-arch.svg

Генерація

При необхідності генерації подій з передаванням інформації або без, необхідний створити відповідний документ:

  • В разі генерації подій як побічний ефект при обробці запитів чи субʼєктів, то необхідно створити документи "Маршрутизатор", "Брокер повідомлень" або доповнити опис документа "Емітер".
  • В разі генерації події за розкладом, необхідно створити документ "Планувальник", в якому описати завдання.

Відповідний документ повинен бути зареєстрований в документі "Реєстр" предметної області, який в свою чергу, має бути зареєстрований в відповідному сервісі, включеному до бізнес-схеми. Це гарантує, що при запуску ядра обчислень будуть завантажені всі елементи відповідного документа. При виклику та виконанні конкретного обробника документа ядро обчислень надасть актуальний знімок структури бізнес-схеми, викличе відповідний обробник, в рамках роботи якого буде згенерована подія.

emitter-emit.svg

Склад

Емітер поєднує в собі дві функції - опис деталей слухача, та створення обробників подій.

Загальна структура емітера

type EventType = "session:to:session" | "session:to:room" | "broadcast:to:service"
type AuthScope = 'public:route' | 'private:user' | 'private:system'
type Version = "v1" | "v2" | "v3" | "v4" | "v5" | string;


type EventHandler = (
...args // arguments
) => Promise<void>

export type EmitterStructure<E extends string = string> = {
[event in E]: {
[type in EventType]?: {
scope?: AuthScope
version?: Version
handler: EventHandler;
};
};
};

де:

  • Типи:
    • EventType - перелік типів ws подій.
    • AuthScope - перелік типів приватизації.
    • Version - тип версії.
    • EventHandler - структура обробника події.
    • EmitterStructure - структура документа "Емітер".
  • Структури обʼєкта події:
    • event - строкове представлення назви події.
    • type - тип ws події.
    • scope - тип приватизації події.
    • version - версія кінцевого маршруту.
    • handler - обробник події.
небезпека

X-Fiber перевіряє назви подій, які не повинні мати слеш або крапку, а при потребі створення назви події використовуйте slug або на худий кінець - camelCase. В разі наявності в назві події слешу або крапки ядро обчислень при завантаженні схеми видасть помилку з вказанням на місце розташування слухача в бізнес-схемі.

інформація

Опис події складається з опису типу приватизації та версії маршруту.

Тип події

Назва подіїТипОпис
handshakeЗʼєднанняРеєструє зʼєднання та записує ідентифікатори в обʼєкт зʼєднання.
handshake.errorЗʼєднанняНадає помилку, яка виникла при створені зʼєднання
authenticateАвторизаціяАвтентифікує запит та записує токен доступу в обʼєкт зʼєднання.
authenticate.errorЗʼєднанняНадає помилку, яка виникла при авторизації.
authenticate:refreshЗʼєднанняСповіщає про успішне оновлення токена доступу
route:service.not:foundЗʼєднанняПовертає помилку про створення події до неіснуючого сервіса.
route:domain.not:foundЗʼєднанняПовертає помилку про створення події до неіснуючої предметної області.
route:event.not:foundЗʼєднанняПовертає помилку про створення події до неіснуючої події.
session:to:sessionЗʼєднанняСтворення події від одного користувача до іншого користувча
broadcast:to:roomЗʼєднанняПублікація події до усіх зʼєднань в рамках кімнати.
broadcast:to:serviceЗʼєднанняПублікація події до усіх зʼєднань в рамках сервіса.
broadcast:to:schemeЗʼєднанняПублікація події до усіх зʼєднань.

Деталі події

Деталі події складаються з:

  1. Версії події (за замовчуванням v1),
  2. Типу приватизації (за замовчуванням public:route),

Версія

інформація

Версія кінцевого маршруту дозволяє відокремити минулі реалізації кінцевих маршрутів з якими працюють відповідні джерела взаємодії від нової, що спрощує підтримку API без потреби створювати кінцеві маршрути аналогічного призначення з словами синонімами.

За замовчуванням кінцевому маршруту присвоюється версія v1. Окремо варто підкреслити, що версії потрібно вказувати v1 / v2 / v3 / v4 і т.д.

Тип приватизації

Тип приватизації визначає, чи потребує маршрут автентифікації та даних сесії, яка вже зберігається на сервері. З розрахунку типу приватизації випливає можлива структура контексту виконання запиту. Так, для приватних кінцевих маршрутів контекст буде містити ще й дані про сесію.

X-Fiber підтримує наступні типи приватизації:

export type AuthScope = "public:route" | "private:user" | "private:system";

де:

  • public:route: Цей тип приватизації використовується для публічних маршрутів, які доступні для всіх користувачів без обмежень.
  • private:user: Цей тип приватизації застосовується до запитів, що відносяться до конкретного користувача. Для цього типу потрібні відповідні токени доступу користувача для аутентифікації та авторизації, які повинні надходити в заголовках запиту.
  • private:system: Цей тип приватизації використовується для системних запитів, які взаємодіють зі службами або компонентами системи. Для цього типу також потрібні відповідні системні токени доступу, які повинні надходити в заголовках запиту.
інформація

Залежно від типу приватизації, http адаптер ядра обчислень формує відповідний контекст виконання, що враховує наявність відповідних токенів доступу, окрім випадку з public:route. Деталі структури контексту виконання обробника запиту описані в "Обробник запиту"

Обробник події

Обробник події являє собою асинхронну функцію, яка складається з 3х аргументів та нічого не повертає у відповідь на запит.

import { Agents, Context } from '@x-fiber/proton'

export type EventHandler = <T = any>(
payload: T,
agents: Agents,
context: Context
) => Promise<void>;

де:

  • EventHandler - структура обробника події.
  • T - дані повідомлення.
  • Agents - перелік агентів функціональності.
  • Context - контекст виконання запиту.

Використання композиції

X-Fiber виділяє окремі документи з можливостями використання в цих спеціалізованих інструментів, наприклад в документі "Репозиторій" наявний провайдер з переліком методів для створення запитів до бази даних. Таке рішення прийняте навмисно, щоб ділити бізнес-логіки на окремі структурні складові, а при описі обробника запита використовувати підхід "Композиція"

handler-execution-process.svg

Аргумент запиту

Аргументами запиту виступає обʼєкт повідомлення, який слухач очікує при отриманні події.

Аргумент агентів функціональності

Аргумент агентів функціональності являє собою обʼєкт з переліком агентів:

type IFunctionalityAgent = {
// agent functionality description
}

type ISchemaAgent = {
// agent functionality description
}

type IIntegrationAgent = {
// agent functionality description
}

export type Agents = {
fnAgent: IFunctionalityAgent;
schemaAgent: ISchemaAgent;
inAgent: IIntegrationAgent;
};

де:

  • fnAgent - агент з переліком просторів імен функціональності, які надають функціональні компоненти ядра обчислень.
  • schemaAgent - агент з переліком функціональності для доступу до інших складових бізнес-схеми.
  • inAgent - агент з переліком просторів імен функціональності, які надають інтеграційні рішення ядра обчислень.

Деталі структури кожного агента функціональності описується в розділі "Агенти"

Аргумент контексту виконання

інформація

Контекст виконання має базову частину, яка складається зі сховища загальної інформації, унікального ідентифікатора запиту, знімку бізнес-схеми тощо. Окрім базової частини, контекст виконання може містити сесію користувача чи сесію системи, в залежності від типу приватизації. При обробці запиту в рамках життєвого циклу виконання, адаптер ws протоколу здійснить валідацію токена доступу, на основі нього здійснить пошук сесії в Redis сховищі та в разі успіху

  • наповнить контекст виконання даними сесії.

Базовий контекст

Структура базового контексту виконання складається з наступним складових:

type BusinessScheme = {
// ... business scheme description
}

type Store = {
service: string,
domain: string,
action: string,
method: string,
ip: string,
requestId: string,
schema: BusinessScheme,
language: string,
socket: WebSocket
}

type Context<U extends Record<string,any> = never, S extends Record<string,any> = never> = {
store: Store
// ... other fields
}

де:

  • service - назва сервісу.
  • domain - назва предметної області.
  • action - назва дії.
  • method - http метод.
  • ip - IP джерела взаємодії.
  • requestId - унікальний ідентифікатор запиту.
  • scheme - знімок версії бізнес-схеми.
  • language - мова, якою джерело взаємодії просить надати відповідь.
  • socket - повний обʼєкт socket зʼєднання.

Окрім базового контексту, контекст виконання може бути розширений за рахунок типу приватизації кінцевого маршруту

public:route

Тип приватизації не передбачає перевірок щодо приватності запитів та відсутності перевірки токенів доступу, оскільки вважається, що запити з типом public:route призначені для публічних маршрутів, таких як головні сторінки, опис товарів і т. д.

Результуюча структура контексту виконання

type Store = {
// base store context
}

type Context = {
store: Store
user: undefined,
system: undefined
}

private:user

Тип приватизації передбачає, що запити є конфіденційними і вимагають наявності JWT токену доступу користувача під назвою x-user-access-token. Цей токен надається у двох випадках: під час процедури авторизації при вході в веб-застосунок та при оновленні завдяки x-user-refresh-token.

порада

Для створення авторизації використовуйте вбудовані методи агента fnAgent простору імен sessions, який містить методи для організації роботи з сесіями, включаючи авторизацію. Також використовуйте методи агента fnAgent простору імен scrambler для шифрування, хешування та підтвердження.

Під час обробки запиту з типом приватності private:user, адаптер ws ядра обчислень отримає токен доступу.

1. Випадок відсутності токена доступу

В цьому випадку ws адаптер згенерує подію session:to:authentificate.error з наступним вмістом:

{
"type": "FAIL",
data: {
"code": "0002.0002.0007",
"message": "Authorization failed: miss required `x-user-access-token` http header"
}
}

2. Випадок наявності токена зі сплинутим терміном

В цьому випадку ws адаптер згенерує подію session:to:authentificate.error з наступним вмістом:

{
"type": "FAIL",
data: {
"code": "0002.0002.0008",
"message": "Authorization failed: user access token has expired."
}
}

3. Випадок дійсного токену

У випадку, коли заголовок присутній і токен є дійсним, адаптер перевіряє jwt токен на наявність sessionId. Цей sessionId створюється під час авторизації користувача, використовуючи методи з простору імен sessions в веб-застосунку і використовується для отримання інформації про сесію користувача з Redis сховища. Після успішного отримання інформації адаптер додає ці дані до контексту виконання.

type Store = {
// ... base request context
}

type Context<
U extends Record<string,any> = any,
S extends Record<string,any> = any
> = {
store: Store
user: {
sessionId: string // session unique identifier
} & U // other information about the user session
system: S // never
}
примітка

Цей механізм авторизації використовується для доступу до різноманітних ресурсів, пов'язаних з профілем користувача, таких як особисті дані, інформація про ролі, налаштування та інша приватна інформація, необхідна для забезпечення безпеки та відповідності до політик конфіденційності.

private:system

Тип приватизації передбачає, що запити є приватними і вимагають наявності jwt токену доступу до системи - x-system-access-token. Цей токен надається в трьох випадках:

  • При безпосередньому виданні токена, який може бути включений у договір або інші правові документи.
  • Під час авторизації в веб-застосунку через спеціальний маршрут, що описаний в бізнес-схемі.
  • Під час оновлення, за допомогою x-system-refresh-token, який використовується для поновлення токенів.
порада

При авторизації системи через кінцевий маршрут, використовуйте вбудовані методи агента fnAgent простору імен sessions, який містить методи для організації роботи з сесіями, включаючи авторизацію. Також використовуйте методи агента fnAgent простору імен scrambler для шифрування, хешування та підтвердження.

Під час обробки запиту з типом приватності private:system, адаптер ws ядра обчислень намагатиметься знайти токен доступу

1. Випадок відсутності заголовку

В цьому випадку ws адаптер згенерує подію session:to:authentificate.error з наступним вмістом:

{
"type": "FAIL",
data: {
"code": "0002.0002.0007",
"message": "Authorization failed: miss required `x-system-access-token` http header"
}
}

2. Випадок наявності токена зі сплинутим терміном

В цьому випадку ws адаптер згенерує подію session:to:authentificate.error з наступним вмістом:

{
"type": "FAIL",
data: {
"code": "0002.0002.0008",
"message": "Authorization failed: system access token has expired."
}
}

3. Випадок дійсного токену

У випадку, коли заголовок присутній і токен є дійсним, адаптер перевіряє jwt токен на наявність sessionId. Цей sessionId створюється під час авторизації системи, використовуючи методи з простору імен sessions в веб-застосунку або через пряму передачу токена доступу і використовується для отримання інформації про сесію системи з Redis сховища. Після успішного отримання інформації адаптер додає ці дані до контексту виконання.

type Store = {
// ... base request context
}

type Context<
U extends Record<string,any> = any,
S extends Record<string,any> = any
> = {
store: Store
user: U
system: {
sessionId: string // session unique identifier
} & S // other information about the system session
}
примітка

Зазначений тип приватизації є ключовим у забезпеченні безпеки та конфіденційності при доступі до ресурсів на маршрутах інтеграцій та побудові міжсистемної взаємодії.

Життєвий цикл виконання

Кожний обробник запиту виконується в рамках життєвого циклу виконання запиту:

ws-lifecycle.svg

1. Перевірка елементів події Перевірка валідності елементів події. Пошук відповідного сервісу, прикладної області та маршруту з ws запитом. У випадку відсутності обов'язкових елементів, ws адаптер ядра обчислень згенерує події event:service:not:found, event:service:not:found чи event:service:not:found в залежності від того, якого елемента структури не було знайдено.

2. Перевірка типу приватизації AuthScope

Простору авторизації AuthScope визначає доступність ресурсу для користувачів та систем на основі типу приватизації кінцевого маршруту.

3. Формування контексту

Формування контексту запиту для послідуючої передачі контексту виконання в обробник запиту.

4. Запуск виконання контексту запиту

Створює новий контекст для зберігання даних у AsyncLocalStorage для забезпечення ізоляції даних між різними запитами або операціями, що теоретично можуть відбуватись паралельно, з привʼязуванням до унікального ідентифікатора запиту.

5. Виконання запиту бізнес-схеми

Здійснюється виклик обробника кінцевого маршруту з передачею агентів доступу, контексту запиту та даних запиту з можливим поверненням результату.

6. Закриття контексту виконання запиту

Закриття контексту виконання запиту, логування відповіді, статус тощо.

Реалізація емітера

import { setEmitter } from "@x-fiber/proton";

export const BusUserAggEmitter = setEmitter<'new:user'>({
'new:user': {
POST: {
version: 'v1',
scope: "public:route",
handler: async (payload, agents, context) => {
const { sessions, logger } = agents.fnAgent;
const user = {
id: '550e8400-e29b-41d4-a716-446655440000',
name: 'John',
surname: 'Doe'
}

try {
await sessions.open(user);
} catch (e) {
logger.error(e)
throw e
}
},
},
},
});

Реалізація генерації

небезпека

Опис генерації подій знаходиться в активній розробці.

Реєстрація

Щоб успішно використовувати події, документ "Емітер" повинен бути зареєстрований в документі "Реєстр" цієї предметної області. Наприклад для предметної області - агрегат "Користувачі", необхідно в опис документів функції setRegistry для встановлення посилання на емітер та інші компоненти модуля:

import { setRegistry } from '@x-fiber/proton';
import { BusUsersAggEmitter } from './BusUsers.agg.emitter';

export const BusUsersAggRegistry = setRegistry<'BusUsersAgg'>('BusUsersAgg', {
emitter: BusUsersAggEmitter,
// ... other documents
});

Власні рішення

warning

X-Fiber виконує обробники запитів в рамках їх життєвого циклу, це означає, що інструменти, які не надає X-Fiber, але необхідні для реалізації бізнес-логіки є можливість поділити на два типи:

  • інструменти без створення стану - використання таких інструментів, наприклад date-fns, як бібліотека для роботи з датами, можуть бути додані в будь-якому місці попередньо встановивши їх в проєкт. X-Fiber рекомендує опис розгортання таких інструментів виносити в окрему директорію від опису бізнес-логіки.
  • інструменти зі створення стану - використання таких практик наразі X-Fiber не покривається, але рішення яке буде реалізовано в найближчих релізах це окрема дві функції - запуску та старту, які будуть запускатись після запуску та зупинки життєвого циклу роботи ядра обчислень. Ця функція може приймати ваші рішення по запуску та зупинки використання стану інструментів.

У разі впровадження власних рішень, рекомендується керуватися кращими практиками з метою поліпшення підтримки кодової бази. Якщо ви вважаєте, що інструмент, який ви використовуєте, має достатню популярність, ви можете запропонувати його включення в саму платформу X-Fiber. Ми готові обговорити цю можливість з вами, а при позитивному рішенні ми включимо цей інструмент до архітектури платформи.