@Echoes() and @EchoRequest() or @EchoEvent() decorators, you can easily create handlers for both synchronous requests and asynchronous events.
Standalone core and Orion integration
@orion-js/echoes is framework-independent. Its required dependency graph does not include
@orion-js/schema, @orion-js/services, @orion-js/http, @orion-js/logger, or any other Orion
runtime package.
Orion applications keep their existing behavior through @orion-js/echoes-orion. It connects
Echoes to Orion schemas, dependency injection, async logger context, HTTP routes, and Orion error
classes:
@orion-js/components do not need this explicit import because Components
installs the adapter automatically.
Schema execution is still runtime behavior, not just TypeScript inference. For every handler,
Echoes cleans and validates
params before calling resolve, then cleans returns before
sending the result. The Orion adapter delegates those operations to @orion-js/schema, so
existing custom cleaners, defaults, validations, and ValidationError values keep working.Creating Echo Controllers
An echoes controller is a class decorated with@Echoes() that contains methods decorated with @EchoRequest() or @EchoEvent():
Echo Request Handlers
Use the@EchoRequest() decorator with the createEchoRequest() function to define methods that handle synchronous requests from other services:
Echo Event Handlers
Use the@EchoEvent() decorator with the createEchoEvent() function to define methods that process asynchronous events:
Event Decorator Options
ThecreateEchoEvent() function accepts options similar to createEchoRequest():
consumerGroup + topic configuration. Increase the listener’s integer
configVersion whenever persisted retry or offset settings change:
maxConcurrency on an event to override
events.pulse.subscription.maxConcurrency for that receiver.
Batch event handlers with Pulse
Use@EchoBatchEvent() when one receiver invocation should process multiple events together:
params and individual context, including eventId,
headers, creation time, and attempt number. batchSize is a maximum; Pulse immediately invokes the
handler with whatever is available and never waits to fill the array. One resolveBatch invocation
consumes one worker and one maxConcurrency slot.
The complete array is one retry and acknowledgement unit. If the handler throws, Pulse retries the
whole delivery, so bulk side effects should use each context.eventId as an idempotency key. Batch
event receivers are Pulse-only and require a transaction-capable MongoDB deployment. Kafka
consumption fails at startup when a batch receiver is configured.
For a hot rollout, first deploy the new handler and packages with batchSize: 1. After every replica
understands batch deliveries, increase configVersion and set the intended size. Processes using a
normal handler can consume a multi-event delivery sequentially, while batch handlers naturally
receive an older one-event delivery as an array of length one.
Making Requests
To make a request to another service:Publishing Events
To publish an event for other services to consume:Starting the Echoes Service
To enable echoes in your application, you need to configure and start the echoes service:Using Echoes without Orion
A standalone service can use a SimpleSchema-compatible object directly. Echoes recognizes objects withclean() and validate() methods and executes both at runtime. typedEchoesSchema() adds
the value type when the schema library does not expose it in a form TypeScript can infer:
kafkajs for
Kafka, or @orion-js/pulse with its MongoDB peer dependency for Pulse. A service that only uses
synchronous requests loads neither event transport.
Incremental migration from Kafka to Pulse
Echoes can listen through Kafka and Pulse at the same time while publishing through exactly one transport. This allows the migration to happen in three independent deployments, without dual-publishing an event. Install Pulse and its MongoDB peer dependency in the application:1. Prepare every listener
Keep the existing top-level Kafka configuration and add Pulse underevents. Deploy this version
to every instance before changing any publisher:
publish() still sends only to Kafka.
The event handler API does not change.
The Pulse transport uses a MongoDB application pool of at most one connection per service instance
by default. Callbacks remain concurrent because they do not retain the pool socket. Set
events.pulse.maxPoolSize only when a specific high-throughput service has measured pool
contention. events.pulse.maxIdleTimeMS defaults to 30 seconds and lets larger pools contract after
bursts. events.pulse.discoveryLockTimeoutMs independently tunes discovery-leader failover;
handler locks continue using events.pulse.lockTimeoutMs.
2. Switch publishers
Once every listener is running the dual-consumer configuration, change onlypublishTo:
publishTo can also come from an environment flag:
3. Remove Kafka consumption
After every publisher uses Pulse and Kafka has no pending events for the service, stop consuming from Kafka:client, consumer, and producer options can then be removed from that application.
If only Pulse is configured, Echoes defaults both consumption and publishing to Pulse.
Pulse is an optional peer dependency and is loaded only when configured. Applications that stay on
Kafka do not need to install or initialize it.
Error Handling
Echoes automatically handles errors in request and event handlers:Custom Error Types
Orionjs handles special error types appropriately:- UserError: For expected application errors
- ValidationError: For data validation errors
Type Safety
Using TypeScript with schema inference, you can ensure type safety for your echo handlers:Best Practices
- Organize by Domain: Group related echo handlers in the same controller class.
-
Leverage Dependency Injection: Use
@Inject(() => Service)to access repositories and services. - Keep Methods Focused: Each echo handler should have a clear, single responsibility.
- Use Strong Typing: Define parameter and return types with schemas and infer TypeScript types.
- Handle Errors Gracefully: Catch and properly categorize errors.
- Idempotent Handlers: Design event handlers to be idempotent (safe to process the same event multiple times).
- Timeout Configuration: Set appropriate timeouts for requests based on expected execution time.
- Security: Use the shared key to secure inter-service communication.
- Service Discovery: Keep the service registry updated when adding new services.
- Monitoring: Implement proper logging and monitoring for echo handlers.