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

Closed
opened 2026-08-11 19:26:31 +02:00 by minimons · 0 comments
Owner

Background

The existing notification framework under:

com.r35157.libs.notification

does not provide the intended transport-independent abstraction.

The current API uses transport-specific message types:

BoundNotifier<PushMessage>
BoundNotifier<DiscordMessage>
BoundNotifier<EmailMessage>

Although these message types implement the empty NotificationMessage marker interface, Java generic invariance means that consumers still need to know the concrete transport-specific message type.

This prevents a consumer from receiving a fully configured notification service without knowing whether it sends through Discord, Pushover, SMTP, or another future transport.

The intended architecture is that a composition root creates and configures a notification service and then injects only a fully bound, transport-independent service into the consumer.

For example, the future EvelynBurnerService must only know that it can push a notification message. It must not know whether the message is delivered through Discord, Pushover, SMTP, Telegram, or another implementation.

Before implementing the Discord integration, refactor and repair the existing notification abstraction.

Objective

Replace the current notifier/message design with a service-oriented API where:

  • All transport-specific and destination-specific configuration is bound before the service is injected into a consumer.
  • A consumer only supplies a semantically tagged text message.
  • A consumer does not import transport-specific message classes.
  • The package and class names consistently describe notification services rather than notifiers.
  • SMTP can bind both a destination email address and a fixed subject before being passed to a consumer.

Package relocation

Move the complete notification package from:

com.r35157.libs.notification

to:

com.r35157.service.notification

Move the implementation subpackages accordingly:

com.r35157.service.notification.impl.discord
com.r35157.service.notification.impl.pushover
com.r35157.service.notification.impl.smtp

Use the singular package segment service, exactly as shown above.

Update all affected imports and usages.

No classes or compatibility aliases must remain under the old com.r35157.libs.notification package.

Rename the public interfaces

Rename:

BoundNotifier
    -> BoundNotificationService

AddressedNotifier
    -> AddressedNotificationService

Move NotificationDestination to the new package without changing its fundamental purpose.

Remove transport-specific message generics

BoundNotificationService must not be generic over a message type.

Its public contract must be equivalent to:

public interface BoundNotificationService {

    void push(ΩNotificationMessageΩ message) throws IOException;
}

ΩNotificationMessageΩ resolves to String.

AddressedNotificationService may remain generic only over its destination type:

public interface AddressedNotificationService<
        D extends NotificationDestination>
{
    void push(
            D destination,
            ΩNotificationMessageΩ message
    ) throws IOException;

    default BoundNotificationService bind(D destination) {
        return message -> push(destination, message);
    }
}

The precise formatting may follow existing project conventions, but the resulting type semantics must be as described above.

Remove the obsolete message abstraction

Remove the empty marker interface:

NotificationMessage

Remove the transport-specific message and wrapper classes that become unnecessary:

DiscordMessage
PushMessage
EmailMessage
EmailBody
EmailSubject

Do not replace these classes with a new TextMessage, record, marker interface, or general message wrapper.

The semantic message type must be expressed directly by ΩNotificationMessageΩ.

Message validation

Every public push(...) entry point must reject a notification message that is:

  • null
  • empty
  • blank

Such invalid input must fail before any network or transport operation is attempted.

Use IllegalArgumentException consistently for invalid notification messages.

The validation belongs in the notification service push logic. Do not introduce a separate runtime message object only to perform this validation.

Rename the implementations

Rename the existing implementation classes:

PushOverNotifier
    -> PushoverNotificationService

DiscordNotifier
    -> DiscordNotificationService

SMTPNotifier
    -> SMTPNotificationService

Use Pushover, not PushOver, in class names, filenames, package references, variables, and updated examples.

The resulting implementations must use the new service interfaces:

PushoverNotificationService
        implements BoundNotificationService

DiscordNotificationService
        implements BoundNotificationService

SMTPNotificationService
        implements AddressedNotificationService<SMTPDestination>

Pushover behavior

Adapt the existing functional Pushover implementation to accept ΩNotificationMessageΩ directly.

Preserve its existing delivery behavior and synchronous IOException-based error contract.

This issue is an API and architecture refactor. Do not add unrelated Pushover behavior, retries, asynchronous execution, or configuration changes.

SMTP binding semantics

An SMTP notification must be fully bound to both:

  • its destination email address
  • its fixed email subject

before it is injected into a consumer.

SMTPDestination must therefore represent the complete destination-specific SMTP binding required to send a plain notification message. It may contain:

ΩEmailAddressΩ emailAddress
ΩEmailSubjectΩ subject

SMTPNotificationService must provide a convenient way to create a bound service directly from these two values, equivalent to:

BoundNotificationService bind(
        ΩEmailAddressΩ emailAddress,
        ΩEmailSubjectΩ subject
);

The resulting bound service must require only the notification message when used:

BoundNotificationService notificationService =
        smtpNotificationService.bind(emailAddress, subject);

notificationService.push(message);

The address and subject must not be supplied again on every push(...).

Reject null or blank email addresses and subjects when the SMTP destination or binding is created.

Do not retain EmailMessage, EmailBody, or the current EmailSubject record merely to transport these values.

The actual SMTP delivery implementation is not part of this issue. Preserve its current unimplemented status while adapting its public API to the repaired framework.

ValueTag prerequisites

The DeTag setup is expected to provide the following ValueTags, all resolving to String:

ΩNotificationMessageΩ
ΩEmailAddressΩ
ΩEmailSubjectΩ

In particular, ΩEmailAddressΩ and ΩEmailSubjectΩ must be treated as existing prerequisites when this issue is implemented.

Do not introduce fallback Java wrapper types for these values.

Do not add, redefine, or modify the externally managed ValueTag definitions as part of this issue.

Discord scope

Rename and adapt the existing Discord stub so that it implements:

BoundNotificationService

and accepts ΩNotificationMessageΩ.

Do not implement Discord webhook delivery in this issue. That will be handled by a separate follow-up issue after this refactor has been completed.

Existing consumers and examples

Update all production-code imports, inline examples, and compile-time usages affected by the package, class, and API changes.

After this refactor, a transport-independent consumer must be able to depend solely on:

com.r35157.service.notification.BoundNotificationService

and call:

notificationService.push(message);

It must not need to import anything from an impl package.

Do not use the existing alarm-specific Pushover implementation as an architectural model. The alarm integration was implemented separately as a time-critical solution and is outside the scope of this refactor.

Out of scope

This issue must not:

  • Implement Discord webhook delivery.
  • Implement the future EvelynBurnerService.
  • Change Solana, wallet, Jupiter, or token-burning code.
  • Add notification configuration loading to Nenjim.
  • Change NenjimHubImpl.startAutoRunProcesses().
  • Refactor the existing alarm actions.
  • Implement SMTP delivery.
  • Add asynchronous notification delivery.
  • Add retry, queue, outbox, persistence, or timeout policies.
  • Introduce transport-specific message types into the public service API.
  • Add or modify unit tests.

Unit-test restriction

Do not add any unit tests for this issue.

Do not modify existing unit tests as part of this issue.

Verification must be limited to compilation, assembly, existing unaffected verification mechanisms, and relevant manual or structural checks.

Acceptance criteria

  • The notification framework resides under com.r35157.service.notification.
  • No notification classes or compatibility aliases remain under com.r35157.libs.notification.
  • BoundNotifier and AddressedNotifier no longer exist.
  • BoundNotificationService accepts ΩNotificationMessageΩ directly and has no message generic.
  • AddressedNotificationService is generic only over the destination type.
  • NotificationMessage, PushMessage, DiscordMessage, EmailMessage, EmailBody, and the EmailSubject record have been removed.
  • PushOverNotifier has become PushoverNotificationService.
  • DiscordNotifier has become DiscordNotificationService.
  • SMTPNotifier has become SMTPNotificationService.
  • Pushover retains its existing functional behavior.
  • Discord and SMTP retain their current unimplemented delivery status.
  • SMTP can bind an ΩEmailAddressΩ and an ΩEmailSubjectΩ into a BoundNotificationService.
  • A bound service requires only an ΩNotificationMessageΩ when push(...) is called.
  • Null and blank notification messages are rejected before delivery is attempted.
  • Consumers do not need transport-specific message classes.
  • All affected production code compiles successfully.
  • No unit tests have been added or modified.
## Background The existing notification framework under: ```text com.r35157.libs.notification ``` does not provide the intended transport-independent abstraction. The current API uses transport-specific message types: ```java BoundNotifier<PushMessage> BoundNotifier<DiscordMessage> BoundNotifier<EmailMessage> ``` Although these message types implement the empty `NotificationMessage` marker interface, Java generic invariance means that consumers still need to know the concrete transport-specific message type. This prevents a consumer from receiving a fully configured notification service without knowing whether it sends through Discord, Pushover, SMTP, or another future transport. The intended architecture is that a composition root creates and configures a notification service and then injects only a fully bound, transport-independent service into the consumer. For example, the future `EvelynBurnerService` must only know that it can push a notification message. It must not know whether the message is delivered through Discord, Pushover, SMTP, Telegram, or another implementation. Before implementing the Discord integration, refactor and repair the existing notification abstraction. ## Objective Replace the current notifier/message design with a service-oriented API where: * All transport-specific and destination-specific configuration is bound before the service is injected into a consumer. * A consumer only supplies a semantically tagged text message. * A consumer does not import transport-specific message classes. * The package and class names consistently describe notification services rather than notifiers. * SMTP can bind both a destination email address and a fixed subject before being passed to a consumer. ## Package relocation Move the complete notification package from: ```text com.r35157.libs.notification ``` to: ```text com.r35157.service.notification ``` Move the implementation subpackages accordingly: ```text com.r35157.service.notification.impl.discord com.r35157.service.notification.impl.pushover com.r35157.service.notification.impl.smtp ``` Use the singular package segment `service`, exactly as shown above. Update all affected imports and usages. No classes or compatibility aliases must remain under the old `com.r35157.libs.notification` package. ## Rename the public interfaces Rename: ```text BoundNotifier -> BoundNotificationService AddressedNotifier -> AddressedNotificationService ``` Move `NotificationDestination` to the new package without changing its fundamental purpose. ## Remove transport-specific message generics `BoundNotificationService` must not be generic over a message type. Its public contract must be equivalent to: ```java public interface BoundNotificationService { void push(ΩNotificationMessageΩ message) throws IOException; } ``` `ΩNotificationMessageΩ` resolves to `String`. `AddressedNotificationService` may remain generic only over its destination type: ```java public interface AddressedNotificationService< D extends NotificationDestination> { void push( D destination, ΩNotificationMessageΩ message ) throws IOException; default BoundNotificationService bind(D destination) { return message -> push(destination, message); } } ``` The precise formatting may follow existing project conventions, but the resulting type semantics must be as described above. ## Remove the obsolete message abstraction Remove the empty marker interface: ```text NotificationMessage ``` Remove the transport-specific message and wrapper classes that become unnecessary: ```text DiscordMessage PushMessage EmailMessage EmailBody EmailSubject ``` Do not replace these classes with a new `TextMessage`, record, marker interface, or general message wrapper. The semantic message type must be expressed directly by `ΩNotificationMessageΩ`. ## Message validation Every public `push(...)` entry point must reject a notification message that is: * `null` * empty * blank Such invalid input must fail before any network or transport operation is attempted. Use `IllegalArgumentException` consistently for invalid notification messages. The validation belongs in the notification service push logic. Do not introduce a separate runtime message object only to perform this validation. ## Rename the implementations Rename the existing implementation classes: ```text PushOverNotifier -> PushoverNotificationService DiscordNotifier -> DiscordNotificationService SMTPNotifier -> SMTPNotificationService ``` Use `Pushover`, not `PushOver`, in class names, filenames, package references, variables, and updated examples. The resulting implementations must use the new service interfaces: ```java PushoverNotificationService implements BoundNotificationService DiscordNotificationService implements BoundNotificationService SMTPNotificationService implements AddressedNotificationService<SMTPDestination> ``` ## Pushover behavior Adapt the existing functional Pushover implementation to accept `ΩNotificationMessageΩ` directly. Preserve its existing delivery behavior and synchronous `IOException`-based error contract. This issue is an API and architecture refactor. Do not add unrelated Pushover behavior, retries, asynchronous execution, or configuration changes. ## SMTP binding semantics An SMTP notification must be fully bound to both: * its destination email address * its fixed email subject before it is injected into a consumer. `SMTPDestination` must therefore represent the complete destination-specific SMTP binding required to send a plain notification message. It may contain: ```java ΩEmailAddressΩ emailAddress ΩEmailSubjectΩ subject ``` `SMTPNotificationService` must provide a convenient way to create a bound service directly from these two values, equivalent to: ```java BoundNotificationService bind( ΩEmailAddressΩ emailAddress, ΩEmailSubjectΩ subject ); ``` The resulting bound service must require only the notification message when used: ```java BoundNotificationService notificationService = smtpNotificationService.bind(emailAddress, subject); notificationService.push(message); ``` The address and subject must not be supplied again on every `push(...)`. Reject null or blank email addresses and subjects when the SMTP destination or binding is created. Do not retain `EmailMessage`, `EmailBody`, or the current `EmailSubject` record merely to transport these values. The actual SMTP delivery implementation is not part of this issue. Preserve its current unimplemented status while adapting its public API to the repaired framework. ## ValueTag prerequisites The DeTag setup is expected to provide the following ValueTags, all resolving to `String`: ```text ΩNotificationMessageΩ ΩEmailAddressΩ ΩEmailSubjectΩ ``` In particular, `ΩEmailAddressΩ` and `ΩEmailSubjectΩ` must be treated as existing prerequisites when this issue is implemented. Do not introduce fallback Java wrapper types for these values. Do not add, redefine, or modify the externally managed ValueTag definitions as part of this issue. ## Discord scope Rename and adapt the existing Discord stub so that it implements: ```java BoundNotificationService ``` and accepts `ΩNotificationMessageΩ`. Do not implement Discord webhook delivery in this issue. That will be handled by a separate follow-up issue after this refactor has been completed. ## Existing consumers and examples Update all production-code imports, inline examples, and compile-time usages affected by the package, class, and API changes. After this refactor, a transport-independent consumer must be able to depend solely on: ```java com.r35157.service.notification.BoundNotificationService ``` and call: ```java notificationService.push(message); ``` It must not need to import anything from an `impl` package. Do not use the existing alarm-specific Pushover implementation as an architectural model. The alarm integration was implemented separately as a time-critical solution and is outside the scope of this refactor. ## Out of scope This issue must not: * Implement Discord webhook delivery. * Implement the future `EvelynBurnerService`. * Change Solana, wallet, Jupiter, or token-burning code. * Add notification configuration loading to Nenjim. * Change `NenjimHubImpl.startAutoRunProcesses()`. * Refactor the existing alarm actions. * Implement SMTP delivery. * Add asynchronous notification delivery. * Add retry, queue, outbox, persistence, or timeout policies. * Introduce transport-specific message types into the public service API. * Add or modify unit tests. ## Unit-test restriction Do not add any unit tests for this issue. Do not modify existing unit tests as part of this issue. Verification must be limited to compilation, assembly, existing unaffected verification mechanisms, and relevant manual or structural checks. ## Acceptance criteria * The notification framework resides under `com.r35157.service.notification`. * No notification classes or compatibility aliases remain under `com.r35157.libs.notification`. * `BoundNotifier` and `AddressedNotifier` no longer exist. * `BoundNotificationService` accepts `ΩNotificationMessageΩ` directly and has no message generic. * `AddressedNotificationService` is generic only over the destination type. * `NotificationMessage`, `PushMessage`, `DiscordMessage`, `EmailMessage`, `EmailBody`, and the `EmailSubject` record have been removed. * `PushOverNotifier` has become `PushoverNotificationService`. * `DiscordNotifier` has become `DiscordNotificationService`. * `SMTPNotifier` has become `SMTPNotificationService`. * Pushover retains its existing functional behavior. * Discord and SMTP retain their current unimplemented delivery status. * SMTP can bind an `ΩEmailAddressΩ` and an `ΩEmailSubjectΩ` into a `BoundNotificationService`. * A bound service requires only an `ΩNotificationMessageΩ` when `push(...)` is called. * Null and blank notification messages are rejected before delivery is attempted. * Consumers do not need transport-specific message classes. * All affected production code compiles successfully. * No unit tests have been added or modified.
minimons added the enhancement label 2026-08-11 19:26:31 +02:00
minimons self-assigned this 2026-08-11 19:26:31 +02:00
minimons added this to the AssetAZ project 2026-08-11 19:26:31 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: r35157/com_r35157_nenjim-hubd-impl_ref#70