feat(eventsourcing): move aggregate id scanning to EventStore - #2730
Conversation
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 106 |
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 Report❌ Patch coverage is 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
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
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
EventStoreownAggregateIdScannerand remove aggregate ID scanning fromSnapshotStore. - Implement/forward
scanAggregateIdacross core/in-memory/routing/metrics/tracing and infrastructure event stores (Mongo/Redis/Elasticsearch), plus update WebFlux handlers and Spring Boot wiring to useEventStore. - Migrate TCK coverage from
SnapshotStoreSpectoEventStoreSpecand 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.
| override fun scanAggregateId( | ||
| namedAggregate: NamedAggregate, | ||
| afterId: String, | ||
| limit: Int | ||
| ): Flux<AggregateId> = |
| EventStreamKeyConverter.toAggregateId(namedAggregate, it) | ||
| }.filter { | ||
| it.id > afterId | ||
| }.sort(compareBy { it.id }) | ||
| .take(limit.toLong()) |
| 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 { |
| local result = redis.call("ZADD", eventStreamKey, version, value); | ||
| redis.call("HSET", aggregateTenantIndexKey, aggregateId, tenantId); | ||
| redis.call("ZADD", aggregateIdIndexKey, 0, aggregateId); |
| 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. |
| 聚合 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 中解析对应的租户。 |
| ``` | ||
| ### 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. |
| ``` | ||
| ### 聚合 ID 扫描 | ||
|
|
||
| `EventStore.scanAggregateId` 会扫描分桶的聚合 ID 索引,并按字典序合并结果。由于 `aggregateId` 全局唯一,scanner 只存储一个聚合 ID 成员,并从同分桶的租户 HASH 中解析对应的租户。 |
| override fun scanAggregateId( | ||
| namedAggregate: NamedAggregate, | ||
| afterId: String, | ||
| limit: Int | ||
| ): Flux<AggregateId> = |
| 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()) | ||
| } |
There was a problem hiding this comment.
💡 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".
| fun toAggregateIdIndexMember(namedAggregate: NamedAggregate, member: String): AggregateId { | ||
| val delimiterIndex = member.indexOf(INDEX_MEMBER_DELIMITER) | ||
| require(delimiterIndex > 0) { | ||
| "Invalid aggregate id index member:$member" | ||
| } |
| companion object { | ||
| val SCRIPT_EVENT_STEAM_APPEND: RedisScript<String> = | ||
| RedisScripts.load("event_steam_append.lua", String::class.java) | ||
| } |
| return Flux.range(0, AGGREGATE_ID_INDEX_BUCKETS) | ||
| .flatMap { bucket -> | ||
| scanAggregateIdBucket(namedAggregate, bucket, range, rangeLimit) | ||
| } | ||
| .sort(compareBy { it.id }) | ||
| .take(limit.toLong()) |
There was a problem hiding this comment.
💡 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".
Goal
Move aggregate ID scanning to the
EventStoreboundary so batch snapshot regeneration and state-event resend enumerate aggregates from the event stream source of truth, not from snapshot storage.Changes
EventStorenow ownsAggregateIdScanner;SnapshotStoreno longer exposes aggregate ID scanning.scanAggregateId.EventStorefor aggregate ID scanning.CROSSSLOTrisk.aggregateIdandtenantIdin a single ordered zset member, so append no longer maintains a separate tenant hash; the Lua script writes the scanner index only whenversion == 1.aggregateId.idorder, and relies on the project invariant thataggregateIdis globally unique.SnapshotStoreSpectoEventStoreSpec, with coverage for ordering,afterId,limit, named aggregate/context filtering, tenant preservation, one result per aggregate, and isolated scan cursors for integration stores.{aggregateId}<NUL>{tenantId}, keeps scanner default arguments/default unsupported behavior onAggregateIdScanner, covers the default-call path throughEventStore, and makes Redis aggregate-id scanning return empty for the terminalLAST_IDcursor without issuing an invalid lex range query.StateEventCompensator.resend, renames the Redis aggregate-id index member parser totoAggregateIdFromIndexMember, and refreshes the OpenAPI compatibility snapshot to8.8.0.wow-benchmarks/docker/benchmark.envfor 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.locallog driver and lower-frequency steady-state healthchecks to reduce Redis/Mongo benchmark runtime noise while preserving fast startup health probing.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-parallelfailed before the tracing fix withUnsupportedOperationException, then passed after forwardingscanAggregateId../gradlew :wow-mongo:test --tests me.ahoo.wow.mongo.MongoEventStoreScanTest --rerun-tasks --no-parallelfailed before the Mongo named aggregate filter fix, then passed after addingcontextName/aggregateNamefilters../gradlew :wow-core:contractTest --tests me.ahoo.wow.eventsourcing.InMemoryEventStoreTest :wow-core:test --tests me.ahoo.wow.eventsourcing.RoutingEventStoreTest --rerun-tasks --no-parallelpassed../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-parallelpassed../gradlew :wow-redis:test --tests 'me.ahoo.wow.redis.eventsourcing.EventStreamKeyConverterTest' --tests 'me.ahoo.wow.redis.eventsourcing.RedisEventStoreScanTest' --no-parallelpassed before and after the Redis index-member optimization../gradlew :wow-redis:check --no-parallelpassed on5098cc103../gradlew :wow-benchmarks:benchmarkQuickInfrastructureE2E :wow-benchmarks:generateInfrastructureBenchmarkReport --no-parallelpassed on5098cc103; 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-parallelpassed on8a8d697fe;quick-infrastructure-e2e.mdnow records host JVM, Docker Server 29.6.1, Docker DesktopnetworkType=gvisor,redis:7.4.9-alpine, andmongo:8.3.4metadata.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.mdpassed on8a8d697fe.docker compose --env-file wow-benchmarks/docker/benchmark.env -f wow-benchmarks/docker/compose.redis.yml configpassed on2735ed3be.docker compose --env-file wow-benchmarks/docker/benchmark.env -f wow-benchmarks/docker/compose.mongo.yml configpassed on2735ed3be.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 on2735ed3be.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 on2735ed3be../gradlew :wow-benchmarks:test --tests me.ahoo.wow.benchmark.infrastructure.BenchmarkInfrastructureConfigTest --no-parallelfailed before the Mongo credential URI-encoding fix, then passed on2735ed3be../gradlew :wow-benchmarks:check --no-parallelpassed on2735ed3be,8a8d697fe,a6f35d766, andf776c84d1.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 ona6f35d766.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 ona6f35d766.docker compose --env-file wow-benchmarks/docker/benchmark.env -f wow-benchmarks/docker/compose.redis.yml up -d --force-recreate --waitpassed ona6f35d766;wow-benchmark-rediswas healthy withlog=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 --waitpassed ona6f35d766;wow-benchmark-mongowas healthy withlog=local,health=1m0s,start=2s../gradlew :wow-benchmarks:benchmarkQuickInfrastructureE2E :wow-benchmarks:generateInfrastructureBenchmarkReport --no-parallelpassed ona6f35d766; 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-parallelpassed onf776c84d1;quick-infrastructure-e2e.mdnow recordsVersion: 8.8.0.pnpm docs:buildpassed onf776c84d1andf9629e1cd../gradlew :wow-core:test --tests me.ahoo.wow.eventsourcing.EventStoreTest --tests me.ahoo.wow.eventsourcing.AggregateIdScannerTest --no-parallelpassed onf9629e1cdand3c01ac2f0.rg -n "tenant HASH|租户 HASH|:tenants|bucket-aligned tenant|同分桶的租户" documentation/docs/en/guide/extensions/redis.md documentation/docs/zh/guide/extensions/redis.mdreturned no matches onf9629e1cd.git diff --checkpassed on2735ed3be,a6f35d766,f776c84d1,f9629e1cd, and3c01ac2f0../gradlew :wow-core:test --tests me.ahoo.wow.eventsourcing.EventStoreTest --tests me.ahoo.wow.eventsourcing.AggregateIdScannerTest --no-parallelpassed onfb88a7aac../gradlew :wow-redis:test --tests me.ahoo.wow.redis.eventsourcing.RedisEventStoreScanTest --no-parallelpassed onfb88a7aac.git diff --checkpassed onfb88a7aac../gradlew :wow-core:test --tests me.ahoo.wow.eventsourcing.EventStoreTest --tests me.ahoo.wow.eventsourcing.AggregateIdScannerTest --no-parallelpassed on241da496f../gradlew :wow-redis:test --tests me.ahoo.wow.redis.eventsourcing.RedisEventStoreScanTest --no-parallelpassed on241da496f.git diff --checkpassed on241da496f../gradlew :wow-core:test --tests 'me.ahoo.wow.event.compensation.StateEventCompensatorTest.resend skips ignored initial error streams' --stacktrace --no-parallelfailed before the fix withexpected value: 0; actual value: 1, then passed on4909c5202../gradlew :wow-openapi:test --tests 'me.ahoo.wow.openapi.snapshot.OpenApiCompatibilitySnapshotTest' -Dwow.snapshot.update=true --stacktrace --no-parallelpassed on4909c5202to 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-parallelpassed on4909c5202.git diff --checkpassed on4909c5202../gradlew detekt --stacktrace --no-parallelpassed on4909c5202after 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-parallelfailed before the fix withSourcingVersionConflictException, then passed on6cde00dc8../gradlew :wow-redis:test --tests 'me.ahoo.wow.redis.eventsourcing.RedisEventStoreScanTest.scan aggregate id should bound bucket query concurrency' --stacktrace --no-parallelfailed before the fix with 128 subscribed bucket queries instead of 16, then passed on6cde00dc8../gradlew :wow-core:test --tests 'me.ahoo.wow.event.compensation.StateEventCompensatorTest' :wow-redis:test --tests 'me.ahoo.wow.redis.eventsourcing.RedisEventStoreScanTest' --stacktrace --no-parallelpassed on6cde00dc8.git diff --checkpassed on6cde00dc8../gradlew detekt --stacktrace --no-parallelpassed on6cde00dc8../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-parallelfailed before the fix withSourcingVersionConflictException, then passed on11ffd8662../gradlew :wow-webflux:test --tests 'me.ahoo.wow.webflux.route.snapshot.BatchRegenerateSnapshotHandlerFunctionTest' --stacktrace --no-parallelpassed on11ffd8662../gradlew :wow-webflux:check --stacktrace --no-parallelpassed on11ffd8662.git diff --checkpassed on11ffd8662.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, andcodecov/projectpassed.Risks and Notes
SnapshotStore.scanAggregateIdis removed from the public snapshot-store API. Custom snapshot stores must drop that method; custom event stores should implementscanAggregateId.128 * limitcandidates in memory. This avoids a single hot Redis slot and keeps Lua append atomic, but it increases scan fan-out.aggregateId.networkType=gvisorto Redis/Mongo containers.-PbenchmarkDockerEnvFile=/path/to/env-file; otherwise Docker may run one topology while reports/JMH use the defaultbenchmark.env.quick-infrastructure-e2e.mdwas regenerated in this update because it has current JMH JSON inwow-benchmarks/results/jmh/quick/infrastructure-e2e; the older framework/webflux/grouped quick reports remain historical snapshots until their benchmark suites are rerun.EventStorekeeps a defaultscanAggregateIdthat returnsUnsupportedOperationExceptionto reduce source breakage, but batch endpoints need a real implementation at runtime.StateEventCompensator.resendnow returns zero and emits no state event for ignored or still-uninitialized state.RegenerateSnapshotHandlernow writes no snapshot until state is initialized.