Revamping Bahmni's Event Driven Architecture

Hello Community :waving_hand:,

We’d like to share a proposal around revamping how Bahmni’s internal modules publish and consume domain events. We’d love your feedback, especially from implementers deploying Bahmni in cloud and hybrid environments and development partners extending Bahmni features.


Current events architecture

Today, whenever a Bahmni module (core, appointments, bed-management) needs to signal a domain change — a patient saved, an encounter recorded, an appointment booked — it calls into openmrs-atomfeed directly. The atomfeed module has become the mandatory dependency for all event publishing in the atomfeed stream.

This creates real problems:

  • Every new event type requires to add dependecy for atomfeed module — the publisher, the atomfeed wiring and raising event.

  • Modules that want to publish events are forced to depend on atomfeed even if their concern has nothing to do with atom feeds.

  • Adding a new delivery channel (say, pushing events to ActiveMQ or Kafka for a cloud deployment) means adding more code into atomfeed or creating parallel plumbing from scratch — there is no clean extension point.

  • The architecture does not scale to the increasing variety of Bahmni deployment contexts, where different sites need different event pipelines.

  • The current events also makes it complex to perform simple operations within OpenMRS context like email sending etc.

In short: atomfeed works well for Bahmni today built as a delivery mechanism but has ended up with strong interdependent coupling points for all of event publishing and lacks extension to publish to more modern event handling systems.


What we’re proposing — Spring Events as the internal bus

The fix is to introduce a clean, decoupled internal event contract using Spring’s ApplicationEvent mechanism.

A shared interface EMREvent<T> is defined in bahmni-common-interfaces:


// Any module publishes a domain event — no knowledge of who's listening

applicationEventPublisher.publishEvent(new EMREvent<>(patient));

Any module that cares about that event registers an @EventListener independently:


// openmrs-atomfeed listens to spring events instead of AOP

@EventListener

public void onPatientSave(EMREvent<Patient> event) { ... }



// communications-omod (new)

@EventListener

public void onPatientSave(EMREvent<Patient> event) { ... }

EMREvent<T> implements Spring’s ResolvableTypeProvider so generic types resolve correctly at runtime — EMREvent<Patient> and EMREvent<Encounter> are treated as distinct event types despite Java’s type erasure.

Key shift: publishers fire and forget . They no longer need to know about atomfeed, ActiveMQ, or any downstream system. The Spring Events bus routes events to whoever is listening. The events come into action only when a listener is attached.


Extensibility — Built for Cloud and Varied Deployments

This is where the architecture really opens up. The Spring Events bus becomes the stable contract. Adding support for a new broker or platform simply means writing a new listener module — nothing in the existing publishers changes.

The architecture looks like this:

Option 1 (Recommended): Single Event Infrastructure

flowchart LR
    subgraph BCI["bahmni-common-interfaces"]
        SE["EMREvent&lt;T&gt; implements ResolvableTypeProvider"]
    end

    BC[bahmni-core] -.->|depends on| BCI
    AP[appointments] -.->|depends on| BCI
    OAF[bahmni-event-publisher] -.->|depends on| BCI

    subgraph TX["Single Transaction — Outbox Pattern"]
        BC -->|"publishes EMREvent&lt;Patient&gt;, EMREvent&lt;Encounter&gt;"| SEC[Spring Events Context]
        AP -->|"publishes EMREvent&lt;Appointment&gt;"| SEC
        BM[bed-management] -->|"publishes EMREvent&lt;BedAssignment&gt;"| SEC

        SEC -->|"@EventListener(EMREvent&lt;Patient&gt;) @EventListener(EMREvent&lt;Encounter&gt;) @EventListener(EMREvent&lt;Appointment&gt;) @EventListener(EMREvent&lt;BedAssignment&gt;)"| OAF
        OAF --> ERQ[(event_records_queue)]

        
    end

    ERQ -->|Option 1| OMAF[ OpenMRS Scheduler -- openmrs-atomfeed]
    OMAF --> ER[(event_records)]

    ERQ -->|Option 2| ES["external-service(reads from activemq_events_queueand pushes to ActiveMQ)"]
    ES --> AMQ([ActiveMQ])

Option 2: Multiple Event Infrastructure (Implementation specific needs)

flowchart LR
    subgraph BCI["bahmni-common-interfaces"]
        SE["EMREvent&lt;T&gt; implements ResolvableTypeProvider"]
    end

    BC[bahmni-core] -.->|depends on| BCI
    AP[appointments] -.->|depends on| BCI
    OAF[bahmni-event-publisher] -.->|depends on| BCI

    subgraph TX["Single Transaction — Outbox Pattern"]
        BC -->|"publishes EMREvent&lt;Patient&gt;, EMREvent&lt;Encounter&gt;"| SEC[Spring Events Context]
        AP -->|"publishes EMREvent&lt;Appointment&gt;"| SEC
        BM[bed-management] -->|"publishes EMREvent&lt;BedAssignment&gt;"| SEC

        SEC -->|"@EventListener(EMREvent&lt;Patient&gt;) @EventListener(EMREvent&lt;Encounter&gt;) @EventListener(EMREvent&lt;Appointment&gt;) @EventListener(EMREvent&lt;BedAssignment&gt;)"| OAF
        OAF --> ERQ[(event_records_queue)]

        SEC -->|"@EventListener(EMREvent&lt;Patient&gt;) @EventListener(EMREvent&lt;Appointment&gt;)"| OAM[aactivemq-event-publisher -- implementation specific]
        OAM --> AEQ[(activemq_events_queue)]
    end

    ERQ --> OMAF[ OpenMRS Scheduler -- openmrs-atomfeed]
    OMAF --> ER[(event_records)]

    AEQ --> ES["external-service(reads from activemq_events_queueand pushes to ActiveMQ)"]
    ES --> AMQ([ActiveMQ])

Each delivery path is fully independent. Adding a Kafka path tomorrow is just a new listener module and a new poller — zero touch to core, appointments, or bed-management.


Why not openmrs-module-event?

We have also looked whether the existing openmrs-module-event can fill this role. Unfortunately it can’t — and it’s worth being explicit about why.

openmrs-module-event is a post-commit, Hibernate-triggered, JMS-based notification bus. It fires events automatically when Hibernate detects an entity save/update/delete, and delivers them to ActiveMQ after the transaction has already closed. That makes it difficult for Bahmni needs in two fundamental ways:

  1. Transaction boundary — Bahmni’s outbox pattern requires the listener to write to a DB table inside the same transaction as the business operation. This provides guarantee that all the events are published to the outbox in a transaction or rolled back happens cleanly. That window is gone in openmrs-module-event — listeners run after commit.

  2. No type-safe Spring Eventsopenmrs-module-event uses JMS MessageListener with untyped MapMessage payloads (classname and uuid as strings). There is no EMREvent<Patient> concept, no @EventListener, and no way to distinguish event types without manual string parsing.

Additionally, it requires an ActiveMQ broker to be running even for deployments that only need Atom Feed, and its JMS delivery path is hardwired — there is no extension point for Kafka or other brokers.

This proposal is complementary to openmrs-module-event, not a replacement for it. The Spring Events bus handles in-process, transactional routing. openmrs-module-event could still serve as a lightweight trigger for other use cases where post-commit, loosely typed notifications are sufficient.

What this is NOT changing

  • The outbox pattern that already exists in Bahmni remains intact. Event listeners still write to DB outbox tables within the business transaction, and async pollers handle delivery. That reliability guarantee is preserved.

  • The Atom Feed continues to work as before — it simply becomes one listener among potentially many, rather than the central coupling point.

-–

Whats critical breaking change:

While we do this revamp and the atomfeed events are going to work the same as it was before, there is going to be an additional dependency on the modules that publishes events.

As an example if some implementation is using openmrs-module-appointments outside of Bahmni distribution, there is going to be an additional lightweight dependency on the bahmni-common-interfaces OMOD that needs to be added.

What’s next

  • Finalising the EMREvent<T> contract in bahmni-common-interfaces

  • Migrating bahmni-core and appointments to publish via Spring Events

  • Refactoring openmrs-atomfeed to consume via @EventListener rather than being called directly for writing to event_records_queue and dependent on AOP.

  • Documenting the extension contract so the community can build broker adapters


We’d love your input on the revamp of the events architecture.

Looking forward to the discussion!

@dkayiwa @mseaton @ibacher @angshuonline @binduak @supriyam @rahu1ramesh @sthote @ramashish @burke @sumazmrs @akhilmalhotra @dmukungi @rohit.v @mogoodrich

This looks like a non bahmni specific requirement. Instead of doing it in bahmni-common-interfaces, have you evaluated having it in openmrs-core, such that the entire openmrs ecosystem benefits and maintains it? FWIW, @raff has started doing some of this: Jira

Just in case you need this too: Jira

Great post @mohant .

This is no longer true as of version 4.0.0 (recently released) of event. See EVNT-36 and this commit EVNT-36 - Improve implementation of how events are fired (#21) · openmrs/openmrs-module-event@49265a8 · GitHub in which a new EntityEvent class was introduced, and Spring event firing that happens at multiple stages of the transaction lifecycle, and then backwards-compatibility with the previous asynchronous, post-transaction JMS implementation by listening on this new mechanism.

And as @dkayiwa points out, @raff has been doing additional work around OpenMRS events for the community. I would definitely second @dkayiwa ‘s notion that it would be great to all be trying to evolve in the same direction, with shared, well-tested underlying frameworks.

Awesome!. We absolutely love to have this on OpenMRS core. That would really help a lot of implementations.

We will go through the attached JIRA tickets and get back. We can have a sub-group huddle or have a discussion in platform call.

Thanks @dkayiwa for sharing details

Hello Community,

Following up on this discussion, while exploring possible approaches for improving Bahmni’s event publishing architecture, we also evaluated whether openmrs-module-event could support our needs.

Our current idea is roughly along these lines:

Register a new listener for specific entities and write the events to an outbox table within the same transaction.

Add another module that handles message publication, either by implementing the publisher there or by improving the existing OpenMRS messaging abstraction.

During this analysis, we had a discussion around the capabilities of openmrs-module-event, and we have a few questions that would help us understand whether it can support this approach.

1. Publishing Custom Application Events

Currently the Events module appears to rely on Hibernate interceptors that automatically generate events based on entity lifecycle operations (save/update/delete) for OpenmrsObject entities.

Question: Is it possible to publish custom application-level events that are not tied to an OpenmrsObject entity?

For example, can we raise events directly from the service layer using ApplicationEventPublisher within a transaction, without relying on Hibernate entity lifecycle events?

Our goal is to trigger events manually from the application when needed and have full control over when events are emitted.

2. Defining Custom Event Payloads

The current event structure seems to contain OpenmrsObject and an action type derived from Hibernate entity events.

Question: Is there a recommended approach to define custom event payloads instead of relying on the default OpenmrsObject + action model?

For example, we may want to publish a domain event with a custom payload structure.

3. Listening to Specific Entity Events

A single transaction event can contain multiple entity events because multiple objects may be saved within the same transaction.

Question: What is the recommended way for listeners to subscribe only to specific entity types (e.g., only Order events) when the transaction event contains multiple entities?

Should listeners always filter the entities manually from the aggregated event?

4. Transaction Behavior with TransactionBeforeCompletionEvent

The module exposes lifecycle events such as TransactionBeforeCompletionEvent.

Question: If a listener subscribes to TransactionBeforeCompletionEvent and throws an exception, will the database transaction rollback?

We want to understand if listeners executed at this stage can still participate in the same transaction context.

5. Messaging Broker Flexibility

The current implementation appears tightly coupled to JMS/ActiveMQ for publishing events.

Question: Is there any support (or planned support) for other messaging brokers such as Kafka or RabbitMQ, or is JMS intended to remain the primary integration mechanism?

Would appreciate insights from @mseaton @dkayiwa

Moving Away from Atom Feed to Spring Events

Hi OpenMRS Community,

We are working on refactoring the event publishing mechanism. Currently, OpenMRS modules rely on the OpenMRS Atom Feed module for publishing events across modules like Appointments, Encounters, Patient, and others. We are moving towards a Spring Application Events + Transactional Outbox pattern to decouple our modules from the Atom Feed dependency.

Why are we doing this?

  • The current Atom Feed approach tightly couples individual modules (Appointments, FHIR2, etc.) to the openmrs-atomfeed library

  • Modules like Appointments directly write to the event_records_queue table via Atom Feed dependency, which we want to eliminate

  • Spring Application Events give us a cleaner, framework-native way to publish events without hard module dependencies

  • The Transactional Outbox pattern ensures events are never lost — they are written in the same transaction as the entity save

What are we changing?

We are introducing a new module bahmni-event-outbox which provides:

  • EMREvent<T> — a generic Spring ApplicationEvent abstraction for all entity events

  • EMREventListener — listens to all EMREvent instances and persists them to event_records_queue

Each consuming module will now use ApplicationEventPublisher.publishEvent(EMREvent<?>) instead of directly depending on the Atom Feed module.

Impact on OpenMRS Atom Feed Module

We have added a Global Property atomfeed.enable.publish (default: false) to the Atom Feed module as a kill-switch. This means:

  • Atom Feed publishing is disabled by default after migration

  • Implementations that still need Atom Feed can set this to true via the OpenMRS Admin UI

  • The existing schedulers (EventPublisherTask, EventRecordsNumberOffsetMarkerTask) should be manually stopped once migration is confirmed

Migration Path

For implementations upgrading, please note:

  • You must upgrade the Atom Feed module alongside Bahmni Core

  • Configure atomfeed.enable.publish correctly to avoid duplicate event entries

What comes next?

Once the Spring Events + Outbox pattern is stable, we plan to introduce a JMS module (ActiveMQ) that reads from event_records_queue and publishes to configurable queues and topics. This will allow downstream systems like OpenELIS, Odoo Connect, PACS, and others to subscribe directly to JMS instead of Atom Feed. The JMS module will also support inbound listening, enabling OpenMRS to subscribe to events from external systems — removing the need for direct API calls and decoupling integrations further.

We would love to hear feedback from the community.

FYI @dkayiwa @mseaton @ibacher @angshuonline @binduak @supriyam @rahu1ramesh @sthote @ramashish @burke @sumazmrs @akhilmalhotra @dmukungi @rohit.v @mogoodrich

Thanks @lingeswaran.s for this update! A few questions:

  • Did you look at the latest changes to the eventmodule, and do you feel you can build on top of that? If not, why and what would be the source of your EMREvents?
  • OpenMRS has a long-established convention of prefixing table names with the module id of the module that introduces those tables. This was not adhered to originally with the event_records tables because they were brought in from the external atom feel library (from what I recall), but we should adhere to that in the future.
  • It would be really great if you would not name your module bahmni-xxx, not tie it to Bahmni-specific modules in any way, and create the code as a community artifact in the OpenMRS github repository, as this is a pattern that would be very helpful for many implemenations, whether they are running Bahmni or not.
  • The naming conventions of outbox and queue imply that the records may only be present in the table temporarily until they are confirmed to be successfully delivered to all consumers - is this true, and how do you intend to do this, or are this table expected to grow indefinitely?

Thanks,

Mike

@lingeswaran.s as a follow-up, my hope has been that the changes we have made to the event module to support using Spring events to register listeners that operate in the same transaction, coupled with a message queue approach as I tried to lay out here:

would be something we could get designed out and available, and so I am very interested to see if this new module you are proposing can fulfill that need.

@lingeswaran.s have you also had a look at this ticket Jira ?

Could it be in any way related to this on going work ?

cc @raff

Hi @mseaton and @tendomart,

Thank you for the context and the Jira reference. We have looked into the existing event module in detail — here are our findings and proposal.

Shortcomings in the Current Event Module

Yes, we did evaluate the event module.

The Hibernate interceptor emits events at the entity level, not the root. When a nested object is updated (e.g., a person name change), we lose the root context and must walk up the object hierarchy per entity type — which is brittle at scale.

More critically, there is no way to raise an event when no entity update is involved. If we want to emit a domain event without a corresponding Hibernate lifecycle callback, the current module simply cannot support it. This is a fundamental limitation for our use cases.

What We Need from the Event Module

We are not proposing a full replacement — just two targeted improvements:

1. A common EMREvent<T> interface in the event module, with methods such as:

  • getTags()
  • getContents()
  • getURI()

This allows modules like appointments, bed management, and operation theater to raise events via a provided dependency on the event module — with no new deployment requirement, because the event module is already bundled by distributions. No module would need to depend on any Bahmni-specific OMOD.

2. A pluggable publication mechanism — the means of sending a message should be abstracted out so different transport modules (ActiveMQ, Kafka, RabbitMQ) can plug in independently as separate OMODs. A properties-based opt-out should also allow disabling the broker connection entirely when not needed.

Concrete Examples — Sources of EMR Events

To make this tangible, here are the points in our modules where we raise events today:

Module Operation Event raised
Appointments Save appointment AppointmentEvent (CREATED / UPDATED)
Appointments Change status (scheduled → checked-in, etc.) AppointmentEvent (STATUS_CHANGED)
Appointments Undo status change AppointmentEvent (STATUS_CHANGED)
Bed Management Bed assignment BedAssignmentEvent
Bed Management Bed unassignment BedAssignmentEvent

These are points where we have full root-context control, which the Hibernate interceptor cannot guarantee. We raise the event explicitly at the service layer after the business operation, within the same transaction boundary.

Why Transactional Outbox

Concern Our approach
Atomicity The outbox write and the business operation succeed or fail together. A simple async queue leaves a gap where data is saved but the event is never published.
Control We decide exactly which operation raises an event and what the payload is, rather than reacting to Hibernate lifecycle callbacks on sub-entities.
Decoupling Business modules only know about the Spring publisher and EMREvent. No Atom Feed dependency anywhere.
Transport independence The outbox table is a stable, broker-agnostic contract. Whatever reads from it is a separate pluggable OMOD.
Retry and observability Unpublished records remain in the outbox. A configurable scheduler handles retries and cleanup, with a configurable retention window for audit purposes.

Mapping to Mike’s Common Workflow

Workflow step Our approach
Listen for events Any module raises an EMREvent via Spring publisher; outbox listener writes it transactionally
Persistent queue Outbox table, written in the same transaction as the business operation
Async processing Separate broker-specific OMOD reads from the outbox and publishes to the transport
Track success/failure published flag + published_at timestamp on each outbox record
Retry failed entries Background scheduler retries unpublished records on a configurable schedule
Admin visibility Query outbox table directly; published / published_at columns make failures immediately visible; scheduler and retention window are configurable via global properties

The outbox table is the stable contract. Whether the transport is JMS, Kafka, Camel, or something else — it simply reads from the outbox, sends, and marks the record as published. The outbox core never changes.

Table naming — we will follow the OpenMRS convention.

Regarding @tendomart Jira

The subscriber/listener side (receiving messages from a broker) is handled by a separate broker-specific OMOD, not the outbox core. That use case is fully addressable without modifying the outbox module.

Our Ask ( ? )

The single change that would unblock us is introducing EMREvent as a common interface in the event module — specifically because the event module is already present in every standard OpenMRS distribution, meaning no new dependency is imposed on appointments, bed management, or operationtheater.

Everything else — the outbox table, the scheduler, the broker-specific OMOD — we handle on our side.

We are happy to contribute the interface addition and any associated refactoring directly to the event module if that helps move this forward faster. On naming — openmrs-outbox or any community-preferred name works for us.

If the community prefers not to modify the event module, we have an alternative path: a separate thin common module that holds the EMREvent interface and the advices for common modules, keeping appointments, bed management, and operation theater free of any new direct dependency.

Looking forward to your thoughts.

CC: @angshuonline @mohant

@raff how does this relate to what you are currently working on?

Great to hear you are moving to Spring Events! By the end of June we are going to have Spring Events be part of OpenMRS core so that the events module is no longer needed. The feature will end up in the core 2.9.0 release. We will issue events for many of openmrs-core api service calls, which you may find helpful as well.

Having business level events (application events) in addition to Hibernate events with a common interface is going to be a part of the core solution.

The difference to what you laid out is that we are not planning to add any outbox table, instead we will publish chosen events (configurable by event type) directly to a broker… It’s going to be Apache Artemis that can run embedded, but also standalone in clustered environments. It has a great built-in UI console for monitoring.

Your outbox table with queue, retries and retention window sounds exactly like a broker’s job. What’s the reasoning behind building it instead of using a true broker?

Regardless with Spring Events in core you are going to be free to subscribe to events and put them in an outbox table if you wish…

As mentioned by others the work in core is tracked under TRUNK-6507.

BTW if you need DB persistence for some reason then Artemis can be configured with JDBC, see the docs.

The default and most performant way is a file journal.

Hi @raff, thanks for the update on TRUNK-6507 — Spring Events in core 2.9.0 is exactly the direction we were hoping for.

On your question about why an outbox table instead of publishing directly to Artemis,

Main reasons:

1. Broker independence In Bahmni and other OpenMRS-based deployments - some may use Kafka, or ActiveMQ, some are air-gapped with no broker at all. The outbox acts as a broker-agnostic buffer. The domain transaction writes to the outbox atomically, and a separate pluggable publisher module reads from it and pushes to whatever broker that deployment uses.

2. Closing the domain-broker gap In async mode, there is no atomicity guarantee between the DB write and the broker receiving the message. If the app goes down between service.save() and the publish, the event is silently lost. With the outbox, the event is committed in the same transaction as the domain change — so even if the broker is down for hours, nothing is lost and the publisher retries until confirmed. Artemis JDBC persistence helps on the broker side but does not close this gap.

Tying the publish step directly to Artemis would lock all modules deployments to a single broker choice.

How inbound events work For subscribing to events from external systems (Odoo, OpenELIS, etc.), our broker-specific OMOD (e.g. JMS module) receives the message from the broker and re-publishes it as a Spring EMREvent back into the OpenMRS context(Need to implement broker module in that way as it have that flexibility). Individual modules like appointments or bahmni-core just register an @EventListener — they never know a broker was involved at all. The broker OMOD is the only place that knows about JMS/Kafka/etc.

So the full picture is:

  • Outbox module — transactional write, broker-agnostic
  • Broker OMOD — reads outbox and publishes out; receives inbound and fires Spring Events in
  • Business modules — only speak Spring Events, zero broker dependency

Once Spring Events lands in core, our modules will subscribe directly to core-emitted events and write to the outbox from there — which is exactly what we have designed for.

Questions

You mentioned that having business-level events with a common interface will be part of the core solution in 2.9.0. Could you share what the abstraction looks like? Specifically:

  • What is the base interface/class for these events?
  • Will it carry a typed payload (like EMREvent<T>) or is it untyped?
  • Will @EventListener methods be able to subscribe to specific entity types (e.g. only Patient events vs Encounter events) without manual filtering?

CC: @angshuonline @mohant

Regarding broker independence. As an implementation you may always decide to stop publishing events to Artemis. We’ll publish with JMS so you get independence (runtime configuration change) to some extent and you may also have your own Spring Event handler that publishes to any broker you need (code change). That said we won’t be publishing events directly to a broker from core or modules. We’ll have a Spring event handler that is capable of publishing to a broker. From a module perspective you are issuing a special Spring Event that is going to be handled by a JMS handler and published to a broker. It’s the most flexible and performant approach I can think of.

I think that building your own broker using a relational DB is asking yourself for trouble (bugs and performance issues) with no true benefits. It’s a complex piece of software.

There’s no domain-broker gap.

With the outbox, the event is committed in the same transaction as the domain change

It works the same way, if you have a broker. You publish to a broker within the same transaction as the domain change and rollback the transaction, if the broker doesn’t respond it accepted the message or anything else fails in between.

I’ll draft interfaces next month and we can review. I like the typed EMREvent<T>, but need to check Spring Events internals and docs to see if it is handled efficiently (there’s no type erasure issue). We probably don’t want a single listener to subscribe to all events in the system as well. It can be a performance bottleneck… especially if you are only interested in some events. There’s no point in Spring doing thousands of calls to the single listener if events are simply discarded by it…

I see your point now. The issue with publishing to a broker within the same transaction is that you may not be able to confirm the transaction was properly committed via another message or other means (when connection to a broker is lost). In such a case a kind of outbox table does help for transactional events with strict delivery confirmation. I’ll include that in the core solution.

Updated TRUNK-6515 to include that requirement.

@raff thats 1 of the core aspects to solve/keep in mind while devising capabilities for core eventing. In a distributed system, and in microservices architecture - a transaction outbox pattern ensures that database changes and message dispatching are coordinated without using distributed transactions. (without using 2PC).

I don’t think @lingeswaran.s is suggesting to use outbox as replacement for broker. Its job is purely to act as buffer, which is acted on by relay. The relay is whats actually pushing the message to a broker, and it only retires if it couldn’t earlier push didnt succeed.

From a core OMRS platform perspective, I think it would be great to have independence and abstraction on choice of broker. if OpenMRS would bundle a default implementation as OMOD (not part of Core), e.g. JMS (instead of specifically Artemis) - but an implementation should be able to provide an alternate. This is even more so, with increasing cloud usage and for larger scale usages, we sometimes do not have options of choosing the brokers. Often the ways of connecting to a broker is different. Recently, I had to use JNDI to connect to the broker, and had to customize completely how to wire the JMSTemplate and Publisher. There would be always such cases, and its probably best to have abstraction over such publishing means for an implementer to override easily - I should not have to fight the core to do this. If the publisher comes as an OMOD, we can replace that easily.

I think we are on the same page then. I misunderstood the intention for the outbox table to function on its own with queueing and retention window and a broker being entirely optional. If there’s no broker consuming outbox, writing to the outbox table should be disabled as well.

Do we also agree that only selected events should go through the outbox?

I imagine we would have one or more @TransactionalEventListener(phase = BEFORE_COMMIT) that would be configured to push selected events to the outbox table.

Some events may be pushed by @TransactionalEventListener(phase = AFTER_COMMIT) to a broker without using the outbox… e.g. if performance is more important and consistency in case of failure can be maintained by other means.

Yes. also regarding aftter commit event listener, Any any async event listener also will not participate in transaction.

There’s another important aspect that @lingeswaran.s raised.

  1. Often we need to raise events without any transaction context within the application.
  2. Current event module’s event raising for transaction entity is often at too low level, for it to be useable.

Thanks all - glad to hear that we are now all on the same page regarding the need for this transaction outbox pattern. Thank you @angshuonline and @lingeswaran.s for communicating this better than I was able to do.

I am interested in learning more about the timeline and nature of implementing these features. I see that much of this work is being coordinated under https://openmrs.atlassian.net/browse/TRUNK-6507, but I don’t see a lot of this discussion making it’s way back into tickets. It would be great if we could get the work that has been agreed here specified enough in tickets such that the community can pick it up and move it forward. @raff can you give a sense of where things are and whether this is something that we could move forward collaboratively?