A Modular Telecom BSS/OSS Suite
Modernizing a legacy telecom stack into a modular, TM Forum-aligned suite — operators license only the modules they need, and every module is its own service, its own schema, and its own event stream.
Context
A telecom solutions provider has served operators for decades with its BSS/OSS suite — the systems that run an operator's business: product catalog, customers, orders, billing, field inventory, trouble tickets. The modernization mandate was bigger than a rewrite: turn the suite into a composable product, where a customer can license just a product catalog and a CRM today, add billing next year, and never deploy — or pay for — the modules they don't use. That commercial model is an architectural requirement, not a pricing table.
Constraints
- Sell any subset. The deployable unit is always the platform core plus whatever modules the customer licensed. Every module must install, run, and upgrade independently — some are even standalone-capable when their sibling modules aren't deployed.
- Strict isolation. One Spring Boot service and one PostgreSQL schema per module — no cross-schema foreign keys, ever. Modules reference each other by ID and validate through APIs or events. The default physical layout puts those schemas in four databases on one server; because ownership is logical, any module can be lifted onto its own instance by changing one environment variable, with no code change.
- Audit that can't block business. Telecom operations demand full audit trails, but a logging outage must never stop an order. Every audit event is written to the module's own transactional outbox in the same database transaction, then published asynchronously.
- One authority for identity. Modules never own users, roles, or permissions. A central auth service issues RS256 JWTs carrying roles, permissions, and licensed modules; everything downstream just verifies.
- Two audiences, one platform. Operator staff and the operator's own subscribers use the same deployment but must never share an attack surface. Customers live in a separate identity realm with their own token type — and a filter at the gateway rejects a customer token on any staff or module route outright.
- Speak the industry's language. APIs align with TM Forum Open API standards — TMF620 catalog, TMF622 ordering, TMF678 billing, TMF621 trouble ticketing — so the suite plugs into ecosystems operators already know.
Architecture
light = data. amber: request path · blue: the event backbone feeding audit, notifications, and reporting
How it's put together
- Fifteen Spring Boot services, a schema apiece, two Angular apps. A platform core — auth, gateway, logs/audit, notifications, and a self-care BFF — plus ten independently deployable business modules: catalog, CRM, orders, billing, warehouse, network inventory, trouble tickets, projects, reporting, and an integration hub. The staff console and the customer portal are separate Angular 22 applications behind the same gateway.
- Licensing lives in the token. The auth service records which modules each tenant licensed and stamps them into the JWT. The gateway refuses routes for unlicensed modules at the edge, and every module re-checks its own entitlement as defense-in-depth. The UI reads the same claims — unlicensed modules simply never appear.
- Events over coupling. Every state change writes to the module's own outbox table inside the business transaction; a poller publishes to that module's Kafka topic in a TMF688-style envelope. Cross-module flows ride the same rails — an order event reserves warehouse hardware and fires a fulfillment project without either module calling the other synchronously.
- Reporting without reaching in. The reporting module is a pure Kafka consumer: it builds append-only fact tables from every module's events, deduplicates idempotently, and never queries another module's database. Executive KPIs come from the event stream, not from cross-database joins.
- A customer portal that can't overreach. Subscribers log into a separate identity realm that mints a distinct token type — no staff permissions, no module claims. Those tokens reach exactly one surface: a self-care backend-for-frontend whose every query is scoped to the accounts the caller actually owns. Ownership is enforced in the BFF, not inferred from a customer ID in the request, and the gateway drops customer tokens on every other route.
- AI insights with governance. Every module ships AI-assisted suggestions — pricing anomalies, churn signals, ticket triage. Accepting a suggestion only flips its review status; an AI is never allowed to mutate business data directly.
- Operable, not just runnable. Every service exposes Prometheus metrics and stamps a correlation ID through logs and events; audit records stay in Postgres as compliance data, while high-volume application logs ship as structured JSON to Loki for search — two different problems, two different stores. CI, scripted database backups, and file-based JWT signing keys came with the same hardening pass.
Why it's built this way
The architecture is the business model. Because each module is a sealed unit — its own service, schema, migrations, and event topic — "which modules did you license?" is answered by what gets deployed, not by feature flags in a monolith. Module boundaries double as blast-radius boundaries: a billing incident can't lock a CRM table, and a Kafka outage delays audit trails without ever blocking an order. Keeping ownership logical rather than physical is what makes that affordable: a small operator runs every schema on one Postgres server, while a large one moves billing onto dedicated hardware by editing a connection string — the isolation rules were never about the box the data sits on. The discipline costs something up front — outboxes instead of joins, events instead of foreign keys — but it buys the two things a twenty-year platform needs most: modules can be added without surgery on the ones already running, and every module can evolve, scale, and fail alone.