Skip to content

Comunicação Desacoplada (Event-Driven)

Para manter o isolamento rigoroso entre os módulos (ex: impedir que o módulo de Consultation dependa do módulo de Chat), a comunicação entre eles é estritamente controlada. Nunca permitimos importações diretas de lógica de negócio entre pacotes da pasta modules/.

Estratégia: Domain Events

A comunicação ocorre através de um barramento de eventos centralizado, localizado em core/domain-events. Quando uma ação relevante acontece em um domínio, ele "dispara e esquece" (fire and forget) um evento para o barramento.

Este padrão é essencial em sistemas de saúde para garantir que fluxos secundários (como enviar um SMS de confirmação) não bloqueiem o fluxo principal (como salvar os dados da consulta).

Exemplo Prático: Fluxo de Início de Teleconsulta (entre Módulos)

Imagine que, ao iniciar uma consulta, precisamos criar automaticamente uma sala de chat e notificar o sistema de logs de auditoria.

text
[Módulo Consultation] ----> Publica: CONSULTATION_STARTED
                                     |
                                     v
[Core/DomainEvents] -------- Barramento de Eventos (Mediator)
                                     |
           +-------------------------+-------------------------+
           |                                                   |
           v                                                   v
[Módulo Chat]                                      [Módulo AuditLog]
Escuta: CONSULTATION_STARTED                       Escuta: CONSULTATION_STARTED
Ação: Cria sala privada entre                      Ação: Registra log de acesso
      médico e paciente.                                  ao prontuário.

Benefícios do Modelo de Eventos

  1. Desacoplamento Temporal e Funcional: O domínio de Consultation não precisa saber se o Chat existe ou se está funcionando corretamente. Sua única responsabilidade é notificar que a consulta começou.
  2. Extensibilidade: Podemos adicionar um novo módulo (ex: Scheduling para agendamento de retorno) que escuta o mesmo evento CONSULTATION_STARTED, sem alterar uma única linha de código no módulo de Consultation.
  3. Resiliência: Se o módulo de Chat falhar ao carregar, o médico ainda consegue visualizar os dados da consulta e o prontuário, pois os domínios são independentes.

Regras e Governança de Interação

Para que este modelo escale, seguimos regras rígidas:

  • Payloads Minimalistas: Os eventos devem carregar apenas IDs e status (ex: { consultationId: "abc-123", patientId: "user-456" }). O domínio interessado deve usar esses IDs para buscar os dados que precisa através de sua própria API ou store.
  • Contratos Compartilhados: As definições de nomes de eventos e tipos de payload devem residir em packages/contracts, garantindo que o publicador e o assinante falem a mesma língua.
  • Side-Effects em Sagas: A inscrição em eventos de domínio geralmente ocorre dentro de um Redux-Saga. O Saga fica "ouvindo" o barramento e, ao detectar o evento, dispara as ações internas do seu próprio domínio.

Exemplo de Implementação: Barramento de Eventos

Abaixo, demonstramos como o contrato e a implementação do barramento garantem o desacoplamento.

1. Definição do Contrato (Contracts)

typescript
// packages/contracts/src/events.ts
export enum DomainEventNames {
  CONSULTATION_STARTED = 'CONSULTATION_STARTED',
  CONSULTATION_FINISHED = 'CONSULTATION_FINISHED'
}

export interface DomainEventPayloads {
  [DomainEventNames.CONSULTATION_STARTED]: {
    consultationId: string;
    patientId: string;
    doctorId: string;
  };
}

2. Publicação do Evento (Publisher Module)

O módulo de Consultation não conhece o Chat. Ele apenas notifica o sistema.

typescript
// packages/modules/consultation/store/sagas.ts
import { domainEventsBus } from 'core/domain-events';
import { DomainEventNames } from 'contracts/events';

function* handleStartConsultationSuccess(data: any) {
  // Notifica o ecossistema que a consulta iniciou
  domainEventsBus.publish(DomainEventNames.CONSULTATION_STARTED, {
    consultationId: data.id,
    patientId: data.patientId,
    doctorId: data.doctorId
  });
}

3. Subscrição do Evento (Subscriber Module)

O módulo de Chat ou Audit reage ao evento de forma independente.

typescript
// packages/modules/chat/store/sagas.ts
import { take, put } from 'redux-saga/effects';
import { domainEventsBus } from 'core/domain-events';
import { DomainEventNames } from 'contracts/events';

export function* watchConsultationEvents() {
  // Cria um canal de eventos para escutar o barramento
  const channel = domainEventsBus.createChannel();
  
  while (true) {
    const event = yield take(channel);
    
    if (event.name === DomainEventNames.CONSULTATION_STARTED) {
      // Inicia a sala de chat específica para esta consulta
      yield put(chatActions.initializeRoom({
        consultationId: event.payload.consultationId,
        members: [event.payload.patientId, event.payload.doctorId]
      }));
    }
  }
}