G-SERVICE Docs
Архитектура ISP (OSS/BSS)

Мультитенантность (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 CoreCustomer
Product & SubscriptionProductOffering, Subscription
Billing & FinanceAccount, Transaction, Invoice
NotificationNotification
OMSOrder
Network InventoryDevice, Port
ProvisioningProvisioningState
AAASession
MediationUsageRecord

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-контрактами:

EnumProto-источникИспользование
CurrencyCodecommon.v1.Money.currency_codeBilling, Product
PaymentMethodcommon.v1.PaymentMethodBilling
ServiceTypecommon.v1.ServiceTypeProduct, Provisioning
ConnectionTechnologycommon.v1.ConnectionTechnologyProvisioning, Inventory
BillingModelcommon.v1.BillingModelProduct, Billing
SortDirectioncommon.v1.SortDirectionВсе сервисы (pagination)

Ссылки по теме

On this page