Keep the event log of each Bounded Context in its own Datastore kind - #206
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The version brings the event store storage grouped by the Bounded Context (core-jvm PR #1673), which lands each context's event log under its own kind, e.g. `Billing-Event`, through the existing grouped-layout path. The test fixtures of `spine-server` join the test classpath via the `server-test-fixtures` capability, providing the `DefaultEventStoreTest` contract suite. The main `spine-server` classes arrive transitively: the published fixtures variant depends on its own main component.
The layouts of grouped storages were registered by the entity state type only, so the layout of a context-grouped storage — the event store of a Bounded Context — could not be customized. The new `organizeRecords(BoundedContextName, Class, RecordLayout)` overload and its `RecordLayouts.Builder` counterpart address such a storage by the context name, deriving the group via `StorageGroup.of(BoundedContextName)` — the same single source of truth `core-jvm` uses.
- `KindSpec` — grouped-kind composition, the context-derived group, and the verbatim, case-sensitive group names. - `RecordLayoutsSpec` — a context-registered layout is honored; an unregistered context defaults to the flat grouped-kind layout. - `EventLogIsolationSpec` — two contexts over one factory keep isolated event logs, including the contexts differing only in case: Datastore kinds are stored verbatim, the opposite of the RDBMS behavior pinned in `jdbc-storage`. - `DatastoreDefaultEventStoreTest` — the `DefaultEventStoreTest` contract of `core-jvm` against the local Datastore emulator, with the emulator data wiped per test. `DatastoreStorageFactoryBuilderTest` gains the `NullPointerTester` default for the `BoundedContextName` parameter of the new overload. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The new `docs/event-log-migration.md` guides moving the stored events from the shared `spine.core.Event` kind to the per-context kinds by the `type` property, iterating namespaces for multi-tenant deployments, and covers the custom-kind and shared-event-type cases. Linked from `README.md`. The task plan records the engine-semantics reasoning. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The refreshed `config` splits Jackson forcing into `JacksonV2` groups, forces Caffeine explicitly, renames `CoreJvmCompiler.pluginLib` to `gradlePlugin`, and retires the monolithic `tool-base` artifact. The project-scope forcing also gains `KotlinPoet`, aligning the KSP processor classpath the way the sibling repositories do. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The local dependencies move to the latest published versions: `CoreJvm` to `2.0.0-SNAPSHOT.540` — restoring the branch requirement after the `config` pull — `CoreJvmCompiler` to `.091`, `Logging` to `.424`, and `Validation` to `.463`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`DsEventStoreTest` already runs the `DefaultEventStoreTest` contract of `core-jvm` against the emulator, wiping the data between the tests via `@AfterEach`. The Kotlin suite added earlier on this branch repeated the same contract for no added coverage, and its premise — that only the constructor runs ahead of the base setup — was wrong: cleaning after each test is equivalent to cleaning before it. Also narrows the display name of `KindSpec`, which collided with the one of `KindTest`, and drops the erased type arguments from a `shouldBeInstanceOf` check. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The new `organizeRecords(BoundedContextName, ..)` overload of the factory builder was covered only indirectly: through `RecordLayouts` directly, and through the default grouped kind. `EventLogIsolationSpec` now builds a customized factory the way the Javadoc and the migration guide show, and asserts that the event store of the registered context takes the custom kind while another context keeps the derived one. Also documents the context-addressed flavor in the `RecordLayouts` class docs, aligns the `@MonotonicNonNull` builder fields with the initializer style used elsewhere in the repository, and notes why the `JacksonV2` groups are referenced by their qualified names. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR updates gcloud-jvm to support per–Bounded Context event-log isolation in Google Cloud Datastore (i.e., each context’s Event records land in a distinct kind like Billing-Event), aligning with the corresponding core-jvm behavior change. It also adds context-addressed record-layout registration and updates build tooling and dependency metadata accordingly.
Changes:
- Add context-addressed overloads for Datastore record layout registration and tests validating per-context kinds and isolation.
- Add migration documentation for moving historical events from the shared
spine.core.Eventkind to per-context kinds, and link it from the README. - Update dependency/tooling infrastructure (BuildSrc POM generator improvements, dependency versions, generated dependency reports, Gradle wrapper).
Reviewed changes
Copilot reviewed 36 out of 39 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| version.gradle.kts | Bump published snapshot version. |
| README.md | Add a pointer to the event-log migration guide. |
| gradle/wrapper/gradle-wrapper.properties | Update Gradle wrapper distribution URL. |
| docs/event-log-migration.md | New migration guide describing how to move historical events to per-context kinds. |
| docs/dependencies/pom.xml | Update documented dependency versions and add new plugin dependency entry. |
| docs/dependencies/dependencies.md | Regenerated dependency/license report for updated versions. |
| datastore/src/test/kotlin/io/spine/server/storage/datastore/record/EventLogIsolationSpec.kt | New emulator-backed test asserting per-context event log isolation (including case-different context names). |
| datastore/src/test/kotlin/io/spine/server/storage/datastore/KindSpec.kt | New tests for grouped-kind composition behavior. |
| datastore/src/test/kotlin/io/spine/server/storage/datastore/config/RecordLayoutsSpec.kt | New tests for context-grouped layout registration and defaults. |
| datastore/src/test/java/io/spine/server/storage/datastore/DatastoreStorageFactoryBuilderTest.java | Extend nullability testing defaults for new BoundedContextName parameter. |
| datastore/src/main/java/io/spine/server/storage/datastore/DatastoreStorageFactory.java | Add organizeRecords(BoundedContextName, Class, RecordLayout) overload and refine nullness annotations/docs. |
| datastore/src/main/java/io/spine/server/storage/datastore/config/RecordLayouts.java | Add RecordLayouts.Builder.add(BoundedContextName, Class, RecordLayout) for context-grouped storages. |
| datastore/build.gradle.kts | Adjust test dependency to explicitly require server-test-fixtures capability. |
| buildSrc/src/test/kotlin/io/spine/gradle/report/pom/PomGeneratorIgTest.kt | New Gradle TestKit integration test validating stable/accurate generated POM output. |
| buildSrc/src/test/kotlin/io/spine/gradle/report/pom/DependencyWriterSpec.kt | Update tests to feed resolved versions via the new collection mechanism; add coverage for unresolvable configs. |
| buildSrc/src/main/kotlin/io/spine/gradle/report/pom/ResolvedVersions.kt | New per-project task + helper logic to collect resolved dependency versions safely across projects. |
| buildSrc/src/main/kotlin/io/spine/gradle/report/pom/PomXmlWriter.kt | Wire resolved-versions provider into POM writing. |
| buildSrc/src/main/kotlin/io/spine/gradle/report/pom/PomGenerator.kt | Register per-project version collectors and make generatePom depend on them. |
| buildSrc/src/main/kotlin/io/spine/gradle/report/pom/DependencyWriter.kt | Refactor dependency collection to consume externally-provided resolved versions; expose moduleKey for reuse. |
| buildSrc/src/main/kotlin/io/spine/dependency/local/Validation.kt | Bump Validation version and remove unused artifact constants. |
| buildSrc/src/main/kotlin/io/spine/dependency/local/ToolBase.kt | Update ToolBase coordinates and model the split of the former monolithic artifact into focused modules. |
| buildSrc/src/main/kotlin/io/spine/dependency/local/ProtoTap.kt | Bump ProtoTap version. |
| buildSrc/src/main/kotlin/io/spine/dependency/local/Logging.kt | Bump Logging version. |
| buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvmCompiler.kt | Update CoreJvm Compiler coordinates (separate Gradle plugin vs compiler plugins) and bump version. |
| buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvm.kt | Bump CoreJvm (server) version to the behavioral pivot. |
| buildSrc/src/main/kotlin/io/spine/dependency/local/Compiler.kt | Update compiler artifact naming (fat CLI distribution) and bump version. |
| buildSrc/src/main/kotlin/io/spine/dependency/local/Base.kt | Bump Base Libraries version. |
| buildSrc/src/main/kotlin/io/spine/dependency/lib/PalantirJavaFormat.kt | Bump Palantir Java Format version. |
| buildSrc/src/main/kotlin/io/spine/dependency/lib/Log4j2.kt | Bump Log4j2 version and add documented SLF4J 2.x bridge coordinate. |
| buildSrc/src/main/kotlin/io/spine/dependency/lib/JacksonV2.kt | New Jackson 2.x dependency model to align transitive 2.x consumers. |
| buildSrc/src/main/kotlin/io/spine/dependency/lib/Jackson.kt | Switch “primary” Jackson model to 3.x (tools.jackson), keep annotations on 2.x, and deprecate no-longer-published modules. |
| buildSrc/src/main/kotlin/io/spine/dependency/boms/Boms.kt | Point optional Jackson BOM to Jackson 2.x BOM for transitive alignment. |
| buildSrc/build.gradle.kts | Improve buildSrc test setup (TestKit dependency + pass buildSrc runtime classpath to functional tests); clarify Jackson 2.x rationale. |
| build.gradle.kts | Update forced dependency alignment (JacksonV2 groups, KotlinPoet additions, CoreJvmCompiler plugin artifact rename, drop ToolBase monolith force). |
| .gitignore | Clarify ignoring of .idea/kotlinc.xml to avoid IDE churn. |
| .github/workflows/gradle-wrapper-validation.yml | Remove wrapper-validation workflow file. |
| .agents/tasks/event-store-context-prefix.md | Add a task-plan document describing rationale/work items for this change set. |
Suppressed comments (1)
docs/event-log-migration.md:77
- This URL points to the "Datastore to Datastore Delete" template, which is for bulk deletion rather than migration/copy. It would be safer to link to Dataflow template/pipeline authoring docs (or other copy/migration docs) instead.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ccc0212624
ℹ️ 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".
The guide pointed at the `datastore-to-datastore-delete` Dataflow template as an option for copying the event entities. The template deletes the matching entities instead of copying them, so an operator following the recommendation could erase the historical event log without creating the per-context entities. The guide now states that no ready-made tool performs this copy — the provided templates delete or move verbatim, and a managed import restores the original kind — and points to the Datastore client library and the Apache Beam connector for writing the copy. A warning asks for a managed export before the migration starts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #206 +/- ##
============================================
+ Coverage 90.97% 91.05% +0.08%
Complexity 475 475
============================================
Files 62 62
Lines 1662 1677 +15
Branches 92 92
============================================
+ Hits 1512 1527 +15
Misses 121 121
Partials 29 29 🚀 New features to boost your workflow:
|
This PR is the
gcloud-jvmpart of keeping the event log of each Bounded Contextin its own physical storage. The core part is core-jvm PR
#1673; the RDBMS part is
jdbc-storage PR #182.
What changed
Dependency bump — the behavioral pivot.
CoreJvmmoves to2.0.0-SNAPSHOT.540,where
DefaultEventStorecreates its record storage under aStorageGroupnamedafter the context. This library already keys grouped storages by
(group, record type), so per-context kinds —Billing-Event,Shipping-Event,Billing_System-Event— appear through the existing grouped-layout path with nostorage-code changes.
Context-addressed layout registration. Grouped layouts could be registered only
by entity state type, so the layout of a context-grouped storage could not be
customized. Two new overloads close that gap, deriving the group through
StorageGroup.of(BoundedContextName)— the same single source of truth core-jvmuses:
DatastoreStorageFactory.Builder.organizeRecords(BoundedContextName, Class, RecordLayout)RecordLayouts.Builder.add(BoundedContextName, Class, RecordLayout)Why this repo needs less than
jdbc-storage. Datastore kinds are arbitraryUTF-8, stored verbatim and compared case-sensitively, and
Kind.of(recordType, group)composes
<group>-<SimpleName>with no character replacement or truncation. So thesanitization and clash detection that
jdbc-storageneeded have no counterpart here— the composition is already injective for distinct group names, and the dash cannot
occur in a Proto type name, keeping grouped kinds apart from type-derived ones. This
is verified rather than assumed:
EventLogIsolationSpecpins that contexts differingonly in case keep separate event logs at the storage level — the deliberate mirror of
jdbc-storage's test asserting the opposite for RDBMS tables.
Tests (355 tests, 0 failures, 18 Docker-gated skips):
KindSpec— grouped-kind composition, the context-derived group, verbatimcase-sensitive group names;
RecordLayoutsSpec— a context-registered layout is honored; an unregisteredcontext defaults to the flat grouped-kind layout;
EventLogIsolationSpec— two contexts over one factory keep isolated event logs,including the case-different pair;
DatastoreDefaultEventStoreTest— core'sDefaultEventStoreTestcontract againstthe local Datastore emulator, with data wiped per test;
DatastoreStorageFactoryBuilderTestgains theNullPointerTesterdefault for thenew
BoundedContextNameparameter.Documentation. The new
docs/event-log-migration.mdguides moving the storedevents from the shared
spine.core.Eventkind to the per-context kinds by thetypeproperty, iterating namespaces for multi-tenant deployments, and covers the
custom-kind, shared-event-type, and composite-index cases. Linked from
README.md.Housekeeping (separate commits):
./config/pull; the root build script adapted tothe updated
configAPI (JacksonV2forcing groups,Caffeine,coreJvmCompiler.gradlePlugin, the retired monolithictool-baseentry dropped) plusKotlinPoetforcing that the KSP processor classpath now needs; the local dependencyrefresh (
CoreJvmCompiler.091,Logging.424,Validation.463); and theregenerated dependency reports.
Notes for reviewers
.agents/tasks/event-store-context-prefix.md.events stay under
spine.core.Eventuntil migrated — see the new docs page.organizeRecords(Event.class, ...)anduseRecordStorage(...)registrations never serve grouped storages (pre-existing, documented behavior),
so they no longer reach the event store. The migration page says so.
🤖 Generated with Claude Code