UNPKG

@axinom/mosaic-transactional-inbox-outbox

Version:

This library encapsulates the Mosaic based transactional inbox and outbox pattern

133 lines (125 loc) 4.36 kB
import { MessageEnvelopeOverrides, MessagePublicationConfig, MessagingRegistry, } from '@axinom/mosaic-message-bus'; import { MessagingSettings } from '@axinom/mosaic-message-bus-abstractions'; import { randomUUID } from 'node:crypto'; import { DatabaseClient, ListenerConfig, TransactionalMessage, initializeMessageStorage, } from 'pg-transactional-outbox'; import { InboxOutboxLogger } from '../common'; import { TransactionalLogMapper } from '../transactional-log-mapper'; /** * Helper interface for outbox message metadata, that includes the message * envelope overrides and publication config which are used to send a RabbitMQ * message. */ export interface RabbitMqOutboxMetadata { /** overrides for Mosaic Envelope properties (ex: auth_token) */ envelopeOverrides?: MessageEnvelopeOverrides; /** additional rabbitMq configuration (ex: composed routing key for the specific environment) */ options?: MessagePublicationConfig; /** if the message is a command or an event */ action?: 'command' | 'event'; [key: string]: unknown; } /** * Add additional data to the outbox message that is going to be stored * @param envelopeOverrides Explicitly defined message envelope values, e.g. auth token or message context * @param options Explicitly defined message options, e.g. if additional header values should be passed * @param concurrency Define if the message must be sent in the sequential order it was added or if it can be sent in parallel with others. * @param lockedUntil The date and time in ISO 8601 "internet time" UTC format (e.g. "2023-10-17T11:48:14Z") until when the message cannot be sent out * @param segment Can be set to allow processing of messages with different segments in parallel. */ export interface StoreOutboxMessageAdditionalData { envelopeOverrides?: MessageEnvelopeOverrides; options?: MessagePublicationConfig; concurrency?: 'sequential' | 'parallel'; lockedUntil?: string; segment?: string; } /** * Function to store the transactional outbox message * @param aggregateId The (database) ID of the aggregate type or use the const UNKNOWN_AGGREGATE_ID or MULTIPLE_AGGREGATE_IDS values. * @param messagingSettings The messaging related settings object * @param payload The payload of the message * @param client The database client to use to store the outbox message * @param optionalData Add additional data to the outbox message that is going to be stored */ export type StoreOutboxMessage = <T>( aggregateId: string, messagingSettings: Pick< MessagingSettings, 'messageType' | 'aggregateType' | 'action' >, payload: T, client: DatabaseClient, optionalData?: StoreOutboxMessageAdditionalData, ) => Promise<void>; const storeOutboxMessageKey = 'storeOutboxMessage'; /** * Returns a function to store transactional outbox messages. * See the StoreOutboxMessage type for parameter descriptions. */ export const setupOutboxStorage = ( listenerConfig: ListenerConfig, logger: InboxOutboxLogger, config: { logLevel: string }, app?: MessagingRegistry, ): StoreOutboxMessage => { const storage = initializeMessageStorage( listenerConfig, new TransactionalLogMapper(logger, config.logLevel), ); const storeOutboxMessage = async <T>( aggregateId: string, { aggregateType, messageType, action, }: Pick<MessagingSettings, 'messageType' | 'aggregateType' | 'action'>, payload: T, client: DatabaseClient, { envelopeOverrides, options, concurrency, lockedUntil, segment, }: StoreOutboxMessageAdditionalData = {}, ): Promise<void> => { const metadata: RabbitMqOutboxMetadata = { envelopeOverrides, options, action, }; const message: TransactionalMessage = { id: randomUUID(), aggregateId, payload, aggregateType, messageType, metadata, concurrency: concurrency ?? 'parallel', lockedUntil, segment, }; await storage(message, client); }; if (app) { app.set(storeOutboxMessageKey, storeOutboxMessage); } return storeOutboxMessage; }; /** * Retrieve the storeOutboxMessage instance from the MessagingRegistry * @param app * @returns */ export const getStoreOutboxMessage = ( app: MessagingRegistry, ): StoreOutboxMessage => app.get(storeOutboxMessageKey);