� Архітектура CQRS у NestJS — SmartFeed Studio
📌 Чому CQRS (Command Query Responsibility Segregation)?
У звичайних монолітних архітектурах один сервіс (наприклад, UserService) часто виконує і валідацію, і бізнес-логіку, і запис у базу, і генерацію JWT токенів, що призводить до циклічних залежностей та розростання “божественних” об’єктів (God Objects).
У SmartFeed Studio застосовано суворий патерн CQRS за допомогою бібліотеки @nestjs/cqrs:
flowchart TD Client["🌐 Клієнтський Запит (HTTP)"] --> Controller["🎮 Controller (REST Endpoint)"]
subgraph CQRS["⚙️ NestJS CQRS Buses"] Controller -->|Зміна стану / Запис| CB["🚌 CommandBus"] Controller -->|Отримання даних / Читання| QB["🚌 QueryBus"]
CB --> HandlerC["✍️ CommandHandler\n(CreateUser, SelectTariffPlan, etc.)"] QB --> HandlerQ["📖 QueryHandler\n(GetUserByEmail, GetMyLicense, etc.)"]
HandlerC -.->|Публікація події| EB["📢 EventBus"] EB --> HandlerE["👂 EventHandler\n(UserCreatedEventHandler -> Auto-license)"] end
HandlerC --> DB[("🗄 PrismaService (Write)")] HandlerQ --> DB[("🗄 PrismaService (Read)")] HandlerE --> DB[("🗄 PrismaService (Provision)")]🏛 Модульне Розмежування (Single Responsibility Principle)
-
UsersModule(Рівень Збереження Даних):- Відповідає тільки за сутність
Userу PostgreSQL. - Ніколи не імпортує
AuthModule, JWT, Passport або токени. - Обробляє команди:
CreateUserCommand,ResetPasswordCommand,ChangePasswordCommand. - Обробляє запити:
GetUserByEmailQuery,GetUserByIdQuery,GetUsersListQuery,GetUsersStatsQuery.
- Відповідає тільки за сутність
-
AuthModule(Орган Авторизації та Видачі Токенів):- Керує ендпоінтами
/auth/register,/auth/login,/auth/refresh,/auth/me,/auth/forgot-password,/auth/reset-password. - Спілкується з
UsersModuleвиключно через шиниCommandBusтаQueryBus:// При реєстрації:await this.commandBus.execute(new CreateUserCommand(email, password, fullName, role));// При логіні:const user = await this.queryBus.execute(new GetUserByEmailQuery(email));
- Керує ендпоінтами
-
LicensesModule(Асинхронна Реакція на Події):- Слухає подію
UserCreatedEventна шиніEventBus. - Автоматично генерує ліцензійний ключ формату
SF-STARTER-XXXX-XXXX-XXXX, прив’язує активний план та обчислює термін діїexpiresAt.
- Слухає подію
-
PlansModule(Керування Тарифами):- CRUD для тарифних планів:
CreateTariffPlanCommand,UpdateTariffPlanCommand,DeleteTariffPlanCommand,GetTariffPlansQuery.
- CRUD для тарифних планів: