Skip to content

feat(eventsourcing): move aggregate id scanning to EventStore - #2730

Merged
Ahoo-Wang merged 19 commits into
mainfrom
codex/eventstore-aggregate-id-scanner
Jul 7, 2026
Merged

feat(eventsourcing): move aggregate id scanning to EventStore#2730
Ahoo-Wang merged 19 commits into
mainfrom
codex/eventstore-aggregate-id-scanner

Conversation

@Ahoo-Wang

@Ahoo-Wang Ahoo-Wang commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Goal

Move aggregate ID scanning to the EventStore boundary so batch snapshot regeneration and state-event resend enumerate aggregates from the event stream source of truth, not from snapshot storage.

Changes

  • EventStore now owns AggregateIdScanner; SnapshotStore no longer exposes aggregate ID scanning.
  • In-memory, routing, metrics, tracing, mock, Mongo, Redis, and Elasticsearch event stores now forward or implement scanAggregateId.
  • WebFlux batch handlers and Spring Boot route wiring now inject/use EventStore for aggregate ID scanning.
  • Redis event store uses bucketed Redis Cluster hash-tag keys for event streams and aggregate-id scanner indexes, keeping append and index maintenance in one Lua script without CROSSSLOT risk.
  • Redis scanner stores aggregateId and tenantId in a single ordered zset member, so append no longer maintains a separate tenant hash; the Lua script writes the scanner index only when version == 1.
  • Redis scanner reads all 128 bucket indexes, merges candidates in lexicographical aggregateId.id order, and relies on the project invariant that aggregateId is globally unique.
  • TCK coverage moved from SnapshotStoreSpec to EventStoreSpec, with coverage for ordering, afterId, limit, named aggregate/context filtering, tenant preservation, one result per aggregate, and isolated scan cursors for integration stores.
  • Mongo/Redis scan tests moved to event store tests, Redis cluster-key/index-member tests were added, and docs describe scanner ownership plus the Redis event-store key layout.
  • Review follow-up documents Redis aggregate-id index members as {aggregateId}<NUL>{tenantId}, keeps scanner default arguments/default unsupported behavior on AggregateIdScanner, covers the default-call path through EventStore, and makes Redis aggregate-id scanning return empty for the terminal LAST_ID cursor without issuing an invalid lex range query.
  • Review follow-up skips ignored or uninitialized streams in StateEventCompensator.resend, renames the Redis aggregate-id index member parser to toAggregateIdFromIndexMember, and refreshes the OpenAPI compatibility snapshot to 8.8.0.
  • Review follow-up skips later resend streams when the state was never sourced from an initial event, and bounds Redis aggregate-id bucket scan fan-out to 16 concurrent bucket queries.
  • Review follow-up skips ignored failed-create streams and still-uninitialized later streams during snapshot regeneration, with direct handler and batch-entry regression tests that save no invalid snapshot.
  • Infrastructure benchmark reports now record host/container client placement, Docker server/VM settings, and Redis/Mongo container image fingerprints so Docker Desktop networking effects are visible beside benchmark scores.
  • Infrastructure benchmark Docker Compose profiles now use wow-benchmarks/docker/benchmark.env for image tags, container names, host ports, tmpfs sizes, Mongo credentials, and Mongo cache sizing; Gradle benchmark tasks and JMH fixtures consume the same runtime config.
  • Infrastructure benchmark Compose profiles now use Docker's local log driver and lower-frequency steady-state healthchecks to reduce Redis/Mongo benchmark runtime noise while preserving fast startup health probing.
  • Project version metadata, onboarding docs, and the quick Infrastructure E2E benchmark report are updated to 8.8.0; the infrastructure report was regenerated from the existing JMH JSON rather than hand-editing benchmark rows.

Verification

  • ./gradlew :wow-opentelemetry:contractTest --tests me.ahoo.wow.opentelemetry.eventsourcing.TracingEventStoreTest --rerun-tasks --no-parallel failed before the tracing fix with UnsupportedOperationException, then passed after forwarding scanAggregateId.
  • ./gradlew :wow-mongo:test --tests me.ahoo.wow.mongo.MongoEventStoreScanTest --rerun-tasks --no-parallel failed before the Mongo named aggregate filter fix, then passed after adding contextName/aggregateName filters.
  • ./gradlew :wow-core:contractTest --tests me.ahoo.wow.eventsourcing.InMemoryEventStoreTest :wow-core:test --tests me.ahoo.wow.eventsourcing.RoutingEventStoreTest --rerun-tasks --no-parallel passed.
  • ./gradlew :wow-core:check :wow-webflux:check :wow-mongo:test :wow-redis:test :wow-elasticsearch:test :wow-spring-boot-starter:test :wow-opentelemetry:check :wow-mock:check --no-parallel passed.
  • ./gradlew :wow-redis:test --tests 'me.ahoo.wow.redis.eventsourcing.EventStreamKeyConverterTest' --tests 'me.ahoo.wow.redis.eventsourcing.RedisEventStoreScanTest' --no-parallel passed before and after the Redis index-member optimization.
  • ./gradlew :wow-redis:check --no-parallel passed on 5098cc103.
  • ./gradlew :wow-benchmarks:benchmarkQuickInfrastructureE2E :wow-benchmarks:generateInfrastructureBenchmarkReport --no-parallel passed on 5098cc103; Redis improved from 6075.14 to 6688.35 ops/s at 1 thread and from 18583.21 to 19338.45 ops/s at 4 threads versus the pre-optimization local run.
  • ./gradlew :wow-benchmarks:generateInfrastructureBenchmarkReport --no-parallel passed on 8a8d697fe; quick-infrastructure-e2e.md now records host JVM, Docker Server 29.6.1, Docker Desktop networkType=gvisor, redis:7.4.9-alpine, and mongo:8.3.4 metadata.
  • rg -n "Infrastructure Runtime|Benchmark Client|Docker Server|Docker Desktop VM|Redis Container|Mongo Container|Network Note|gvisor|mongo:8.3.4|redis:7.4.9-alpine" wow-benchmarks/results/reports/quick-infrastructure-e2e.md passed on 8a8d697fe.
  • docker compose --env-file wow-benchmarks/docker/benchmark.env -f wow-benchmarks/docker/compose.redis.yml config passed on 2735ed3be.
  • docker compose --env-file wow-benchmarks/docker/benchmark.env -f wow-benchmarks/docker/compose.mongo.yml config passed on 2735ed3be.
  • WOW_BENCHMARK_REDIS_HOST_PORT=6380 docker compose --env-file wow-benchmarks/docker/benchmark.env -f wow-benchmarks/docker/compose.redis.yml config | rg 'published: "6380"' passed on 2735ed3be.
  • WOW_BENCHMARK_MONGO_HOST_PORT=27018 WOW_BENCHMARK_MONGO_WIREDTIGER_CACHE_GB=1 docker compose --env-file wow-benchmarks/docker/benchmark.env -f wow-benchmarks/docker/compose.mongo.yml config | rg 'published: "27018"|--wiredTigerCacheSizeGB|"1"' passed on 2735ed3be.
  • ./gradlew :wow-benchmarks:test --tests me.ahoo.wow.benchmark.infrastructure.BenchmarkInfrastructureConfigTest --no-parallel failed before the Mongo credential URI-encoding fix, then passed on 2735ed3be.
  • ./gradlew :wow-benchmarks:check --no-parallel passed on 2735ed3be, 8a8d697fe, a6f35d766, and f776c84d1.
  • docker compose --env-file wow-benchmarks/docker/benchmark.env -f wow-benchmarks/docker/compose.redis.yml config | rg 'driver: local|interval: 30s|start_interval: 2s|max-size: 10m|published: "6379"' passed on a6f35d766.
  • docker compose --env-file wow-benchmarks/docker/benchmark.env -f wow-benchmarks/docker/compose.mongo.yml config | rg 'driver: local|interval: 1m0s|start_interval: 2s|max-size: 10m|published: "27017"' passed on a6f35d766.
  • docker compose --env-file wow-benchmarks/docker/benchmark.env -f wow-benchmarks/docker/compose.redis.yml up -d --force-recreate --wait passed on a6f35d766; wow-benchmark-redis was healthy with log=local, health=30s, start=2s.
  • docker compose --env-file wow-benchmarks/docker/benchmark.env -f wow-benchmarks/docker/compose.mongo.yml up -d --force-recreate --wait passed on a6f35d766; wow-benchmark-mongo was healthy with log=local, health=1m0s, start=2s.
  • ./gradlew :wow-benchmarks:benchmarkQuickInfrastructureE2E :wow-benchmarks:generateInfrastructureBenchmarkReport --no-parallel passed on a6f35d766; Mongo improved from 2851.32 to 4670.54 ops/s at 1 thread and from 7424.67 to 13799.50 ops/s at 4 threads versus the previous local report, while Redis changed from 6688.35 to 6243.38 ops/s at 1 thread and from 19338.45 to 18405.67 ops/s at 4 threads.
  • ./gradlew :wow-benchmarks:generateInfrastructureBenchmarkReport --no-parallel passed on f776c84d1; quick-infrastructure-e2e.md now records Version: 8.8.0.
  • pnpm docs:build passed on f776c84d1 and f9629e1cd.
  • ./gradlew :wow-core:test --tests me.ahoo.wow.eventsourcing.EventStoreTest --tests me.ahoo.wow.eventsourcing.AggregateIdScannerTest --no-parallel passed on f9629e1cd and 3c01ac2f0.
  • rg -n "tenant HASH|租户 HASH|:tenants|bucket-aligned tenant|同分桶的租户" documentation/docs/en/guide/extensions/redis.md documentation/docs/zh/guide/extensions/redis.md returned no matches on f9629e1cd.
  • git diff --check passed on 2735ed3be, a6f35d766, f776c84d1, f9629e1cd, and 3c01ac2f0.
  • ./gradlew :wow-core:test --tests me.ahoo.wow.eventsourcing.EventStoreTest --tests me.ahoo.wow.eventsourcing.AggregateIdScannerTest --no-parallel passed on fb88a7aac.
  • ./gradlew :wow-redis:test --tests me.ahoo.wow.redis.eventsourcing.RedisEventStoreScanTest --no-parallel passed on fb88a7aac.
  • git diff --check passed on fb88a7aac.
  • ./gradlew :wow-core:test --tests me.ahoo.wow.eventsourcing.EventStoreTest --tests me.ahoo.wow.eventsourcing.AggregateIdScannerTest --no-parallel passed on 241da496f.
  • ./gradlew :wow-redis:test --tests me.ahoo.wow.redis.eventsourcing.RedisEventStoreScanTest --no-parallel passed on 241da496f.
  • git diff --check passed on 241da496f.
  • ./gradlew :wow-core:test --tests 'me.ahoo.wow.event.compensation.StateEventCompensatorTest.resend skips ignored initial error streams' --stacktrace --no-parallel failed before the fix with expected value: 0; actual value: 1, then passed on 4909c5202.
  • ./gradlew :wow-openapi:test --tests 'me.ahoo.wow.openapi.snapshot.OpenApiCompatibilitySnapshotTest' -Dwow.snapshot.update=true --stacktrace --no-parallel passed on 4909c5202 to regenerate the OpenAPI snapshot.
  • ./gradlew :wow-core:test --tests 'me.ahoo.wow.event.compensation.StateEventCompensatorTest' :wow-redis:test --tests 'me.ahoo.wow.redis.eventsourcing.EventStreamKeyConverterTest' --tests 'me.ahoo.wow.redis.eventsourcing.RedisEventStoreScanTest' :wow-openapi:test --tests 'me.ahoo.wow.openapi.snapshot.OpenApiCompatibilitySnapshotTest' :wow-webflux:test --tests 'me.ahoo.wow.webflux.route.event.state.ResendStateEventHandlerTest' --stacktrace --no-parallel passed on 4909c5202.
  • git diff --check passed on 4909c5202.
  • ./gradlew detekt --stacktrace --no-parallel passed on 4909c5202 after fixing the CI-reported import-order and argument-wrapping issues.
  • ./gradlew :wow-core:test --tests 'me.ahoo.wow.event.compensation.StateEventCompensatorTest.resend skips later streams when initial stream did not source state' --stacktrace --no-parallel failed before the fix with SourcingVersionConflictException, then passed on 6cde00dc8.
  • ./gradlew :wow-redis:test --tests 'me.ahoo.wow.redis.eventsourcing.RedisEventStoreScanTest.scan aggregate id should bound bucket query concurrency' --stacktrace --no-parallel failed before the fix with 128 subscribed bucket queries instead of 16, then passed on 6cde00dc8.
  • ./gradlew :wow-core:test --tests 'me.ahoo.wow.event.compensation.StateEventCompensatorTest' :wow-redis:test --tests 'me.ahoo.wow.redis.eventsourcing.RedisEventStoreScanTest' --stacktrace --no-parallel passed on 6cde00dc8.
  • git diff --check passed on 6cde00dc8.
  • ./gradlew detekt --stacktrace --no-parallel passed on 6cde00dc8.
  • ./gradlew :wow-webflux:test --tests 'me.ahoo.wow.webflux.route.snapshot.BatchRegenerateSnapshotHandlerFunctionTest.should skip snapshot regeneration when initial stream did not source state' --stacktrace --no-parallel failed before the fix with SourcingVersionConflictException, then passed on 11ffd8662.
  • ./gradlew :wow-webflux:test --tests 'me.ahoo.wow.webflux.route.snapshot.BatchRegenerateSnapshotHandlerFunctionTest' --stacktrace --no-parallel passed on 11ffd8662.
  • ./gradlew :wow-webflux:check --stacktrace --no-parallel passed on 11ffd8662.
  • git diff --check passed on 11ffd8662.
  • GitHub PR checks on latest head 11ffd8662: Benchmark Smoke, Compensation Core Test, Contract Test, Integration Test, Java compatibility Test, Local Test, Static Analysis, Compensation Domain Test, Codacy Static Code Analysis, codecov/patch, and codecov/project passed.

Risks and Notes

  • Breaking API change: SnapshotStore.scanAggregateId is removed from the public snapshot-store API. Custom snapshot stores must drop that method; custom event stores should implement scanAggregateId.
  • Breaking Redis data change: Redis event stream keys and scanner index keys intentionally changed to bucketed hash-tag keys. Existing Redis data written with the old key format will not be read by the new Redis event store without an explicit migration, rebuild, or environment reset.
  • Redis migration path: rewrite old event stream/request/index keys into the new bucketed format, or rebuild Redis event-store data from the canonical event source before enabling the new scanner in an existing Redis-backed environment.
  • Redis scanner performance: each page scans 128 bucket indexes and merges at most 128 * limit candidates in memory. This avoids a single hot Redis slot and keeps Lua append atomic, but it increases scan fan-out.
  • Redis index-member encoding preserves tenant data inside the zset member and assumes normal non-empty aggregate IDs; generated IDs satisfy this, and the implementation depends on the stated global uniqueness of aggregateId.
  • Infrastructure benchmark comparisons should account for the recorded benchmark-client and Docker Desktop network topology; the current local report shows a host JVM client connecting through Docker Desktop networkType=gvisor to Redis/Mongo containers.
  • Custom benchmark Compose env files should be passed to Gradle with -PbenchmarkDockerEnvFile=/path/to/env-file; otherwise Docker may run one topology while reports/JMH use the default benchmark.env.
  • The Compose runtime-noise changes improved Mongo in the local quick infrastructure run, but Redis moved slightly lower in the same run; treat these as local directional benchmark results, not universal infrastructure capacity claims.
  • Only quick-infrastructure-e2e.md was regenerated in this update because it has current JMH JSON in wow-benchmarks/results/jmh/quick/infrastructure-e2e; the older framework/webflux/grouped quick reports remain historical snapshots until their benchmark suites are rerun.
  • Runtime fallback: EventStore keeps a default scanAggregateId that returns UnsupportedOperationException to reduce source breakage, but batch endpoints need a real implementation at runtime.
  • State-event batch resend still advances the aggregate-id cursor for ignored failed-create streams, but StateEventCompensator.resend now returns zero and emits no state event for ignored or still-uninitialized state.
  • Snapshot batch regeneration still advances the aggregate-id cursor for ignored failed-create streams, but RegenerateSnapshotHandler now writes no snapshot until state is initialized.
  • Redis aggregate-id scanning still fans out across all 128 bucket indexes per page, but bucket queries are now subscribed in bounded batches of 16 to reduce connection-pool pressure.
  • Rollback is a normal revert of this PR; batch handlers would need to return to snapshot-store scanning if this boundary change is backed out.

@codacy-production

codacy-production Bot commented Jul 6, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 106 complexity

Metric Results
Complexity 106

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@codecov

codecov Bot commented Jul 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.07407% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.29%. Comparing base (62ac799) to head (11ffd86).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
...ticsearch/eventsourcing/ElasticsearchEventStore.kt 85.71% 0 Missing and 3 partials ⚠️
...oo/wow/event/compensation/StateEventCompensator.kt 77.77% 1 Missing and 1 partial ⚠️
...ain/kotlin/me/ahoo/wow/eventsourcing/EventStore.kt 0.00% 1 Missing ⚠️
...wow/redis/eventsourcing/EventStreamKeyConverter.kt 94.73% 0 Missing and 1 partial ⚠️
...ebflux/route/snapshot/RegenerateSnapshotHandler.kt 85.71% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #2730      +/-   ##
============================================
+ Coverage     93.27%   93.29%   +0.01%     
- Complexity     4821     4838      +17     
============================================
  Files           839      839              
  Lines         16065    16111      +46     
  Branches       1190     1198       +8     
============================================
+ Hits          14985    15031      +46     
+ Misses          675      672       -3     
- Partials        405      408       +3     
Flag Coverage Δ
contract 50.90% <56.25%> (?)
integration 74.51% <85.05%> (?)
local 85.52% <72.59%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Ahoo-Wang
Ahoo-Wang marked this pull request as ready for review July 6, 2026 21:21
Copilot AI review requested due to automatic review settings July 6, 2026 21:21

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR moves aggregate ID scanning (scanAggregateId) from the snapshot storage boundary to the event store boundary, so batch snapshot regeneration and state-event resend enumerate aggregates from the event stream (source of truth) instead of snapshot storage.

Changes:

  • Make EventStore own AggregateIdScanner and remove aggregate ID scanning from SnapshotStore.
  • Implement/forward scanAggregateId across core/in-memory/routing/metrics/tracing and infrastructure event stores (Mongo/Redis/Elasticsearch), plus update WebFlux handlers and Spring Boot wiring to use EventStore.
  • Migrate TCK coverage from SnapshotStoreSpec to EventStoreSpec and update documentation to reflect the new ownership.

Reviewed changes

Copilot reviewed 46 out of 46 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
wow-webflux/src/test/kotlin/me/ahoo/wow/webflux/route/state/ResendStateEventHandlerFunctionTest.kt Update route handler test wiring to pass EventStore instead of SnapshotStore.
wow-webflux/src/test/kotlin/me/ahoo/wow/webflux/route/snapshot/BatchRegenerateSnapshotHandlerFunctionTest.kt Update batch snapshot regeneration tests to rely on event-store scanning.
wow-webflux/src/test/kotlin/me/ahoo/wow/webflux/route/event/state/ResendStateEventHandlerTest.kt Update resend tests to scan aggregates via EventStore.
wow-webflux/src/main/kotlin/me/ahoo/wow/webflux/route/snapshot/BatchRegenerateSnapshotHandlerFunction.kt Switch batch regeneration enumeration from snapshot scanning to event-store scanning.
wow-webflux/src/main/kotlin/me/ahoo/wow/webflux/route/event/state/ResendStateEventHandler.kt Switch resend enumeration from snapshot scanning to event-store scanning.
wow-webflux/src/main/kotlin/me/ahoo/wow/webflux/route/event/state/ResendStateEventFunction.kt Propagate constructor/wiring change from SnapshotStore to EventStore.
wow-spring-boot-starter/src/test/kotlin/me/ahoo/wow/spring/boot/starter/eventsourcing/snapshot/SnapshotAutoConfigurationTest.kt Remove snapshot-store scan delegation now that SnapshotStore no longer scans.
wow-spring-boot-starter/src/test/kotlin/me/ahoo/wow/spring/boot/starter/eventsourcing/routing/StorageRoutingAutoConfigurationTest.kt Remove snapshot-store scanning override from test doubles.
wow-spring-boot-starter/src/test/kotlin/me/ahoo/wow/spring/boot/starter/eventsourcing/routing/StorageRouteResolverTest.kt Remove snapshot-store scanning override from test doubles.
wow-spring-boot-starter/src/main/kotlin/me/ahoo/wow/spring/boot/starter/webflux/WebFluxAutoConfiguration.kt Update route module wiring to inject EventStore for scanning.
wow-spring-boot-starter/src/main/kotlin/me/ahoo/wow/spring/boot/starter/webflux/route/EventRouteModule.kt Update module constructor/factory wiring to pass EventStore.
wow-redis/src/test/kotlin/me/ahoo/wow/redis/eventsourcing/RedisEventStoreScanTest.kt Move Redis scan behavior test from snapshot store to event store.
wow-redis/src/main/kotlin/me/ahoo/wow/redis/eventsourcing/RedisSnapshotStore.kt Remove snapshot-store scanning implementation.
wow-redis/src/main/kotlin/me/ahoo/wow/redis/eventsourcing/RedisEventStore.kt Add scanAggregateId implementation via key scanning for event streams.
wow-redis/src/main/kotlin/me/ahoo/wow/redis/eventsourcing/EventStreamKeyConverter.kt Add reverse conversion from Redis key to AggregateId for scanning.
wow-opentelemetry/src/main/kotlin/me/ahoo/wow/opentelemetry/snapshot/TracingSnapshotStore.kt Remove snapshot-store scan forwarding.
wow-opentelemetry/src/main/kotlin/me/ahoo/wow/opentelemetry/eventsourcing/TracingEventStore.kt Forward scanAggregateId through tracing decorator.
wow-mongo/src/test/kotlin/me/ahoo/wow/mongo/MongoSnapshotStoreScanTest.kt Remove snapshot-store scan test (scanner ownership moved).
wow-mongo/src/test/kotlin/me/ahoo/wow/mongo/MongoEventStoreScanTest.kt Add event-store scan tests for sorting and named-aggregate filtering.
wow-mongo/src/main/kotlin/me/ahoo/wow/mongo/MongoSnapshotStore.kt Remove snapshot-store scanning implementation.
wow-mongo/src/main/kotlin/me/ahoo/wow/mongo/MongoEventStore.kt Add scanAggregateId implementation backed by initial-version records.
wow-elasticsearch/src/main/kotlin/me/ahoo/wow/elasticsearch/eventsourcing/ElasticsearchSnapshotStore.kt Remove snapshot-store scanning implementation.
wow-elasticsearch/src/main/kotlin/me/ahoo/wow/elasticsearch/eventsourcing/ElasticsearchEventStore.kt Add scanAggregateId implementation for event-stream index.
wow-core/src/test/kotlin/me/ahoo/wow/eventsourcing/snapshot/SnapshotDispatcherTest.kt Remove snapshot-store scan forwarding from test double.
wow-core/src/test/kotlin/me/ahoo/wow/eventsourcing/snapshot/RoutingSnapshotStoreTest.kt Remove snapshot-store routing scan test and related tracking fields.
wow-core/src/test/kotlin/me/ahoo/wow/eventsourcing/RoutingEventStoreTest.kt Add routing test ensuring scanAggregateId is routed to the configured store.
wow-core/src/test/kotlin/me/ahoo/wow/eventsourcing/AggregateIdScannerTest.kt Update scanner test to use event-store scanning with mock event streams.
wow-core/src/main/kotlin/me/ahoo/wow/metrics/MetricSnapshotStore.kt Remove snapshot scanning metric wrapper.
wow-core/src/main/kotlin/me/ahoo/wow/metrics/MetricEventStore.kt Add metric wrapper around event-store scanAggregateId.
wow-core/src/main/kotlin/me/ahoo/wow/eventsourcing/snapshot/SnapshotStore.kt Remove AggregateIdScanner from SnapshotStore contract and drop no-op scan.
wow-core/src/main/kotlin/me/ahoo/wow/eventsourcing/snapshot/RoutingSnapshotStore.kt Remove routing scan forwarding from snapshot store.
wow-core/src/main/kotlin/me/ahoo/wow/eventsourcing/snapshot/InMemorySnapshotStore.kt Remove in-memory snapshot scan implementation.
wow-core/src/main/kotlin/me/ahoo/wow/eventsourcing/RoutingEventStore.kt Add routing forwarding for scanAggregateId.
wow-core/src/main/kotlin/me/ahoo/wow/eventsourcing/InMemoryEventStore.kt Add in-memory event-store scanning over stored aggregates.
wow-core/src/main/kotlin/me/ahoo/wow/eventsourcing/EventStore.kt Make EventStore extend AggregateIdScanner and provide a default scanAggregateId.
wow-core/src/contractTest/kotlin/me/ahoo/wow/eventsourcing/EventSourcingStateAggregateRepositoryTest.kt Remove snapshot scan override from test double.
test/wow-tck/src/main/kotlin/me/ahoo/wow/tck/eventsourcing/snapshot/SnapshotStoreSpec.kt Remove scan-related TCK tests from snapshot-store spec.
test/wow-tck/src/main/kotlin/me/ahoo/wow/tck/eventsourcing/EventStoreSpec.kt Add scan-related TCK tests to event-store spec (ordering, filtering, tenant, uniqueness).
test/wow-mock/src/main/kotlin/me/ahoo/wow/eventsourcing/mock/DelaySnapshotStore.kt Remove scan delay wrapper from snapshot store mock.
test/wow-mock/src/main/kotlin/me/ahoo/wow/eventsourcing/mock/DelayEventStore.kt Add scan delay wrapper to event store mock.
documentation/docs/zh/guide/snapshot.md Document scanner ownership on EventStore (zh).
documentation/docs/zh/guide/extensions/mongo.md Update diagrams for scanner ownership on EventStore (zh).
documentation/docs/zh/guide/eventstore.md Document EventStore scanning responsibility and signature (zh).
documentation/docs/en/guide/snapshot.md Document scanner ownership on EventStore (en).
documentation/docs/en/guide/extensions/mongo.md Update diagrams for scanner ownership on EventStore (en).
documentation/docs/en/guide/eventstore.md Document EventStore scanning responsibility and signature (en).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +103 to +107
override fun scanAggregateId(
namedAggregate: NamedAggregate,
afterId: String,
limit: Int
): Flux<AggregateId> =
Comment on lines +113 to +117
EventStreamKeyConverter.toAggregateId(namedAggregate, it)
}.filter {
it.id > afterId
}.sort(compareBy { it.id })
.take(limit.toLong())
@Ahoo-Wang Ahoo-Wang changed the title [codex] scan aggregate ids with event store feat(eventsourcing): move aggregate id scanning to EventStore Jul 6, 2026
Copilot AI review requested due to automatic review settings July 6, 2026 22:18

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 49 out of 49 changed files in this pull request and generated 3 comments.

Comment on lines +14 to +18
local eventStreamPrefixKey = contextAlias .. "." .. aggregateName .. ":es";

local eventStreamKey = eventStreamPrefixKey .. ":" .. aggregateIdKey
local aggregateIdIndexKey = eventStreamPrefixKey .. ":ids"
local aggregateTenantIndexKey = eventStreamPrefixKey .. ":tenants"

```kotlin
interface EventStore {
interface EventStore : AggregateIdScanner {

```kotlin
interface EventStore {
interface EventStore : AggregateIdScanner {
@Ahoo-Wang Ahoo-Wang added the enhancement New feature or request label Jul 6, 2026
Copilot AI review requested due to automatic review settings July 7, 2026 00:22

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 52 out of 52 changed files in this pull request and generated 1 comment.

Comment on lines +25 to +27
local result = redis.call("ZADD", eventStreamKey, version, value);
redis.call("HSET", aggregateTenantIndexKey, aggregateId, tenantId);
redis.call("ZADD", aggregateIdIndexKey, 0, aggregateId);
Copilot AI review requested due to automatic review settings July 7, 2026 02:15

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 54 out of 54 changed files in this pull request and generated 4 comments.

Comment on lines +160 to +166
Aggregate ID ZSET key: {{contextAlias}.{aggregateName}:es:{bucket}}:ids
Score: 0
Member: {aggregateId}

Aggregate tenant HASH key: {{contextAlias}.{aggregateName}:es:{bucket}}:tenants
Field: {aggregateId}
Value: {tenantId}
```
### Aggregate ID Scanning

`EventStore.scanAggregateId` scans bucketed aggregate ID indexes and merges the results in lexicographical order. Aggregate IDs are globally unique, so the scanner stores one aggregate ID member and resolves its tenant from the bucket-aligned tenant HASH.
Comment on lines +160 to +166
聚合 ID ZSET Key: {{contextAlias}.{aggregateName}:es:{bucket}}:ids
Score: 0
Member: {aggregateId}

聚合租户 HASH Key: {{contextAlias}.{aggregateName}:es:{bucket}}:tenants
Field: {aggregateId}
Value: {tenantId}
```
### 聚合 ID 扫描

`EventStore.scanAggregateId` 会扫描分桶的聚合 ID 索引,并按字典序合并结果。由于 `aggregateId` 全局唯一,scanner 只存储一个聚合 ID 成员,并从同分桶的租户 HASH 中解析对应的租户。
Copilot AI review requested due to automatic review settings July 7, 2026 02:52

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 63 out of 63 changed files in this pull request and generated 4 comments.

Comment thread documentation/docs/en/guide/extensions/redis.md Outdated
```
### Aggregate ID Scanning

`EventStore.scanAggregateId` scans bucketed aggregate ID indexes and merges the results in lexicographical order. Aggregate IDs are globally unique, so the scanner stores one aggregate ID member and resolves its tenant from the bucket-aligned tenant HASH.
Comment thread documentation/docs/zh/guide/extensions/redis.md Outdated
```
### 聚合 ID 扫描

`EventStore.scanAggregateId` 会扫描分桶的聚合 ID 索引,并按字典序合并结果。由于 `aggregateId` 全局唯一,scanner 只存储一个聚合 ID 成员,并从同分桶的租户 HASH 中解析对应的租户。
@Ahoo-Wang

Copy link
Copy Markdown
Owner Author

@codex

Copilot AI review requested due to automatic review settings July 7, 2026 03:22

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 72 out of 72 changed files in this pull request and generated 1 comment.

Comment on lines +103 to +107
override fun scanAggregateId(
namedAggregate: NamedAggregate,
afterId: String,
limit: Int
): Flux<AggregateId> =
Copilot AI review requested due to automatic review settings July 7, 2026 03:26
@Ahoo-Wang

Copy link
Copy Markdown
Owner Author

@codex

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 72 out of 72 changed files in this pull request and generated 1 comment.

Comment on lines +115 to +123
val range = Range.open(toAggregateIdIndexMemberLowerBound(afterId), AggregateIdScanner.LAST_ID)
val rangeLimit = Limit.limit().count(limit)
return Flux.range(0, AGGREGATE_ID_INDEX_BUCKETS)
.flatMap { bucket ->
scanAggregateIdBucket(namedAggregate, bucket, range, rangeLimit)
}
.sort(compareBy { it.id })
.take(limit.toLong())
}
Copilot AI review requested due to automatic review settings July 7, 2026 03:38

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3c01ac2f06

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread gradle.properties

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 73 out of 73 changed files in this pull request and generated 2 comments.

Comment on lines +54 to +58
fun toAggregateIdIndexMember(namedAggregate: NamedAggregate, member: String): AggregateId {
val delimiterIndex = member.indexOf(INDEX_MEMBER_DELIMITER)
require(delimiterIndex > 0) {
"Invalid aggregate id index member:$member"
}
Comment on lines 41 to 44
companion object {
val SCRIPT_EVENT_STEAM_APPEND: RedisScript<String> =
RedisScripts.load("event_steam_append.lua", String::class.java)
}
Copilot AI review requested due to automatic review settings July 7, 2026 04:08

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 76 out of 76 changed files in this pull request and generated 2 comments.

Comment on lines +121 to +126
return Flux.range(0, AGGREGATE_ID_INDEX_BUCKETS)
.flatMap { bucket ->
scanAggregateIdBucket(namedAggregate, bucket, range, rangeLimit)
}
.sort(compareBy { it.id })
.take(limit.toLong())
@Ahoo-Wang

Copy link
Copy Markdown
Owner Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6cde00dc81

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copilot AI review requested due to automatic review settings July 7, 2026 04:51

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 77 out of 77 changed files in this pull request and generated no new comments.

@Ahoo-Wang
Ahoo-Wang merged commit ecd0a2e into main Jul 7, 2026
12 checks passed
@Ahoo-Wang
Ahoo-Wang deleted the codex/eventstore-aggregate-id-scanner branch July 7, 2026 05:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants