Сервіс
Наша стратегія розробки базується на ідеї використання сервісів як окремих компонентів функціональності, які можна розглядати і розробляти незалежно один від одного. Кожен сервіс відповідає за виконання конкретного завдання або надання певної послуги.
Замість того, щоб будувати веб-застосунок як єдину монолітну систему, ми розглядаємо його як модульний моноліт - сукупність таких сервісів, які можуть взаємодіяти один з одним. Це дає можливість гнучко конфігурувати та масштабувати систему, додаючи або вилучаючи окремі сервіси за потреби.
Особливість цього підходу полягає в тому, що розробники можуть працювати над різними сервісами незалежно один від одного, зосереджуючись на своїй області компетенції або відповідальності. Це сприяє швидкому впровадженню нових функцій та зменшує ризик виникнення конфліктів між різними командами.
Такий підхід також сприяє легкості підтримки, оскільки проблеми, які виникають у конкретних сервісах, можуть бути ізольовані і вирішені без впливу на решту системи. Також він полегшує вступ нових розробників у проєкт, оскільки їм не потрібно розуміти всю систему цілком, а лише той сервіс, над яким вони працюють.
Архітектура
Реєстрація сервісів або групи сервісів здійснюється в конкретному ядрі обчислень, що забезпечує точність та ефективність їхньої роботи. Однак існує можливість використання комбінованої реєстрації, де один сервіс діє на одному ядрі обчислень, тоді як група інших сервісів працює на іншому ядрі обчислень. Також можливе розміщення кожного сервісу на окремому ядрі з метою максимізованого залучення ресурсів.
За взаємодію ж між сервісами відповідає документ "Брокер повідомлень" broker
, який описується в конкретних предметних
областях. Детальніше про взаємодію між сервісами та групування сервісів в конкретних ядрах обчислень дивись:
Склад
Структура сервісу має слідуючий вигляд:
type RegistryStructure = {
// ... registry description array
}
type ServiceDocumentation<N extends string = string> = {
// ... service documentation description
}
type ServiceStructure<S extends string = string> = {
service: S;
domains: RegistryStructure[];
documentation?:
| ServiceDocumentation
| ServiceDocumentation[]
| null;
};
export const setService = <S extends string = string>(
service: S,
domains: RegistryStructure[],
documentation?:
| ServiceDocumentation
| ServiceDocumentation[]
| null
): ServiceStructure<S> => {
return { service, domains, documentation };
};
де:
- Типи:
RegistryStructure
- документ "Реєстратор", який відповідає за реєстрацію похідних документів предметної області.ServiceDocumentation
- детального опису функціональності, інтерфейсів та ін. сервісу конкретною мовою перекладу, будь-тоen
,ru
чи ін.ServiceStructure
- сформований обʼєкт сервісу для подальшої реєстрації вsetServices
ядра обчислень.setService
- вбудована функція в ядро обчислень, яка призначена для опису сервісів бізнес-схеми.S
- строковий тип назви сервісу, який повинен бути унікальним в рамках бізнес-схеми одного ядра обчислень.- Аргументи:
service
- ідентифікаційний тег, який визначає назву сервісу.domains
- перелік предметних областей, які охоплює конкретний сервіс.documentation
- детальний опис функціональності, інтерфейсів, параметрів виклику та іншої важливої інформації про сервіс. Ця документація допомагає розробникам та іншим зацікавленим особам розуміти, як користуватися сервісом.
- Результат:
- Сформований опис бізнес-схеми сервісу, який необхідно зареєструвати в
setServices
ядра обчислень.
При описі кожного сервісу важливо уникати дублювання опису функціональності, яка вже визначена в інших сервісах. Це допомагає уникнути зайвої реєстрації сервісів та зберігає чистоту архітектури системи. Окремо кожний сервіс повинен мати унікальну назву, оскільки завантажувач бізнес-схеми при завантаженні та запису складових сервісу орієнтується на його назву.
Реалізація
Ми рекомендуємо здійснювати декомпозицію програмного коду на модулі. Це сприяє збереженню структурованості, зменшенню залежностей між різними частинами програми, полегшує тестування та налагодження к одової бази. Декомпозиція на модулі дозволяє кожному модулю мати чітко визначену відповідальність та набір функцій або методів, що реалізують цю відповідальність.
Декомпозиція коду на модулі полягає у розділенні програмного коду на невеликі, самодостатні блоки, які відповідають
за конкретні функціональні області програми. X-Fiber
надає перелік вбудованих документів - складові опису предметної області.
import { setService } from 'x-fiber/proton'
import {
BusUsersAgg,
BusUsersAuthSpec,
BusUsersRolesSpec,
BusUsersRolesPermissionsSpec
} from './bus.users'
export const Service1 = setService<"BusinessAdmin" | "Accounting">("BusinessAdmin", [
BusUsersAgg,
BusUsersAuthSpec,
BusUsersRolesSpec,
BusUsersRolesPermissionsSpec,
]);
де:
BusinessAdmin
- назва сервісу.[BusUsersAgg, BusUsersAuthSpec, ...]
- масив функціональних сфер.
Декомпозиція коду має ряд переваг:
- Розбиття коду на модулі допомагає утримувати програму організованою та легко читабельною.
- Модулі дозв оляють уникнути сильних залежностей між різними частинами програми.
- Кожен модуль може бути протестований окремо, що полегшує виявлення та виправлення помилок
- Модульна структура дозволяє використовувати код у різних частинах програми або навіть у різних програмах.
Однак, в деяких випадках, може знадобитися створити опис сервісу в одному файлі, об'єднуючи різні модулі. Ц е може бути доцільно, наприклад, якщо сервіс є досить малим або суто з навчальних мотивацій.
import { setService } from 'x-fiber/proton'
export const Service1 = setService<"BusinessAdmin" | "Accounting">("BusinessAdmin", [
[
{
domain: "BusUsers",
documents: {
router: {
"v1/send-status": {
GET: {
scope: "public:route",
handler: async (request, agents, context) => {
const resource = agents.schemaAgent.getResource<{
BusUsers: { createSuccess: string }
}>('BusUsers.sendSuccessStatus')
console.log(resource) // successful send status 204.
return {
format: "status",
payload: {
statusCode: 204,
},
};
},
},
},
},
dictionaries: [
{
language: 'en',
dictionary: {
BusUsers: {
sendSuccessStatus: 'successful send status 204.'
}
}
}
]
},
},
{
domain: "BusUsersAuthSpec",
documents: {},
},
],
[
{
language: "uk",
description: {
description: {},
release: "v1.0.0",
},
},
]
]);
При створенні веб-застосунків X-Fiber
рекомендує нормалізовувати бізнес-схему шляхом поділу бізнес-логіки на складові
- предметні області. Логіку роботи предметних областей поділяти на відповідні документи.
Взаємодія між сервіса ми
Спілкування між сервісами відбувається в рамках брокерів повідомлень, реалізованих в рамках предметних областей цих
сервісів. Комунікація здійснюється завдяки технології RabbitMQ
та через протокол AMQP
. Протокол складається з трьох складових:
- Повідомлення
Message
- одиниця інформації одного запиту. Повідомлення передаються між сервісами завдяки генерації подій та їх прослуховувань. - Продюсер
Producer
- генерує та поширює події. Продюсер відповідає за ініціацію події, яка може генеруватись при виконанні іншої дії, планувальником задач тощо, та передає її в необхідну чергу повідомлень. - Слухач
Listener
- реагує та виконує певні дії з повідомленням. Слухач підписується на відповідну чергу повідомлень та отримує повідомлення в разі їх генерації продюсером / продюсерами.
Предметна область
Кожний сервіс складається з групи предметних областей. Предметна область при цьому описується переліком документів. Методологія створення та реалізація предметної області описується в наступних розділах:
📄️ Реалізація
📚 Опис реалізації предметної області
📄️ Методологія
📚 Методологія побудови предметних областей