Writing a Custom Backend
You can create your own backend for any messaging system -- AMQP, Kafka, gRPC, in-memory, whatever fits your infrastructure. This guide walks through building one from scratch. If you just need to use an existing custom backend, see Using a Custom Backend.
What You're Building
A backend is a class that extends BaseStrataBackend. It handles sending and receiving messages, managing queues, and optionally supporting service discovery. Strata calls your backend's methods during its lifecycle; your backend emits events when messages arrive.
Extending BaseStrataBackend
BaseStrataBackend is an abstract class that provides the event emitter interface and defines all required methods. Here's the full skeleton:
import { logging } from '@strata-js/util-logging';
import {
BackendConfig,
BaseStrataBackend,
ConcurrencyCheck,
DiscoveredServices,
KnownServiceCommand,
PostEnvelope,
RequestEnvelope,
ServiceCommandResponse,
StrataRequest,
StrataService,
} from '@strata-js/strata';
// -------------------------------------------------------------------------------------------------
const logger = logging.getLogger('myBackend');
// -------------------------------------------------------------------------------------------------
export interface MyBackendConfig extends BackendConfig
{
type : 'my-backend';
connectionUrl : string;
}
// -------------------------------------------------------------------------------------------------
export class MyBackend extends BaseStrataBackend<MyBackendConfig>
{
#initialized = false;
get initialized() : boolean { return this.#initialized; }
// ---------------------------------------------------------------------------------------------
// Lifecycle
// ---------------------------------------------------------------------------------------------
async init(config ?: MyBackendConfig) : Promise<void>
{
this.config = config;
// Connect to your messaging system here
this.#initialized = true;
logger.info('MyBackend initialized.');
}
async teardown() : Promise<void>
{
// Close connections, clean up resources
this.#initialized = false;
}
// ---------------------------------------------------------------------------------------------
// Request Handling
// ---------------------------------------------------------------------------------------------
async listenForRequests(
serviceGroup : string,
serviceID : string,
waterMarkCheck : ConcurrencyCheck
) : Promise<void>
{
// Subscribe to your message source.
// When a message arrives, emit it:
// this.emit('incomingRequest', parsedEnvelope);
// Use waterMarkCheck() to respect concurrency limits.
}
async stopListeningForRequests(serviceGroup : string) : Promise<void>
{
// Unsubscribe from the message source
}
async sendRequest(
serviceGroup : string,
envelope : RequestEnvelope | PostEnvelope,
clientID ?: string
) : Promise<void>
{
// Publish the request envelope to the appropriate queue/topic
}
async sendResponse(request : StrataRequest) : Promise<void>
{
// Send the response back to the client's response queue
}
async listenForResponses(clientID : string, clientName ?: string) : Promise<void>
{
// Subscribe for response messages.
// When a response arrives, emit it:
// this.emit('incomingResponse', parsedEnvelope);
}
async stopListeningForResponses() : Promise<void>
{
// Unsubscribe from response messages
}
// ---------------------------------------------------------------------------------------------
// Commands
// ---------------------------------------------------------------------------------------------
async listenForCommands(serviceGroup : string, serviceID : string) : Promise<void>
{
// Subscribe to command channels.
// When a command arrives, emit it:
// this.emit('incomingCommand', parsedCommand);
}
async stopListeningForCommands() : Promise<void>
{
// Unsubscribe from command channels
}
async listenForCommandResponses(clientID : string) : Promise<void>
{
// Subscribe to command response channels.
// When a response arrives, emit it:
// this.emit('incomingCommandResponse', parsedResponse);
}
async stopListeningForCommandResponses() : Promise<void>
{
// Unsubscribe from command response channels
}
async sendCommand(target : string, command : KnownServiceCommand) : Promise<void>
{
// Publish the command to the target
}
async sendCommandResponse(
target : string,
envelope : ServiceCommandResponse
) : Promise<void>
{
// Send the command response back to the caller
}
// ---------------------------------------------------------------------------------------------
// Discovery
// ---------------------------------------------------------------------------------------------
async enableDiscovery(service : StrataService) : Promise<void>
{
// Register this service instance for discovery
}
async disableDiscovery() : Promise<void>
{
// Deregister this service from discovery
}
async listServices() : Promise<DiscoveredServices>
{
// Return currently discovered services
return {};
}
}
// -------------------------------------------------------------------------------------------------Events to Emit
The critical contract between your backend and Strata is the event emitter. Your backend must emit these events when messages arrive:
| Event | When to Emit | Payload |
|---|---|---|
incomingRequest | A request or post arrives | RequestEnvelope | PostEnvelope |
incomingResponse | A response arrives | ResponseEnvelope |
incomingCommand | A service command arrives | KnownServiceCommand |
incomingCommandResponse | A command response arrives | ServiceCommandResponse |
Strata's service and client layers listen for these events and handle all processing from there.
Registering Your Backend
Once your backend class is ready, register it before starting the service or client. See Using a Custom Backend for the full registration walkthrough.
service.registerBackend('my-backend', MyBackend);
await service.start();TIP
Look at the NullBackend source in the Strata repository for a minimal working implementation. It implements every method as a no-op, which is useful as a starting template.
Contributing
If you build a backend for a messaging system that others might use (AMQP, Kafka, NATS, etc.), consider publishing it as a standalone npm package. The Strata project is open to contributions of new backends, and we'd welcome third-party backend plugins. Open an issue on GitLab if you'd like to discuss getting your backend listed in the official docs.
Next Steps
- Using a Custom Backend -- registering and configuring a custom backend.
- Backends -- built-in backend reference and common configuration.
- Envelope Validation -- what gets validated on every message.
- Service Commands -- controlling running services via commands.