Skip to content

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:

typescript
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:

EventWhen to EmitPayload
incomingRequestA request or post arrivesRequestEnvelope | PostEnvelope
incomingResponseA response arrivesResponseEnvelope
incomingCommandA service command arrivesKnownServiceCommand
incomingCommandResponseA command response arrivesServiceCommandResponse

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.

typescript
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