ADR 0004: Domain Events with ActiveSupport::Notifications¶
- Status Accepted
- Date 2026-04-14
Context¶
MedTracker records audit history with PaperTrail, but it does not currently publish domain events for important medication operations. That forces any secondary behaviour such as notifications or analytics to infer intent by polling database state instead of reacting to the operation that just happened.
Issue #1051 identifies four initial domain events:
DoseTakenMedicationRestockedScheduleCreatedLowStockThresholdReached
The first delivery needed to choose an event mechanism and add DoseTaken and
LowStockThresholdReached.
Decision¶
We will use ActiveSupport::Notifications as the in-process domain event mechanism.
Event names for the initial tranche:
dose_taken.med_trackerlow_stock_threshold_reached.med_trackertake_attempted.med_trackertake_recorded.med_trackertake_blocked_by_rules.med_trackertake_errors.med_tracker
Publishers emit raw notification payloads at the domain or service boundary. This decision does not add a custom event bus or subscriber framework. It also does not add event wrapper objects.
Rationale¶
Why ActiveSupport::Notifications¶
- It is already part of Rails and needs no new dependency.
- It is lightweight and sufficient for in-process publication.
- It keeps the first event tranche small and easy to review.
- It gives us a standard subscription API for later notification and analytics consumers.
Why not a custom event bus¶
A custom bus would add structure before we have even proven the event set, naming, or subscriber needs. That would increase design surface area without solving a concrete problem in this tranche.
Why not wisper or another gem¶
An additional gem would add dependency and pattern overhead for a problem Rails already solves adequately for best-effort in-process notifications.
Consequences¶
Positive¶
- Domain intent is published at the point where it occurs.
- Notifications and analytics can subscribe without coupling themselves to controllers or models.
- The event mechanism stays small and aligned with current Rails conventions.
Negative¶
- These events run in the application process and provide best-effort delivery. They do not provide durable integration delivery.
- Subscribers are not replayable if the process crashes after the database commit.
- We still need follow-up work for additional publishers and subscribers.
Follow-up¶
Deferred from this ADR tranche:
MedicationRestockedScheduleCreated- notification subscribers
- analytics subscribers
- any durable or cross-process event transport
Follow-up: Operational signals¶
The application observability work completed after this decision adds a privacy-safe operational-event boundary beside domain publication. ActiveSupport::Notifications remains the in-process domain-event mechanism. The operational boundary does not replace, wrap, or become a subscriber framework. It records safe attempts and outcomes independently so that a logging failure cannot alter domain behaviour and subscriber ordering cannot hide the publication attempt.
Domain payloads may remain rich enough for trusted in-process subscribers.
Operational records use event-specific allowlists and must not serialize those
raw payloads. A helper can emit the safe operational record before invoking
ActiveSupport::Notifications, and can record a subscriber failure before
re-raising it, but the original synchronous propagation semantics remain
unchanged.
This ADR remains accepted. It should be superseded only if a later decision replaces or wraps the domain-event mechanism itself.