Skip to content

Fix null reader validation and typo in OptimizingAppendListenerDecorator - #116

Merged
vanrogu merged 1 commit into
developfrom
claude/eventstore-review-batch-2a3bnc
Aug 3, 2026
Merged

Fix null reader validation and typo in OptimizingAppendListenerDecorator#116
vanrogu merged 1 commit into
developfrom
claude/eventstore-review-batch-2a3bnc

Conversation

@vanrogu

@vanrogu vanrogu commented Aug 3, 2026

Copy link
Copy Markdown
Member

Summary

This PR addresses null safety in bookmark operations and corrects a class name typo. It adds explicit null validation for reader parameters in bookmark methods, fixes a misspelled class name, and improves documentation around the DEFAULT_PURPOSE constant and stream concretization logic.

Key Changes

  • Null reader validation: Added requireReader(String) method to explicitly validate that reader parameters are not null in placeBookmark() and getBookmark() methods. Previously, some paths were accidentally guarded by .toString() calls on already-typed String values. This makes the validation uniform across all backends and explicit in the code.

  • Class name correction: Renamed OptimizingApendListenerDecorator to OptimizingAppendListenerDecorator (fixed typo: "Apend" → "Append"). Updated all references in:

    • Implementation class and constructor
    • Test class and all test method calls
    • EventStoreImpl subscription code
    • CLAUDE.md documentation
  • Stream concretization fix: Updated EventStreamId.concretizes() to reject wildcard-purpose streams from concretizing other streams. Added check !this.isAnyPurpose() to prevent a wildcard stream from filling in another wildcard's purpose (which it cannot do). Added comprehensive test case testWildcardPurposeConcretizesNothing().

  • Documentation improvements:

    • Made EventStreamId.DEFAULT_PURPOSE public with detailed javadoc explaining its role as storage-level wire format for interop layers and raw SQL operations
    • Enhanced EventStreamId.concretizes() javadoc to clarify that wildcards concretize nothing
    • Updated EventSource bookmark method javadocs to document null-check behavior and NullPointerException contract
    • Added internal documentation to OptimizingAppendListenerDecorator explaining it is not meant for direct use
  • Code cleanup: Removed unnecessary .toString() calls on String parameters in PostgreSQL bookmark implementation (reader parameter was already typed as String)

Implementation Details

The null validation is centralized in a single requireReader(String) method with comprehensive javadoc explaining why it exists: different backends previously handled null readers inconsistently (in-memory HashMap accepts null keys; PostgreSQL returns constraint violations or silent no-ops). The explicit check ensures uniform behavior across all backends and makes the contract clear at the API level.

The wildcard concretization fix ensures the semantic contract stated in javadoc is enforced: "This stream has a specific purpose (not a wildcard)" is now validated in code, not just documentation.

https://claude.ai/code/session_01L2PUkUZA3LpkDYFxKVRMRx

…og sizes

Five independent review items.

1. Rename OptimizingApendListenerDecorator to OptimizingAppendListenerDecorator.
   The impl module *is* published -- its pom opts in with maven.deploy.skip=false, it
   is not in central-publishing's excludeArtifacts, it is in the BOM, and Maven Central
   carries it up to 0.9.1 -- so this is a real, if tiny, break rather than a free rename.
   Taken anyway: the class is 0.x, absent from the README, from CLAUDE.md's API sections
   and even from its own package-info's "Main Components", and it is applied by the store
   to every listener passed to subscribe(), so naming it from outside only gets a listener
   wrapped twice. A deprecated forwarder would enshrine the typo permanently in a package
   the ServiceLoader exists to hide, for no known caller. Javadoc now says it is internal.

2. Tighten EventStreamId.concretizes to match its javadoc rather than the reverse.
   The doc lists four conditions, the code checked three, so a wildcard-purpose stream
   concretized another wildcard-purpose stream in the same context. Unreachable through
   the library: canAppendTo reaches concretizes only after this.equals(other) has already
   returned true for that shape, and append() rejects the read-only target regardless.
   But concretizes is public API in the published api module, and "concretize" means
   supplying a value for a wildcard -- a wildcard supplies none. The doc describes what
   the method is for; the code was missing a clause. Behaviour of canAppendTo is unchanged.

3. Drop reader.toString() on values already typed String (EventStoreImpl.getBookmark,
   PostgresEventStorageImpl.bookmark/removeBookmark). Note these did not stringify a null
   into "null" -- they threw NPE -- so they were accidental null guards, and removing them
   alone would have left null readers undefined and backend-dependent: a HashMap null key
   in memory, a not-null violation on a Postgres place, a silent no-op on a remove and an
   empty Optional on a get. Replaced with one explicit Objects.requireNonNull at the store
   boundary, so every backend answers the same way, plus @throws on EventSource. The SPI
   contract is deliberately left alone: guarding one backend and not the others would
   recreate the divergence.

4. notify(BookmarkPlacedNotification) logged eventuallyConsistentSubscribers.size() in
   both debug lines while describing bookmark listeners; now bookmarkSubscribers.size().

5. Make EventStreamId.DEFAULT_PURPOSE public. CLAUDE.md already points operators at it for
   raw SQL and interop, where the value is needed; publishing the constant is additive and
   binary-compatible, and lets an interop layer bind what the library binds.

Verified: mvn clean install, api + impl module tests (150), and the TCK against the
in-memory backends (426 tests, 0 failures). Docker was unavailable in this environment,
so the PostgreSQL backends of the TCK were not re-run; the only Postgres change is
dropping two no-op toString() calls on bound statement parameters.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L2PUkUZA3LpkDYFxKVRMRx
@vanrogu
vanrogu merged commit 9c6958b into develop Aug 3, 2026
@vanrogu
vanrogu deleted the claude/eventstore-review-batch-2a3bnc branch August 3, 2026 13:02
vanrogu pushed a commit that referenced this pull request Aug 3, 2026
Two conflicts, both where develop's review batch (#116) landed on lines this
branch had also changed.

1. OptimizingApendListenerDecorator was renamed to OptimizingAppendListenerDecorator
   on develop while this branch added a delegate() accessor to it. Took the rename
   and kept the accessor, which notifyQuietly needs to log the listener the caller
   subscribed rather than the decorator wrapping it. The accessor is now placed
   after the constructor -- on this branch it had landed between the constructor's
   javadoc and the constructor, orphaning that javadoc.

   The instanceof in EventStoreImpl.notifyQuietly still named the old class. Git
   could not see that one: the rename and the reference are in different files, so
   it merged cleanly and would have failed to compile.

2. notify(BookmarkPlacedNotification) -- develop fixed the debug line to log
   bookmarkSubscribers.size() instead of eventuallyConsistentSubscribers.size();
   this branch changed the line below it to route through notifyQuietly. Both kept.

Verified after the merge: mvn clean install, api + impl + inmem module tests (155),
and the TCK against both in-memory backends (428 tests, 0 failures). The one error
is EventImportRoundTripTest, which hard-requires a Postgres container -- no Docker
in this environment, and it fails identically on develop.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017mPz52fur5brEvq8sskF8Z
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants