Мультитенантность (Multi-Provider)
Архитектура мультипровайдерности — как платформа обслуживает несколько юридических лиц (ISP, платёжных агентов, реселлеров) в одном инстансе.
Мультитенантность (Multi-Provider)
Платформа спроектирована для обслуживания нескольких юридических лиц (провайдеров) в едином инстансе. Это позволяет холдинговым структурам, управляющим компаниям и платёжным агентам работать в одной системе с полной изоляцией данных.
Контекст: В российском телеком-рынке распространены холдинги из нескольких ISP (разные лицензии, регионы, бренды), а также модель платёжного агента — отдельное юрлицо, принимающее платежи от имени нескольких провайдеров. Архитектура multi-provider покрывает оба кейса.
Модель данных
Provider (Провайдер)
Провайдер — юридическое лицо, владеющее клиентами, подписками и финансовыми данными.
// packages/nestjs-common/src/domain/tenant.ts
interface Provider {
id: string;
code: string; // Уникальный код (slug)
name: string; // Торговое название
legalName: string; // Полное юридическое наименование
inn: string; // ИНН
legalEntityType: LegalEntityType; // ISP | PAYMENT_AGENT | MVNO | RESELLER
status: ProviderStatus; // ACTIVE | SUSPENDED | TERMINATED
createdAt: Date;
updatedAt: Date;
}Типы юридических лиц
| Тип | Описание | Пример |
|---|---|---|
ISP | Интернет-провайдер с лицензией | ООО «Ростелеком-Регион» |
PAYMENT_AGENT | Платёжный агент (54-ФЗ) | ООО «Единый платёжный центр» |
MVNO | Виртуальный оператор мобильной связи | ООО «Тинькофф Мобайл» |
RESELLER | Реселлер (продаёт услуги от имени ISP) | ИП «Партнёр-Телеком» |
Изоляция данных
Принцип: Row-Level Isolation
Каждая доменная сущность содержит providerId — UUID провайдера-владельца. Все запросы к данным фильтруются по providerId.
┌─────────────────────────────────────────────┐
│ HTTP Request │
│ Header: x-provider-id: <uuid> │
├─────────────────────────────────────────────┤
│ TenantInterceptor │
│ → Извлекает providerId │
│ → Прикрепляет TenantContext к request │
├─────────────────────────────────────────────┤
│ @Tenant() decorator │
│ → Извлекает TenantContext в controller │
├─────────────────────────────────────────────┤
│ Use Case → Repository │
│ → Все запросы WHERE provider_id = :id │
└─────────────────────────────────────────────┘Затронутые сущности
Каждая сущность во всех 9 сервисах содержит providerId:
| Сервис | Сущности с providerId |
|---|---|
| Customer Core | Customer |
| Product & Subscription | ProductOffering, Subscription |
| Billing & Finance | Account, Transaction, Invoice |
| Notification | Notification |
| OMS | Order |
| Network Inventory | Device, Port |
| Provisioning | ProvisioningState |
| AAA | Session |
| Mediation | UsageRecord |
Drizzle ORM Schema
Каждая таблица содержит колонку provider_id:
import { pgTable, uuid } from 'drizzle-orm/pg-core';
export const customers = pgTable('customers', {
id: uuid('id').primaryKey().defaultRandom(),
providerId: uuid('provider_id').notNull(), // ← tenant isolation
// ...остальные колонки
});Платёжный агент
Модель
Платёжный агент — отдельное юрлицо, принимающее платежи от имени одного или нескольких провайдеров. Это требование 54-ФЗ для операторов, не имеющих собственной кассы.
interface PaymentAgent {
id: string;
code: string;
name: string;
legalName: string;
inn: string;
commissionPercent: number; // Комиссия агента (%)
status: PaymentAgentStatus;
providerIds: string[]; // Список обслуживаемых провайдеров
createdAt: Date;
updatedAt: Date;
}Settlement (Взаиморасчёт)
По итогам периода формируется акт взаиморасчёта между агентом и провайдером:
interface Settlement {
id: string;
paymentAgentId: string;
providerId: string;
periodStart: Date;
periodEnd: Date;
totalCollected: Money; // Собрано всего
commissionAmount: Money; // Комиссия агента
netAmount: Money; // К перечислению провайдеру
transactionCount: number;
settledAt: Date | null;
createdAt: Date;
}Схема взаимодействия
┌──────────────┐ платёж ┌──────────────────┐
│ Абонент │ ──────────────→ │ Платёжный агент │
│ (Provider A)│ │ (ООО «ЕПЦ») │
└──────────────┘ └────────┬─────────┘
│ settlement
▼
┌──────────────────┐
│ Provider A │
│ (ООО «ISP-1») │
└──────────────────┘Реализация в коде
1. TenantInterceptor (глобальный)
Извлекает providerId из заголовка x-provider-id и прикрепляет к запросу:
// packages/nestjs-common/src/common/interceptors/tenant.interceptor.ts
@Injectable()
export class TenantInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler) {
const req = context.switchToHttp().getRequest();
const providerId = req.headers['x-provider-id'];
if (!providerId) {
throw new BadRequestException('Missing x-provider-id header');
}
req.tenantContext = { providerId };
return next.handle();
}
}2. @Tenant() decorator
Извлекает TenantContext из запроса в controller:
// Controller
@Get('accounts/by-customer/:customerId')
findOrCreateByCustomer(
@Tenant() tenant: TenantContext,
@Param('customerId') customerId: string,
) {
return this.getOrCreateAccount.execute(tenant.providerId, customerId);
}3. Доменная модель (TenantScoped)
Все tenant-scoped сущности реализуют интерфейс TenantScoped:
// packages/nestjs-common/src/domain/tenant.ts
interface TenantScoped {
readonly providerId: string;
}
// Пример: Account extends AggregateRoot, TenantScoped
interface Account extends AggregateRoot, TenantScoped {
customerId: string;
balance: Money;
status: AccountStatus;
}Shared Enums (nestjs-common)
Все кросс-сервисные перечисления вынесены в @repo/nestjs-common/domain и выровнены с proto-контрактами:
| Enum | Proto-источник | Использование |
|---|---|---|
CurrencyCode | common.v1.Money.currency_code | Billing, Product |
PaymentMethod | common.v1.PaymentMethod | Billing |
ServiceType | common.v1.ServiceType | Product, Provisioning |
ConnectionTechnology | common.v1.ConnectionTechnology | Provisioning, Inventory |
BillingModel | common.v1.BillingModel | Product, Billing |
SortDirection | common.v1.SortDirection | Все сервисы (pagination) |
Ссылки по теме
- BSS-сервисы: BSS Layer — Customer, Product, Billing, Notification.
- OSS-сервисы: OSS Layer — Inventory, Provisioning, AAA, Mediation.
- Принципы: Принципы архитектуры — DDD, SOLID, Repository Pattern.
- API-контракты: API-контракты — Protobuf, ConnectRPC.