@axinom/mosaic-transactional-inbox-outbox
Version:
This library encapsulates the Mosaic based transactional inbox and outbox pattern
133 lines (125 loc) • 4.36 kB
text/typescript
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);