Inter-Service Communication (NATS)
Relevant source files
The following files were used as context for generating this wiki page:The project utilizes a microservices architecture where NATS (Neural Autonomic Transport System) serves as the central message broker for all inter-service communication. The API Gateway is the primary producer of NATS messages, forwarding client requests to the appropriate backend microservices. This decoupled approach allows services to be scaled and maintained independently.
Communication is primarily based on a request-response pattern, where the Gateway sends a message and awaits a reply. The system also employs a publish-subscribe model for specific events, such as the distribution of the JWT public key. All communication is governed by a predefined set of event patterns, ensuring a consistent and type-safe contract between services.
Sources: SETUP.md, apps/gateway/README.md, deploys/README.md
Architecture Overview
The system is composed of a single API Gateway that acts as an entry point for clients. The Gateway communicates with various backend microservices (e.g., User, Product, Cart) exclusively through NATS. Each microservice has its own dedicated PostgreSQL database and is responsible for a specific business domain.
Sources: SETUP.md, deploys/README.md
The following diagram illustrates the high-level communication flow.
This diagram shows how the API Gateway uses NATS to communicate with backend microservices.
Sources: SETUP.md, deploys/README.md
NATS Client Registration
In the API Gateway, NATS clients for each microservice are registered and injected into the controllers. The BaseGatewayController provides a standardized way to interact with these clients. Each service is identified by a unique token (e.g., USER_SERVICE, PRODUCT_SERVICE).
The service names and their corresponding NATS client tokens are defined centrally.
| Service Name | Injection Token |
|---|---|
user-service | USER_SERVICE |
product-service | PRODUCT_SERVICE |
order-service | ORDER_SERVICE |
payment-service | PAYMENT_SERVICE |
reporter-service | REPORTER_SERVICE |
Sources: libs/shared/events.ts:91-97, apps/gateway/REFACTORING-GUIDE.md
Event-Driven Contracts
Communication is standardized through a central EVENTS object, which defines the NATS message patterns for every action. This acts as a contract between the Gateway and the microservices.
Sources: libs/shared/events.ts:1-88
Example Event Patterns
| Service | Action | NATS Pattern |
|---|---|---|
| User | Find by ID | user.findById |
| Auth | Login | auth.login |
| Product | Get by Slug | product.getBySlug |
| Cart | Add Item | cart.addItem |
| Order | Create | order.create |
| Payment | Process Payment | payment.process |
| AR | Create Snapshot | ar.snapshotCreate |
Sources: libs/shared/events.ts:2-88
Communication Patterns
The primary communication method is request-response, but the system also uses publish-subscribe for specific use cases like key distribution.
Request-Response Flow
The Gateway controllers use a send method to dispatch a message to a microservice and wait for a response. The BaseGatewayController abstracts this logic, providing a type-safe method for all child controllers.
Sources: apps/gateway/REFACTORING-GUIDE.md
The following sequence diagram shows a typical request to get a product by its ID.
This diagram shows the sequence of a request from a client to the Gateway, through NATS to a microservice, and the response back.
Sources: apps/gateway/REFACTORING-GUIDE.md
Resiliency: Timeout and Retry
To handle transient failures and unresponsive services, all NATS requests from the Gateway are wrapped with timeout and retry logic. A request will time out after 5 seconds. If it fails (e.g., due to timeout or a service error), it will be retried once after a 1-second delay. If the retry also fails, an HttpException is thrown.
The following code snippet from the Gateway illustrates this implementation:
// Example from apps/gateway/README.md
async login(dto: LoginDto) {
return firstValueFrom(
this.userService.send(EVENTS.AUTH.LOGIN, dto).pipe(
timeout(5000), // Timeout after 5 seconds
retry({ // Retry 1 time if fail
count: 1,
delay: 1000, // Wait 1 second before retry
}),
catchError(error => {
// ... error handling
}),
),
);
}
Sources: apps/gateway/README.md:100-116, libs/shared/utils/nats-utils.spec.ts
The flow for sending a message is as follows:
This flowchart shows the retry and error handling logic when sending a NATS message.
Sources: apps/gateway/README.md:100-116, libs/shared/utils/nats-utils.spec.ts:25-41
Publish-Subscribe for Key Distribution
A notable use of the publish-subscribe pattern is for distributing the RSA public key for JWT verification.
- On startup, the
user-apppublishes its public key to a NATS subject (auth.public-key). - The
gatewayand other microservices subscribe to this subject. - Upon receiving the key, they cache it in memory to verify JWTs locally without needing to query the
user-appfor each request.
This mechanism allows for efficient, decentralized token verification and supports dynamic key rotation.
Sources: deploys/README.md