Skip to content

refactor: notification event handling#4141

Merged
chrisgacsal merged 6 commits into
mainfrom
refactor/notification-events
Apr 15, 2026
Merged

refactor: notification event handling#4141
chrisgacsal merged 6 commits into
mainfrom
refactor/notification-events

Conversation

@chrisgacsal
Copy link
Copy Markdown
Collaborator

@chrisgacsal chrisgacsal commented Apr 15, 2026

Overview

  • make sure that notification Events are dispatch only if there are active channels to sent to allowing Events without delivery status (in case there are no active destinations)
  • remove active channel check from logic used for evaluating entitlement rules
  • move event handler to backend only (it is temporary)
  • return error if event is being created for disabled rule

Fixes #(issue)

Notes for reviewer

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Fixed notification delivery by filtering to active and non-deleted notification channels
  • Refactor

    • Improved notification event handler lifecycle management with explicit startup and shutdown hooks
    • Simplified notification service architecture by decoupling event handler dependency
    • Changed notification event handler from asynchronous to synchronous execution for better control flow

Signed-off-by: Krisztian Gacsal <chrisgacsal@users.noreply.github.com>
Signed-off-by: Krisztian Gacsal <chrisgacsal@users.noreply.github.com>
@chrisgacsal chrisgacsal requested a review from a team as a code owner April 15, 2026 11:56
@chrisgacsal chrisgacsal added the release-note/bug-fix Release note: Bug Fixes label Apr 15, 2026
@chrisgacsal chrisgacsal self-assigned this Apr 15, 2026
@chrisgacsal chrisgacsal force-pushed the refactor/notification-events branch from a223896 to d8c8b2a Compare April 15, 2026 11:59
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 15, 2026

📝 Walkthrough

Walkthrough

This PR refactors notification event handler lifecycle management. The eventHandler dependency is removed from NotificationService and its injection pattern, becoming independently managed at the application level. Eager-load filtering for active (non-disabled, non-deleted) channels is introduced, the handler's Start() becomes synchronous instead of goroutine-based, and related guards checking enabled channels are removed.

Changes

Cohort / File(s) Summary
Notification Service Architecture
openmeter/notification/service/service.go, openmeter/notification/service/event.go
Removed eventHandler field and dependency from Service and Config. Updated event creation to use IsDeletedAt() for deletion checks and removed synchronous dispatch logic from event service.
Notification Wiring & DI
app/common/notification.go, cmd/balance-worker/wire_gen.go, cmd/jobs/internal/wire_gen.go, cmd/notification-service/wire_gen.go
Removed NewNoopNotificationEventHandler, simplified NewNotificationEventHandler signature (no close function return), and removed eventHandler parameter from NewNotificationService. Generated wire code updated accordingly with cleanup simplifications.
Server Event Handler Lifecycle
cmd/server/main.go, cmd/server/wire.go, cmd/server/wire_gen.go
Added explicit startup/shutdown hooks for NotificationEventHandler in main. Added NotificationEventHandler field to Application struct and wired it separately from the service.
Active Channel Filtering
openmeter/notification/adapter/event.go, openmeter/notification/adapter/rule.go
Added EagerLoadRulesWithActiveChannels() and EagerLoadActiveChannels() helpers to filter channels by disabled/deleted state using point-in-time checks. Updated ListEvents, GetEvent, ListRules, and GetRule to use these filters.
Event Handler Synchronization
openmeter/notification/eventhandler/handler.go
Changed Start() from goroutine-based async execution to synchronous blocking operation; panic recovery now applies to outer call stack rather than spawned goroutine.
Consumer & Rule Logic Simplification
openmeter/notification/consumer/entitlementbalancethreshold.go, openmeter/notification/consumer/entitlementreset.go, openmeter/notification/rule.go, openmeter/notification/internal/rule.go
Removed HasEnabledChannels() guards from consumer handlers and deleted the method itself. Updated test fixtures to include populated Customer field in test payloads.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • feat: ledger account provisioning #4055 — Earlier changes to notification event handler wiring patterns and NewNotificationEventHandler/NewNotificationService refactoring that this PR builds upon or supersedes.

Suggested labels

release-note/misc

Suggested reviewers

  • GAlexIHU
  • turip
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'refactor: notification event handling' clearly and concisely summarizes the main objective of the changeset, which involves restructuring how notification events are handled across the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/notification-events

Warning

Review ran into problems

🔥 Problems

Timed out fetching pipeline failures after 30000ms


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
openmeter/notification/adapter/event.go (1)

215-219: Consider using a single now variable for consistency.

clock.Now() is called twice here (lines 217 and 219). While the time difference would be negligible in practice, using a single now variable would be cleaner and ensure temporal consistency - same pattern used in ListRules.

♻️ Suggested fix
+		now := clock.Now()
+
 		ruleQuery := a.db.NotificationRule.Query().
 			Where(ruledb.Namespace(params.Namespace)).
 			Where(ruledb.ID(params.RuleID)).
 			Where(ruledb.Or(
 				ruledb.DeletedAtIsNil(),
-				ruledb.DeletedAtGT(clock.Now()),
+				ruledb.DeletedAtGT(now),
 			)).
-			WithChannels(EagerLoadActiveChannels(clock.Now()))
+			WithChannels(EagerLoadActiveChannels(now))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/notification/adapter/event.go` around lines 215 - 219, Capture
clock.Now() once into a local variable (e.g., now) and reuse it in the query to
ensure temporal consistency: replace the two clock.Now() calls used in Where(...
DeletedAtGT(...)) and WithChannels(EagerLoadActiveChannels(...)) with that
single now variable; update the same block where Where, ruledb.Or,
ruledb.DeletedAtIsNil, ruledb.DeletedAtGT and
WithChannels/EagerLoadActiveChannels are used (same pattern as ListRules).
cmd/server/main.go (1)

278-301: Minor redundancy: Close() is called twice.

The defer at lines 280-284 and eventHandleStop at lines 292-298 both call app.NotificationEventHandler.Close(). While this is safe (the handler uses sync.OnceFunc internally), it's a bit redundant.

Comparing to the telemetry and API server blocks - those use defer Close() for hard-close on panic/exit and Shutdown(ctx) for graceful shutdown in the callback. Since EventHandler.Close() is already idempotent and immediate, you could remove the defer block entirely and rely solely on the run group's stop function.

That said, the defensive defer doesn't hurt anything - just a style nit.

♻️ Optional: Remove redundant defer
 	// Set notification event handler
 	{
-		defer func() {
-			if err = app.NotificationEventHandler.Close(); err != nil {
-				logger.Warn("failed to close notification event handler", "error", err)
-			}
-		}()
-
 		eventHandlerStart := func() error {
 			logger.Info("starting notification event handler")

 			return app.NotificationEventHandler.Start()
 		}

 		eventHandleStop := func(err error) {
 			logger.Debug("shutting down notification event handler gracefully...", "error", err)

 			if err = app.NotificationEventHandler.Close(); err != nil {
 				logger.Warn("failed to shutdown notification event handler", "error", err)
 			}
 		}

 		group.Add(eventHandlerStart, eventHandleStop)
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/server/main.go` around lines 278 - 301, Remove the redundant deferred
Close call for app.NotificationEventHandler: delete the outer defer block that
calls app.NotificationEventHandler.Close(), and keep the existing graceful
shutdown callback (eventHandleStop) passed to group.Add which performs the
Close; this removes the duplicate Close invocation while leaving
eventHandlerStart, eventHandleStop and group.Add intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@openmeter/notification/eventhandler/handler.go`:
- Around line 73-101: The test environment hangs because Handler.Start() blocks
until <-h.stopCh and NewTestEnv starts it synchronously; change NewTestEnv to
start the handler in a goroutine instead of calling Start() directly so setup
can complete and the registered cleanup can later call Close() to signal stopCh.
Specifically, locate where NewTestEnv invokes Handler.Start() and replace the
direct call with a goroutine (e.g., go func() { if err := h.Start(); err != nil
{ /* capture/log to an error channel or test logger */ } }()), ensuring any
start errors are propagated back to the testenv via a channel or logger so they
aren’t lost.

In `@openmeter/notification/internal/rule.go`:
- Around line 162-173: The fixture sets CreatedAt to now while UpdatedAt is
earlier (updatedAt = now - 12h), producing created_at > updated_at; change the
timestamps so UpdatedAt is >= CreatedAt (either set updatedAt to now or make
CreatedAt an earlier time) — update the variables used for CreatedAt and
UpdatedAt in the fixture (references: CreatedAt, UpdatedAt, now, updatedAt) so
the timestamps are in valid chronological order.

---

Nitpick comments:
In `@cmd/server/main.go`:
- Around line 278-301: Remove the redundant deferred Close call for
app.NotificationEventHandler: delete the outer defer block that calls
app.NotificationEventHandler.Close(), and keep the existing graceful shutdown
callback (eventHandleStop) passed to group.Add which performs the Close; this
removes the duplicate Close invocation while leaving eventHandlerStart,
eventHandleStop and group.Add intact.

In `@openmeter/notification/adapter/event.go`:
- Around line 215-219: Capture clock.Now() once into a local variable (e.g.,
now) and reuse it in the query to ensure temporal consistency: replace the two
clock.Now() calls used in Where(... DeletedAtGT(...)) and
WithChannels(EagerLoadActiveChannels(...)) with that single now variable; update
the same block where Where, ruledb.Or, ruledb.DeletedAtIsNil, ruledb.DeletedAtGT
and WithChannels/EagerLoadActiveChannels are used (same pattern as ListRules).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7b9c421d-1eb0-4af5-bce6-a1d574422ef4

📥 Commits

Reviewing files that changed from the base of the PR and between d15c199 and d8c8b2a.

📒 Files selected for processing (16)
  • app/common/notification.go
  • cmd/balance-worker/wire_gen.go
  • cmd/jobs/internal/wire_gen.go
  • cmd/notification-service/wire_gen.go
  • cmd/server/main.go
  • cmd/server/wire.go
  • cmd/server/wire_gen.go
  • openmeter/notification/adapter/event.go
  • openmeter/notification/adapter/rule.go
  • openmeter/notification/consumer/entitlementbalancethreshold.go
  • openmeter/notification/consumer/entitlementreset.go
  • openmeter/notification/eventhandler/handler.go
  • openmeter/notification/internal/rule.go
  • openmeter/notification/rule.go
  • openmeter/notification/service/event.go
  • openmeter/notification/service/service.go
💤 Files with no reviewable changes (3)
  • openmeter/notification/consumer/entitlementreset.go
  • openmeter/notification/rule.go
  • openmeter/notification/consumer/entitlementbalancethreshold.go

Comment thread openmeter/notification/eventhandler/handler.go
Comment thread openmeter/notification/internal/rule.go Outdated
* dispatch event if there are active channels to sent to
* make running event handler blocking
* run event handler in backend service only
@chrisgacsal chrisgacsal merged commit a37483f into main Apr 15, 2026
22 of 23 checks passed
@chrisgacsal chrisgacsal deleted the refactor/notification-events branch April 15, 2026 12:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note/bug-fix Release note: Bug Fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants