Skip to Content
PortuguêsDocumentação JumentixPacotes@jumentix/message-mediatorUso do message-mediator

@jumentix/message-mediator — guia de uso

Responsabilidade no escopo

  • Camada: messaging / adaptador de aplicação
  • Responsável por: mediação pub/sub e request em processo ou via broker
  • Usado com: use-cases de backend
  • Não responsável por: rotas OpenAPI ou persistência de banco

O que é

@jumentix/message-mediator desacopla publishers de eventos de handlers e suporta request/response sobre contratos nomeados. O adaptador in-memory roda no browser e nos testes unitários; adaptadores RabbitMQ e BullMQ visam servidores Node selecionados por variáveis de ambiente.

Por que existe

Times júnior precisam de um padrão para “algo aconteceu” (eventos) e “faça isso” (comandos/consultas) sem ligar cada módulo diretamente. O mediator permite prototipar in-memory numa SPA e depois deployar as mesmas assinaturas de handler atrás de um broker — sem reescrever lógica de negócio.

Pré-requisitos

  • Runtime: browser (in-memory) ou Node/Bun (in-memory, RabbitMQ ou BullMQ).
  • Leitura prévia: Começando e async/await básico em JavaScript.
  • Para brokers: URL RabbitMQ ou env de conexão Redis (veja compileMessageMediator no pacote).
  • Opcional: shared-contracts para convenções de nomes de contrato em serviços maiores.

Glossário

TermoSignificado
Evento de integraçãoNotificação fire-and-forget: { name, payload, occurredAt, metadata? }.
MessagePayload de request: { contract, version?, payload, metadata? }.
HandlerFunção registrada para um contract — retorna IMessageResponse.
MediatorObjeto com publish/subscribe e registerHandler/request.
ContratoId string estável de comando ou query (ex.: users.create).
Adaptador in-memoryInMemoryMessageMediatorAdapter — sem durabilidade, mesmo processo.
Adaptador brokerRabbitMQ ou BullMQ — multi-processo, sobrevive a restarts (só Node).

Passos

1. Instalar (< 5 minutos)

bun add @jumentix/message-mediator

2. Primeiro sucesso — publicar e assinar (< 15 minutos)

import { InMemoryMessageMediatorAdapter } from '@jumentix/message-mediator';

const mediator = InMemoryMessageMediatorAdapter.compile();
const seen: string[] = [];

mediator.subscribe('demo.ping', async (event) => {
  seen.push(event.name);
});

await mediator.publish({
  name: 'demo.ping',
  payload: { hello: true },
  occurredAt: new Date().toISOString()
});

console.log(seen); // ['demo.ping']

Verifique o sucesso: seen.length === 1 após publish resolver.

3. Fluxo central — request/response

Registre handler e chame request:

mediator.registerHandler('users.create', async (message) => ({
  result: { id: 'user-1', username: message.payload.username }
}));

const response = await mediator.request({
  contract: 'users.create',
  version: '1.0.0',
  payload: { username: 'ana' }
});

if (response.error) throw response.error;
console.log(response.result);

Handlers podem ser sync ou async. Erros em response.error não lançam exceção a menos que seu wrapper escolha lançar.

4. Fluxo central — compilar por ambiente

import { compileMessageMediator } from '@jumentix/message-mediator';

// JUMENTIX_MESSAGE_MEDIATOR_ADAPTER=inmemory | rabbitmq | bullmq
const mediator = compileMessageMediator();
Env do adaptadorQuando usar
inmemory (padrão)Dev local, demos no browser, testes unitários
rabbitmq / rabbitMensageria async multi-serviço
bullmq / bullFilas de jobs com Redis

Adaptadores broker exigem env vars documentadas; URL ausente lança na compilação com mensagem clara.

5. Fluxo central — timeouts e opções de roteamento

const response = await mediator.request(
  { contract: 'billing.charge', payload: { amount: 10 } },
  { timeoutMs: 5000, routeKey: 'billing-primary' }
);

Registre handlers com routeKey ou queueName correspondentes em IMessageHandlerRegistrationOptions quando precisar de mais de um consumidor por contrato.

6. Superfície completa — mapa da API

ExportPapel
IMessageMediatorTipo da porta completa
IEventBusSó publish + subscribe
InMemoryMessageMediatorAdapter.compile()Mediator in-process
RabbitMqMessageMediatorAdapterRabbitMQ (Node)
BullMqMessageMediatorAdapterBullMQ (Node)
compileMessageMediator()Factory orientada a env
Tipos: IMessage, IMessageResponse, IIntegrationEvent, MessageHandlerTipagem de handlers e payloads

Experimente no playground de docs

Mediator em memória

Troque mensagens entre os domínios Category e Task e componha um read model via request/response do mediator.

const domainMessages = [];
const events = [];
const mediator = api.createInMemory();

const categories = new Map([
  ['work', { id: 'work', name: 'Work', color: '#2563eb' }],
  ['home', { id: 'home', name: 'Home', color: '#16a34a' }]
]);
const tasks = [];

await mediator.subscribe('tasks.created', async (event) => {
  events.push({
    title: event.payload.title,
    categoryId: event.payload.categoryId
  });
});

mediator.registerHandler('categories.get.v1', async (message) => {
  domainMessages.push({
    from: message.metadata.sourceDomain,
    to: 'Categories',
    contract: 'categories.get.v1',
    categoryId: message.payload.id
  });
  return {
    ok: true,
    result: categories.get(message.payload.id) ?? null
  };
});

mediator.registerHandler('tasks.create.v1', async (message) => {
  const task = {
    id: message.payload.id,
    title: message.payload.title,
    categoryId: message.payload.categoryId,
    completed: false
  };
  tasks.push(task);
  await mediator.publish({ name: 'tasks.created', payload: task });
  return { ok: true, result: task };
});

mediator.registerHandler('tasks.board.v1', async () => {
  const cards = await Promise.all(tasks.map(async (task) => {
    const category = await mediator.request({
      contract: 'categories.get.v1',
      payload: { id: task.categoryId },
      metadata: {
        sourceDomain: 'Tasks',
        reason: 'compose task board read model'
      }
    });
    return {
      id: task.id,
      title: task.title,
      categoryName: category.result?.name ?? 'Uncategorized',
      categoryColor: category.result?.color ?? '#64748b'
    };
  }));
  return {
    ok: true,
    result: {
      readModel: 'TaskBoard',
      composedFrom: ['Tasks', 'Categories'],
      cards
    }
  };
});

const response = await mediator.request({
  contract: 'tasks.create.v1',
  payload: {
    id: 'task-1',
    title: 'Wire mediator events',
    categoryId: 'work'
  },
  metadata: { source: 'browser-playground' }
});
const board = await mediator.request({
  contract: 'tasks.board.v1',
  payload: {},
  metadata: { source: 'task-board-page' }
});

return {
  createdTask: response.result,
  board: board.result,
  domainMessages,
  events
};

O stub do playground usa subscribe/publish estilo tópico:

const seen = [];
const mediator = api.createInMemory();
await mediator.subscribe('demo.ping', async (msg) => { seen.push(msg); });
await mediator.publish('demo.ping', { hello: true });

Em código de produção, prefira InMemoryMessageMediatorAdapter.compile() com objetos de evento completos e registerHandler / request para comandos.

Quando não usar in-memory

  • Workers multi-processo — cada processo tem memória própria; eventos não cruzam fronteiras de processo.
  • Filas duráveis entre deploys — use adaptadores RabbitMQ ou BullMQ em Node.

Mantenha adaptadores broker fora de bundles de browser.

Erros comuns

SintomaCausaCorreçãoVerificar sucesso
Handler nunca rodaString errada de name ou contractIgualdade exata; logue registrosseen ou response.result preenchido
No handler registered for contract …Falta registerHandlerRegistre antes de requestresponse.error ausente
Request trava até timeoutHandler nunca resolveRetorne ou rejeite no handler; use timeoutMsResposta dentro do timeout
Eventos perdidos após refreshAdaptador in-memoryTroque para broker em NodeEvento sobrevive a restart
JUMENTIX_RABBITMQ_URL is requiredAdaptador Rabbit sem URLConfigure env ou use inmemory localcompileMessageMediator() funciona

Checklist júnior (“Eu consigo …”)

  • Publicar evento de integração e tratá-lo com subscribe.
  • Registrar handler e completar round-trip request / response.
  • Explicar por que in-memory serve em testes unitários mas não em produção multi-worker.
  • Selecionar adaptador correto via JUMENTIX_MESSAGE_MEDIATOR_ADAPTER.
  • Passar timeoutMs e tratar response.error sem derrubar quem chamou.
  • Descrever diferença entre evento (publish) e comando (request).

Próximo passo

Conecte clientes HTTP e realtime nos guias REST API e Realtime API, depois volte a Começando para a jornada completa de comunicação.