--- status: Accepted date: 2026-07-03 applyTo: - "Fuchs/Notifications/**" - "Fuchs/Services/**" - "Fuchs/Controllers/**" supersededBy: "" --- # 0001 — Domain events (success and failure) trigger user-understandable notifications ## Context Business operations (invoice creation, sending, marking sent, reminders, banking import) happen server-side, often outside a synchronous request the user is watching (background jobs, long-running sends). Users had no reliable way to learn that an operation they cared about — or one that failed — actually happened, short of refreshing lists or checking logs. ## Decision Every meaningful business outcome, success **and** failure, is modeled as a `DomainEvent` (`Fuchs/Notifications/DomainEvent.cs`) with: - a `DomainEventType` enum value identifying what happened, - the acting `UserAccountId`, - a `Title`, and - a `Context` dictionary of the data needed to render a human-readable message (invoice number, email address, file name, row counts, etc.). Services call the corresponding method on `IEventService` (`Fuchs/Notifications/IEventService.cs`, implemented by `EventService`) at the point the outcome is known — e.g. `InvoiceSentToCustomerAsync(invoice, email, userAccountId)` or `InvoiceIssueAsync(message, userAccountId, invoiceId)` on failure. `EventService.PublishAsync` renders the event into a `GuiNotification` with a German, end-user-readable `Message` (e.g. *"Rechnung R2026-0001 wurde an den Kunden mit der E-Mail test@test.de versandt."*) and pushes it — see [0002](0002-gui-notification-delivery-signalr.md) for delivery. Every new business operation with a user-visible outcome (created, sent, failed, imported, etc.) must add a `DomainEventType` value and a matching `IEventService` method, and call it from the service at the point of success **and** the point of failure. ## Consequences - `IEventService` is injected into services that perform user-facing operations (`InvoiceService`, `ReminderService`, `BankingService` callers) — never bypass it by writing directly to `NotificationHub`. - Failure paths must call the `*IssueAsync`/`*Failed` event too, not just succeed-path events — silent failures are the problem this exists to prevent. - Messages are built server-side in `EventService.BuildNotification`, in German, using only `Context` values — keep `Context` populated with everything the message needs (don't rely on the client to look anything up). - Adding a new event type means updating the enum, the `IEventService` interface + `EventService` implementation (trigger method + message branch + `IsFailure` if it's a failure type), and the calling service — in the same change. ## Alternatives considered - **Polling a status endpoint from the client**: rejected — adds latency, extra load, and doesn't generalize to background/multi-tab flows as cleanly as a push model. - **Raw exception messages surfaced to the GUI**: rejected — not user-understandable and leaks internal details; `Context` + a rendered German message keeps the boundary between internal errors and user-facing text explicit.