4.5 KiB
Context
See proposal.md for motivation. The current notification framework combines an API package and three implementation packages under com.r35157.libs.notification; its generic message wrappers leak transport choices into consumers. The code currently has no external production consumer, but each implementation contains an inline compile-time usage example. The separate alarm-specific Pushover action is intentionally outside this module and must remain unchanged.
The repository temporarily hosts immature API and implementation artifacts together. Package ownership must nevertheless express a notification-service module that can later be extracted, with API types separated from transport implementations.
Goals / Non-Goals
Goals:
- Establish a small stable API package containing only the bound service, addressed service, and destination abstraction.
- Make transport implementations consume the three existing String-backed ValueTags directly.
- Preserve the existing Pushover HTTP request and synchronous error behavior while enforcing message validation before transport work.
- Make SMTP's complete destination configuration immutable and bindable before injection into a consumer.
Non-Goals:
- Implementing Discord or SMTP delivery.
- Adding notification configuration, asynchronous delivery, retries, queues, persistence, or timeout policies.
- Refactoring the alarm-specific Pushover implementation or changing
NenjimHubImpl.startAutoRunProcesses(). - Adding or modifying automated tests.
Decisions
Separate API and transport implementation packages under the service owner
The API will live directly in com.r35157.service.notification, while Discord, Pushover, and SMTP remain in impl.discord, impl.pushover, and impl.smtp below it. This follows the repository's future module boundaries while keeping the current single-project layout. Retaining aliases in the old package was rejected because it would prolong the incompatible abstraction and violates the issue's clean migration requirement.
Represent messages directly with the semantic ValueTag
All push contracts use ΩNotificationMessageΩ directly. Validation is performed at each concrete public push entry point before any delivery work. A replacement record, marker hierarchy, or transport generic was rejected because it would recreate the coupling the change removes.
Keep addressed binding minimal and destination-only
AddressedNotificationService<D> captures a destination in its default bind(D) result. SMTPDestination captures both email address and subject and validates both in its compact constructor. SMTPNotificationService adds a convenience overload accepting these two values and delegates to the standard destination binding. Keeping the subject on each message was rejected because it would leave the service only partially configured when injected.
Preserve transport maturity independently
Pushover keeps its current form encoding, endpoint, synchronous HTTP call, status handling, and IOException contract. Discord and SMTP validate message input first and then retain their UnsupportedOperationException behavior for otherwise valid messages. This prevents a refactor from silently expanding either transport's behavior.
Risks / Trade-offs
- [Breaking source migration] All old names and packages disappear at once. → Migrate every production reference and inline example in the same change, then structurally search for old names.
- [Validation duplicated across implementations] Each implementation owns a small null/blank guard. → Prefer explicit local validation over adding an unnecessary public utility or runtime message wrapper; keep the exception type and condition identical.
- [No automated regression coverage] The issue explicitly excludes test changes. → Compile both source sets, run the normal assembly, and perform strict structural checks of packages, types, call sites, and generated-source boundaries.
Migration Plan
- Introduce the renamed API and transport implementations in the new package using the existing ValueTags.
- Update all production imports and inline examples to the new contracts.
- Remove the complete old package and every obsolete notifier/message type without aliases.
- Compile and assemble from
.tjava, validate OpenSpec strictly, and verify the source tree contains no stale API references.
Rollback consists of reverting the complete source and OpenSpec change together; the old and new APIs are intentionally not supported in parallel.