Architecture
Wenex Platform is a distributed monorepo: one API gateway routes all external traffic to 15 domain microservices via gRPC. Workers consume events from Kafka and coordinate via BullMQ. All components share a common library (libs/common) for DTOs, guards, interceptors, and schemas.
System Context
Request Flow
A standard REST request traverses a fixed 16-stage pipeline inside the Platform Gateway before reaching any microservice, then passes through a 5-stage response pipeline on the way back.
Platform Gateway Pipeline Stages
Request side (in order)
| Group | Stages | Purpose |
|---|---|---|
| HTTP headers | XRequestId, XPoweredBy, ETag, NamingConventionReq | Add trace ID, response headers, HTTP caching, convert snake_case → camelCase on input |
| Security | AuthGuard, ScopeGuard, PolicyGuard | Validate JWT/APT, check required scopes, evaluate ABAC policy |
| Rate & cache | Cache, RateLimit | Return cached response if fresh; enforce per-collection request limits |
| Context & data | Metadata, Sentry, Authority, Field, Validation, Ownership, ValidationPipe | Extract auth context, instrument errors, apply zone/ownership filter, strip disallowed fields, validate DTO shape, enforce ownership rules |
Response side (in order)
| Stage | Purpose |
|---|---|
| Serializer | Transform entity → response shape, hide secret fields, apply projection |
| AuditLog | Record write operations for the audit trail |
| Filter | Apply post-query field filtering |
| NamingConventionRes | Convert camelCase → snake_case on output |
| NoApiResponse | Suppress NestJS default wrapper when not needed |
Client Gateway Pipeline Stages
Client applications that mirror the Platform pattern use a simpler pipeline:
| Stage | Purpose |
|---|---|
| AuthGuard | Validate the token |
| PolicyGuard | ABAC policy evaluation |
| Metadata Interceptor | Extract auth context |
| Sentry Interceptor | Error tracking |
| Validation Pipe | DTO validation |
| → Services | Business logic |
| Serializer | Output transformation |
Monorepo Structure
Gateway Internals
The gateway is the sole entry point for external traffic. It hosts three protocol surfaces simultaneously:
Service Architecture
Every domain service follows an identical internal structure:
Worker Architecture
Workers are event-driven: they consume Kafka topics and dispatch BullMQ jobs.
Data Layer
Soft Delete vs Hard Delete
All entities in MongoDB use soft-delete by default. The deleted_at timestamp is set instead of removing the document. Three lifecycle operations exist:
| Operation | HTTP | Effect |
|---|---|---|
deleteOne / deleteById | DELETE /:id | Sets deleted_at — document hidden from queries |
restoreOne / restoreById | PUT /:id/restore | Clears deleted_at — document visible again |
destroyOne / destroyById | DELETE /:id/destroy | Permanently removes document from MongoDB |
Hard delete (destroy) requires Manage scope, not just Write.
Authentication & Authorization Architecture
Metadata — The Auth Context Object
Every service method receives a Metadata object extracted from the request headers and JWT payload. It propagates through every gRPC call:
| Field | Source | Purpose |
|---|---|---|
token | JWT payload | Decoded token claims |
domain | x-domain header or token | Tenant / domain scoping |
client | x-client-id header or token | OAuth client context |
user | token sub | Authenticated user ID |
Communication Topology
Key Design Principles
Observable-first: All service methods return
Observable<T>(RxJS), never Promises. Controllers and resolvers subscribe; gRPC streams are bridged automatically.Metadata is pervasive: Every operation carries auth context, enabling ownership checks, soft-delete filtering, and audit logging without manual threading.
Consistent CRUD surface: All 15 services expose the same 14 operations (
count,create,createBulk,find,cursor,findOne,findById,updateOne,updateBulk,updateById,deleteOne,deleteById,restoreOne,destroyOne). There are no service-specific exceptions.Three-layer authorization: AuthGuard (token validity) → ScopeGuard (required scopes) → PolicyGuard (ABAC rules). A request must pass all three.
Cache coherence: Read operations use
@Cache(COLL_PATH, 'fill')to populate Redis cache. Write operations use@Cache(COLL_PATH, 'flush')to invalidate it.