Keep the event log of each Bounded Context in its own table - #182
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`CoreJvm` moves to `2.0.0-SNAPSHOT.540`, which groups the event store storage by Bounded Context (core-jvm PR #1673). 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 buildscript block now mirrors the one of `core-jvm` on the same `config` version. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
With `core-jvm` grouping the event store storage by the context (core-jvm PR #1673), grouped tables such as `Billing_Event` appear through the existing naming path. This change hardens that path: - `TableNames` now replaces every character prohibited in table names — Bounded Context names are free-form — keeping the output for the existing letters-digits-dots names intact. - `TableSpecs` detects distinct storages whose table names alias after the replacement, and fails fast naming both claimants. The names are compared truncated to the 63-byte PostgreSQL limit. - The new `setTableName(BoundedContextName, Class, String)` overloads allow assigning a custom name to a context-grouped table. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- `TableNamesSpec` — sanitization keeps dotted names intact. - `TableNameCollisionSpec` — aliasing storages are rejected; repeated resolution of one storage is tolerated. - `EventLogIsolationSpec` — two contexts over one factory read back only their own events. - `ContextTableNameSpec` — the context-addressed custom name is honored; a single-type custom name does not leak to grouped tables. - `JdbcDefaultEventStoreTest` — the behavioral contract of `DefaultEventStoreTest` from `core-jvm` runs against H2, with a fresh database per test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`docs/tables.md` now describes the sanitized naming scheme, the event log of a Bounded Context as a grouped storage, the name-clash detection, and the context-addressed `setTableName(..)` overload. The new `docs/event-log-migration.md` guides moving the stored events from the shared `spine_core_Event` table to the per-context tables. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Applies the English style catalog across the repository: restrictive `which` becomes `that`, two comma splices become semicolons, an introductory clause gains its comma, missing articles are added, and a dangling relative pronoun in a suppression comment is resolved. Also addresses the review feedback on the branch: the over-long Javadoc example is wrapped, three paragraph widows are reflowed, and the display name of `EventLogIsolationSpec` names its subject. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 15465b3ee4
ℹ️ 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".
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #182 +/- ##
==========================================
+ Coverage 89.23% 89.53% +0.29%
==========================================
Files 69 69
Lines 1561 1586 +25
Branches 77 80 +3
==========================================
+ Hits 1393 1420 +27
+ Misses 131 129 -2
Partials 37 37 🚀 New features to boost your workflow:
|
MySQL may run with a case-insensitive `lower_case_table_names` setting, which maps `Billing_Event` and `billing_Event` onto one table. Comparing the claimed names case-sensitively would let two such contexts both succeed and intermingle their event logs. The clash detection now lowercases the names alongside the existing length truncation. The migration statements now quote the table names: the library creates its tables with quoted identifiers, so unquoted references are folded to another case by PostgreSQL and H2, failing with a table-not-found error. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR updates jdbc-storage to support per–Bounded Context event-log tables by hardening table-name composition for free-form context names, detecting post-sanitization naming collisions at table-spec creation time, and exposing a context-addressed custom table-name API; it also adds tests and documentation/migration guidance for the new per-context event-log layout.
Changes:
- Sanitize generated table names by replacing all non-
[A-Za-z0-9_]characters with_, and fail fast when distinct storages would alias to the same effective identifier (case-insensitive, truncated to 63 bytes). - Add
setTableName(BoundedContextName, Class, String)overloads to allow custom naming of context-grouped tables (notably event stores). - Add/extend test suites and docs, including event-log migration instructions and new coverage for event-log isolation and name-collision scenarios.
Reviewed changes
Copilot reviewed 61 out of 64 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| version.gradle.kts | Bumps publishable version. |
| rdbms/src/test/kotlin/io/spine/server/storage/jdbc/record/TableNamesSpec.kt | Adds Kotlin coverage for grouped naming + sanitization behavior. |
| rdbms/src/test/kotlin/io/spine/server/storage/jdbc/record/EventLogIsolationSpec.kt | Verifies per-context event store isolation when one factory serves multiple contexts. |
| rdbms/src/test/kotlin/io/spine/server/storage/jdbc/operation/CreateTableSpec.kt | Minor comment punctuation tweak in MySQL collation assertion. |
| rdbms/src/test/kotlin/io/spine/server/storage/jdbc/JdbcDefaultEventStoreTest.kt | Runs core DefaultEventStoreTest contract against JDBC/H2 with per-test fresh DB. |
| rdbms/src/test/kotlin/io/spine/server/storage/jdbc/ContextTableNameSpec.kt | Verifies context-addressed custom table name is honored; single-type naming doesn’t affect grouped event tables. |
| rdbms/src/test/kotlin/io/spine/server/storage/jdbc/config/TableNameCollisionSpec.kt | Tests fast-fail on name aliasing (sanitization, case, 63-byte truncation, custom-vs-derived). |
| rdbms/src/test/java/io/spine/server/storage/jdbc/query/given/DbIteratorTestEnv.java | Grammar tweak in test helper Javadoc. |
| rdbms/src/test/java/io/spine/server/storage/jdbc/query/AbstractQueryTest.java | Grammar tweak in test Javadoc. |
| rdbms/src/test/java/io/spine/server/storage/jdbc/JdbcStorageFactoryTest.java | Improves @DisplayName wording in tests. |
| rdbms/src/main/java/io/spine/server/storage/jdbc/record/TableNames.java | Expands sanitization from .-only to “all prohibited chars” for table names (including free-form groups). |
| rdbms/src/main/java/io/spine/server/storage/jdbc/record/RecordTable.java | Grammar tweak in API Javadoc. |
| rdbms/src/main/java/io/spine/server/storage/jdbc/record/JdbcTableSpec.java | Grammar tweak + clearer error message wording. |
| rdbms/src/main/java/io/spine/server/storage/jdbc/record/JdbcRecordStorage.java | Grammar tweaks in class/Javadoc text. |
| rdbms/src/main/java/io/spine/server/storage/jdbc/record/column/IdColumn.java | Grammar tweaks in class/Javadoc/comments. |
| rdbms/src/main/java/io/spine/server/storage/jdbc/query/SelectMessageByIdQuery.java | Grammar tweak in class Javadoc. |
| rdbms/src/main/java/io/spine/server/storage/jdbc/query/reader/StringColumnReader.java | Grammar tweak in class Javadoc. |
| rdbms/src/main/java/io/spine/server/storage/jdbc/query/reader/MessageColumnReader.java | Grammar tweak in class Javadoc. |
| rdbms/src/main/java/io/spine/server/storage/jdbc/query/reader/MessageBytesColumnReader.java | Grammar tweak in class Javadoc. |
| rdbms/src/main/java/io/spine/server/storage/jdbc/query/reader/LongColumnReader.java | Grammar tweak in class Javadoc. |
| rdbms/src/main/java/io/spine/server/storage/jdbc/query/reader/IntegerColumnReader.java | Grammar tweak in class Javadoc. |
| rdbms/src/main/java/io/spine/server/storage/jdbc/query/reader/ColumnReaderFactory.java | Grammar tweak in class Javadoc. |
| rdbms/src/main/java/io/spine/server/storage/jdbc/query/QueryPredicates.java | Grammar tweak in exception message. |
| rdbms/src/main/java/io/spine/server/storage/jdbc/query/DbIterator.java | Grammar tweaks in Javadoc. |
| rdbms/src/main/java/io/spine/server/storage/jdbc/query/AbstractQuery.java | Grammar tweak in Javadoc. |
| rdbms/src/main/java/io/spine/server/storage/jdbc/operation/mysql/package-info.java | Grammar tweak in package-level Javadoc. |
| rdbms/src/main/java/io/spine/server/storage/jdbc/operation/CreateTable.java | Grammar tweaks in Javadoc. |
| rdbms/src/main/java/io/spine/server/storage/jdbc/JdbcStorageFactory.java | Adds context-addressed setTableName(BoundedContextName, ...) builder overload + docs. |
| rdbms/src/main/java/io/spine/server/storage/jdbc/DataSourceConfig.java | Grammar tweak in Javadoc. |
| rdbms/src/main/java/io/spine/server/storage/jdbc/config/TableSpecs.java | Adds collision detection for effective table names and context-addressed custom name support. |
| gradle/wrapper/gradle-wrapper.properties | Updates Gradle wrapper distribution URL. |
| docs/type-mapping.md | Docs wording tweaks and snippet formatting. |
| docs/tables.md | Documents sanitization rules, per-context event log tables, collision behavior, and customization examples. |
| docs/README.md | Adds link to event-log migration doc. |
| docs/queries.md | Docs wording tweak. |
| docs/event-log-migration.md | New migration guide for moving from shared event table to per-context tables. |
| docs/dependencies/pom.xml | Updates published/docs dependency versions (including Testcontainers coordinates). |
| docs/dependencies/dependencies.md | Regenerates dependency/license report for new version set. |
| buildSrc/src/test/kotlin/io/spine/gradle/report/pom/PomGeneratorIgTest.kt | Adds Gradle TestKit integration test for generatePom behavior. |
| buildSrc/src/test/kotlin/io/spine/gradle/report/pom/DependencyWriterSpec.kt | Updates tests to inject resolved-version maps and cover unresolvable configuration behavior. |
| buildSrc/src/main/kotlin/io/spine/gradle/report/pom/ResolvedVersions.kt | Adds per-project task + helper to persist resolved dependency versions safely across multi-project builds. |
| buildSrc/src/main/kotlin/io/spine/gradle/report/pom/PomXmlWriter.kt | Threads resolved-versions provider into dependency writing. |
| buildSrc/src/main/kotlin/io/spine/gradle/report/pom/PomGenerator.kt | Registers per-project “collect resolved versions” tasks and makes generatePom depend on them. |
| buildSrc/src/main/kotlin/io/spine/gradle/report/pom/DependencyWriter.kt | Refactors dependency collection to take resolved-versions provider; exports moduleKey. |
| buildSrc/src/main/kotlin/io/spine/dependency/test/Testcontainers.kt | Updates Testcontainers version and artifact naming for 2.x line. |
| buildSrc/src/main/kotlin/io/spine/dependency/local/Validation.kt | Updates Validation versions and removes unused constants. |
| buildSrc/src/main/kotlin/io/spine/dependency/local/ToolBase.kt | Updates ToolBase version and splits deprecated monolith into focused module coordinates. |
| buildSrc/src/main/kotlin/io/spine/dependency/local/ProtoTap.kt | Bumps ProtoTap version. |
| buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvmCompiler.kt | Updates CoreJvmCompiler artifacts and splits Gradle plugin vs compiler plugins coordinates. |
| buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvm.kt | Bumps CoreJvm version to pick up per-context event store grouping behavior. |
| buildSrc/src/main/kotlin/io/spine/dependency/local/Compiler.kt | Updates compiler artifacts/version + renames fat CLI artifact coordinate. |
| buildSrc/src/main/kotlin/io/spine/dependency/local/Base.kt | Bumps Base version. |
| buildSrc/src/main/kotlin/io/spine/dependency/lib/PalantirJavaFormat.kt | Updates Palantir Java Format version. |
| buildSrc/src/main/kotlin/io/spine/dependency/lib/Log4j2.kt | Bumps Log4j2 version and adds SLF4J 2 bridge coordinate. |
| buildSrc/src/main/kotlin/io/spine/dependency/lib/JacksonV2.kt | Introduces Jackson 2.x alignment BOM/modules for transitive consumers. |
| buildSrc/src/main/kotlin/io/spine/dependency/lib/Jackson.kt | Switches “main Jackson” coordinates to Jackson 3.x (tools.jackson) and documents 2.x/3.x split. |
| buildSrc/src/main/kotlin/io/spine/dependency/boms/Boms.kt | Adjusts optional Jackson BOM to use Jackson 2.x BOM. |
| buildSrc/build.gradle.kts | Adds Gradle TestKit for buildSrc tests and injects buildSrc runtime classpath into TestKit builds. |
| build.gradle.kts | Updates buildscript dependency forcing and CoreJvm compiler plugin artifact wiring. |
| .gitignore | Adds rationale comment for keeping kotlinc.xml ignored. |
| .github/workflows/gradle-wrapper-validation.yml | Removes Gradle wrapper validation workflow. |
| .agents/tasks/event-store-context-prefix.md | Adds task-plan documentation for the change set. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Codecov flagged the `Builder.setDataSource(DataSource)` overload — the entry point shown in `docs/configuration.md` — as the one uncovered spot near this change. The new spec configures the factory the way the documentation does and checks `isOpen()` across the lifecycle. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both external bot reviews on this PR found real defects in engine interaction — unquoted migration SQL, case-insensitive table clash — rather than in the logic itself, as did the earlier MySQL collation work. The checklist names the semantics to verify per engine: quoting and folding, name case, identifier length, and collation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`effectiveName()` truncated raw UTF-8 bytes, so a custom name crossing the 63-byte limit mid-character decoded with a replacement-character tail. PostgreSQL clips identifiers to the last whole character, so such a name and its clean prefix denote one table there while the detection saw two distinct keys. The clip now backs off continuation bytes, mirroring PostgreSQL. Also makes the producer identifiers in `EventLogIsolationSpec` unique per event, hardening the isolation assertions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The earlier review round accepted the premise that the library creates tables with quoted identifiers and made the migration SQL quote them. Querydsl's `quoteIdentifier(..)` is conditional: with `useQuotes` off on the `SQLTemplatesRegistry` path, an ordinary name — Latin letters, digits, `_`, not a reserved word — is emitted unquoted, and the engine folds it: `BILLING_EVENT` on H2, `billing_event` on PostgreSQL (verified against H2's `INFORMATION_SCHEMA`; pinned by the new `CreatedTableNameSpec`). Only a requires-quotes name is stored verbatim. The migration statements are unquoted again, the docs and KDoc describe the folding, and the case-insensitive clash comparison gains its precise justification: on PostgreSQL and H2 it is required, not conservative. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`claim(..)` ran inside the `computeIfAbsent` mapping function, which fires once per key, so its idempotency guard — the branch tolerating a storage that re-claims the name it already holds — could never execute. Claiming now happens on every `specFor(..)` call, which makes the guard live and exercised by the existing test, and detects a clash even when the specification is served from the cache. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The `0xC0` and `0x80` literals are the UTF-8 continuation-byte mask and pattern rather than arbitrary constants. The method docs now spell out the bit patterns they stand for, and the suppression carries its reason. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This PR is the
jdbc-storagepart of keeping the event log of each Bounded Contextin its own physical storage; the core part is core-jvm PR
#1673.
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 tables by
(record spec, group), soper-context tables —
Billing_Event,Shipping_Event— appear through the existinggrouped-naming path with no storage-code changes. (
CoreJvmCompilerandValidationare bumped alongside; the refreshed
configrides along, and the rootbuild.gradle.ktsis adapted to its updated API —JacksonV2forcing groups,Caffeine.lib,coreJvmCompiler.gradlePlugin.)Hardening of the naming path:
TableNamesnow replaces every character prohibited in a table name — BoundedContext names are free-form, unlike Proto type names. Names built of letters,
digits, and dots (all existing deployments) keep byte-identical table names:
nothing is renamed.
TableSpecsdetects distinct storages whose table names alias after thereplacement (e.g., contexts
Sales.EUandSales_EU→ oneSales_EU_Event)and fails fast with an
IllegalStateExceptionnaming both claimants. Names arecompared truncated to 63 bytes — PostgreSQL's silent identifier limit, the
strictest among the supported engines. Detection was chosen over a digest/escape
encoding deliberately: the factory sees every group it serves, and a loud config
error beats hex in table names.
setTableName(BoundedContextName, Class, String)overloads onJdbcStorageFactory.BuilderandTableSpecs.Builderallow assigning a customname to a context-grouped table. The single-type
setTableName(Event.class, ..)no longer reaches the event table — it is a grouped table now (see the migration
notes).
Tests (all green locally; 342 tests, 0 failures, 18 Docker-gated skips):
TableNamesSpec— sanitization keeps dotted names intact;TableNameCollisionSpec— aliasing storages rejected, repeated resolution of onestorage tolerated, custom-vs-derived clash, past-63rd-byte difference;
EventLogIsolationSpec— two contexts over one factory read back only their ownevents;
ContextTableNameSpec— the context-addressed custom name is honored; asingle-type name does not leak to grouped tables;
JdbcDefaultEventStoreTest— core'sDefaultEventStoreTestbehavioral contractover H2, with a fresh database per test.
Documentation:
docs/tables.md— the sanitized naming rules, "The event log of a BoundedContext", "Name clashes", the context-addressed customization example; warnings
converted to GitHub
> [!WARNING]alerts.docs/event-log-migration.md(new) — migrating the stored events from the sharedspine_core_Eventtable to the per-context tables by thetypecolumn, with thespecial cases (shared event types, custom names, clashing context names).
Proofreading. A final commit applies the repository's English style catalog
across the module: restrictive
which→that(24 sites), two comma splices,an introductory-clause comma, and a few missing articles. It is prose-only —
no behavior changes — and is kept as its own commit for a clean review.
Notes for reviewers
.agents/tasks/event-store-context-prefix.md.events stay in
spine_core_Eventuntil migrated — see the new docs page.🤖 Generated with Claude Code