70: Refactor the notification framework into transport-independent notification services

This commit is contained in:
2026-08-11 20:03:06 +02:00
parent b33db6da49
commit df504e29bf
25 changed files with 522 additions and 168 deletions
@@ -0,0 +1,80 @@
# notification-services Specification
## Purpose
Defines transport-independent notification services whose delivery configuration is bound before consumers submit semantically tagged text messages.
## Requirements
### Requirement: Transport-independent bound notification contract
The notification API SHALL provide a non-generic bound service under `com.r35157.service.notification` whose only delivery input is an `ΩNotificationMessageΩ` and whose synchronous failures use `IOException`. A consumer using an already bound service SHALL NOT need a transport-specific message type or an implementation-package dependency.
#### Scenario: Bound consumer sends a message
- **WHEN** a consumer receives a configured `BoundNotificationService`
- **THEN** it can call `push(message)` using only an `ΩNotificationMessageΩ`
#### Scenario: Delivery failure is reported synchronously
- **WHEN** a bound transport encounters a delivery failure
- **THEN** `push(message)` reports the failure through its `IOException` contract
### Requirement: Addressed services bind destination configuration
The notification API SHALL provide an `AddressedNotificationService` generic only over a `NotificationDestination` type. Binding a destination SHALL produce a `BoundNotificationService` that captures the destination so later calls require only an `ΩNotificationMessageΩ`.
#### Scenario: Destination is bound once
- **WHEN** a destination is passed to an addressed notification service's binding operation
- **THEN** the returned bound service sends subsequent messages to that captured destination without requiring it again
### Requirement: Notification message validation
Every public notification-service `push(...)` entry point SHALL reject a null, empty, or blank `ΩNotificationMessageΩ` with `IllegalArgumentException` before attempting any network or transport operation.
#### Scenario: Null message is rejected
- **WHEN** a caller pushes a null notification message
- **THEN** the service throws `IllegalArgumentException` before delivery is attempted
#### Scenario: Empty or blank message is rejected
- **WHEN** a caller pushes an empty or whitespace-only notification message
- **THEN** the service throws `IllegalArgumentException` before delivery is attempted
### Requirement: Pushover delivery remains functional
The Pushover implementation SHALL accept `ΩNotificationMessageΩ` directly, preserve its existing synchronous HTTP delivery behavior, and expose it as `PushoverNotificationService` implementing `BoundNotificationService`.
#### Scenario: Valid Pushover message is submitted
- **WHEN** a caller pushes a valid message through a configured Pushover notification service
- **THEN** the service submits the same message through the existing Pushover API request flow
#### Scenario: Pushover rejects delivery
- **WHEN** Pushover returns a non-successful HTTP response or communication fails
- **THEN** the service reports the delivery failure as an `IOException`
### Requirement: SMTP binding captures address and subject
An SMTP destination SHALL contain both an `ΩEmailAddressΩ` and a fixed `ΩEmailSubjectΩ`. SMTP SHALL support binding these two values into a `BoundNotificationService`, and SHALL reject a null, empty, or blank address or subject with `IllegalArgumentException` when the destination is created.
#### Scenario: SMTP service is fully bound
- **WHEN** a caller binds a valid email address and subject
- **THEN** the returned bound service requires only an `ΩNotificationMessageΩ` for each push
#### Scenario: Invalid SMTP binding is rejected
- **WHEN** a caller creates or requests an SMTP binding with a null, empty, or blank email address or subject
- **THEN** the operation throws `IllegalArgumentException`
#### Scenario: Valid SMTP delivery remains unimplemented
- **WHEN** a valid message is pushed through a valid SMTP binding
- **THEN** the service reports that SMTP delivery is not implemented
### Requirement: Discord delivery remains unimplemented
The Discord stub SHALL implement `BoundNotificationService`, accept `ΩNotificationMessageΩ` directly, and SHALL NOT implement webhook delivery as part of this capability.
#### Scenario: Valid Discord delivery remains unimplemented
- **WHEN** a valid message is pushed through the configured Discord service
- **THEN** the service reports that Discord delivery is not implemented without performing webhook delivery
### Requirement: Obsolete notification API is removed
The notification framework SHALL reside exclusively under `com.r35157.service.notification`. It SHALL NOT retain compatibility aliases under `com.r35157.libs.notification`, notifier-named public interfaces or implementations, the `NotificationMessage` marker, or transport-specific message wrapper types.
#### Scenario: Production code uses the new service API
- **WHEN** the production source tree is inspected after migration
- **THEN** notification framework code and consumers reference only the new package and service-oriented types
#### Scenario: Removed types are unavailable
- **WHEN** downstream code attempts to import an old notifier or message-wrapper type
- **THEN** that type is no longer part of the source or compiled API