|
| 1 | +# OMDB Spring Boot 3 Starter + Autoconfiguration — Design |
| 2 | + |
| 3 | +_Date: 2026-05-29_ |
| 4 | + |
| 5 | +## Problem |
| 6 | + |
| 7 | +The Java port's Spring integration (`core-spring`) registers its autoconfiguration |
| 8 | +**only** through the legacy Boot 2.x `META-INF/spring.factories` |
| 9 | +`EnableAutoConfiguration` key. **Spring Boot 3 no longer honors that key** — |
| 10 | +autoconfiguration classes must be listed in |
| 11 | +`META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports`. |
| 12 | +The project builds on **Java 21** and **Spring Boot 3.5.6**, so today a consumer who |
| 13 | +adds the dependency and expects `@SpringBootApplication` to pick MetaObjects up gets |
| 14 | +**nothing** auto-wired; they must manually `@Import(MetaDataAutoConfiguration.class)`. |
| 15 | +The existing integration test masks this because it wires the config via |
| 16 | +`@ContextConfiguration(classes = MetaDataAutoConfiguration.class)` rather than |
| 17 | +exercising Boot's autoconfiguration *discovery*. |
| 18 | + |
| 19 | +Separately, `core-spring` only wires the **metadata loader** (`MetaDataLoaderRegistry`, |
| 20 | +a primary `MetaDataLoader`, `MetaDataService`). There is **no** autoconfiguration for |
| 21 | +the OMDB persistence layer: a Spring app that wants an `ObjectManagerDB` must hand-build |
| 22 | +it (`new ObjectManagerDB()` + `setDatabaseDriver(...)` + `setDataSource(...)`). |
| 23 | + |
| 24 | +This work modernizes the Spring integration to Boot 3, ships an idiomatic |
| 25 | +one-dependency **starter**, adds **OMDB persistence autoconfiguration**, and hardens |
| 26 | +OMDB for Java 21 virtual threads. It deliberately does **not** adopt jOOQ (see |
| 27 | +Non-Goals). |
| 28 | + |
| 29 | +## Scope |
| 30 | + |
| 31 | +In scope: |
| 32 | +1. **Boot 3 registration fix** — `AutoConfiguration.imports`; delete the dead |
| 33 | + `spring.factories` entry. |
| 34 | +2. **OMDB persistence autoconfiguration** — build an `ObjectManagerDB` from a Spring |
| 35 | + `DataSource` + the primary `MetaDataLoader`, transaction-integrated via the existing |
| 36 | + `SpringObjectConnections`. |
| 37 | +3. **Thin `metaobjects-spring-boot-starter`** module — one-dependency onboarding. |
| 38 | +4. **Virtual-thread hardening (cheap)** — audit OMDB's blocking JDBC paths for |
| 39 | + carrier-thread pinning (`synchronized` across JDBC calls) and convert to |
| 40 | + `ReentrantLock` where found. No async API, no forced toggle. |
| 41 | +5. **Tidy** — strip stale "OSGi-compatible" language from the autoconfig javadoc |
| 42 | + (OSGi was removed in 7.1.0); refresh the `core-spring` README. |
| 43 | + |
| 44 | +Out of scope / Non-Goals: |
| 45 | +- **jOOQ migration.** jOOQ's free Open Source Edition supports only open-source |
| 46 | + databases; Oracle / SQL Server / DB2 require a *paid* license. OMDB ships drivers for |
| 47 | + Postgres / MySQL / MSSQL / Oracle / Derby, so a jOOQ migration would paywall or drop |
| 48 | + commercial-DB support in a public OSS project. jOOQ also generates code *from* a DB |
| 49 | + schema, the inverse of MetaObjects' metadata-is-the-spine model, and would rip out the |
| 50 | + driver layer just cleaned up in FR-003 Plan 4. Recorded as a closed open-question. |
| 51 | +- Forcing `spring.threads.virtual.enabled` on consumers. |
| 52 | +- Any change to the OMDB SQL-generation or driver dialect logic beyond the pinning audit. |
| 53 | + |
| 54 | +## Architecture |
| 55 | + |
| 56 | +### Module structure |
| 57 | + |
| 58 | +- **`core-spring`** remains the **autoconfigure** module. It **already** compile-depends |
| 59 | + on `omdb` (for `SpringObjectConnections`/`ObjectConnectionDB`), so no dependency change |
| 60 | + is needed; the OMDB beans are gated at runtime by `@ConditionalOnBean(DataSource)` (plus |
| 61 | + a defensive `@ConditionalOnClass(ObjectManagerDB)`), so they only activate when the |
| 62 | + consumer actually has a `DataSource`. |
| 63 | +- **New `metaobjects-spring-boot-starter`** — a thin module (no Java code) whose pom |
| 64 | + depends on `metadata` + `omdb` + `core-spring`. Adding this single dependency gives a |
| 65 | + consumer the full metadata-loader + OMDB-persistence wiring. |
| 66 | +- `omdb` / `om` are unchanged except for the §"Virtual-thread hardening" pinning audit. |
| 67 | + |
| 68 | +Net change: one new module; `core-spring` gains one optional dependency. |
| 69 | + |
| 70 | +### Autoconfiguration classes |
| 71 | + |
| 72 | +Two independent `@AutoConfiguration` classes (loader and persistence stay decoupled): |
| 73 | + |
| 74 | +**`MetaDataAutoConfiguration`** (existing — behavior unchanged): |
| 75 | +- `MetaDataLoaderRegistry` (`@Primary`), primary `MetaDataLoader`, `MetaDataService`. |
| 76 | +- Driven by the existing `metaobjects.*` `@ConfigurationProperties` |
| 77 | + (`MetaDataLoaderConfiguration`). |
| 78 | +- Only the registration mechanism and stale OSGi javadoc change. |
| 79 | + |
| 80 | +**`ObjectManagerAutoConfiguration`** (new): |
| 81 | +- Produces an `ObjectManagerDB` bean from the Spring `DataSource` + the primary |
| 82 | + `MetaDataLoader`. |
| 83 | +- Connections flow through the existing `SpringObjectConnections` so Spring-managed |
| 84 | + transactions / `@Transactional` participate correctly (FR-003 Spring-tx groundwork). |
| 85 | +- Conditions: |
| 86 | + - `@ConditionalOnClass(ObjectManagerDB.class)` — only when `omdb` is on the classpath. |
| 87 | + - `@ConditionalOnBean(DataSource.class)` — only when a DataSource exists. |
| 88 | + - `@ConditionalOnMissingBean(ObjectManagerDB.class)` — consumer override always wins. |
| 89 | +- New `metaobjects.omdb.*` `@ConfigurationProperties`: `dialect`, |
| 90 | + `enforce-transaction` (maps to `ObjectManagerDB.setEnforceTransaction`). |
| 91 | + |
| 92 | +### Driver selection |
| 93 | + |
| 94 | +`DatabaseDriver` is chosen by **auto-detection with an explicit override**: |
| 95 | +- If `metaobjects.omdb.dialect` is set (`postgres|mysql|mssql|oracle|derby`), use the |
| 96 | + matching driver. |
| 97 | +- Otherwise open one connection and map |
| 98 | + `DatabaseMetaData.getDatabaseProductName()` to the driver |
| 99 | + (`PostgreSQL`→`PostgresDriver`, `MySQL`→`MySQLDriver`, |
| 100 | + `Microsoft SQL Server`→`MSSQLDriver`, `Oracle`→`OracleDriver`, |
| 101 | + `Apache Derby`→`DerbyDriver`). Unknown product name → a clear |
| 102 | + `MetaDataException` instructing the user to set `metaobjects.omdb.dialect`. |
| 103 | + |
| 104 | +Rationale: zero-config for the common case, explicit escape hatch otherwise. The |
| 105 | +product-name→driver mapping lives in a small, separately-tested helper. |
| 106 | + |
| 107 | +> **Implementation note:** confirm during planning whether `ObjectManagerDB` needs the |
| 108 | +> `MetaDataLoader`/registry wired beyond `DataSource` + `DatabaseDriver` (the integration |
| 109 | +> tests set only driver + datasource). If the OM requires the loader for object-class |
| 110 | +> binding, wire the primary `MetaDataLoader` in the same bean method. |
| 111 | +
|
| 112 | +## Virtual-thread hardening |
| 113 | + |
| 114 | +OMDB stays a synchronous JDBC API. On Java 21, blocking calls run fine on virtual |
| 115 | +threads **provided** no `synchronized` monitor is held across a blocking JDBC call |
| 116 | +(that pins the carrier thread). Audit the OMDB blocking paths — the FR-003 Plan 4 |
| 117 | +atomic-mapping-cache locking and the `synchronized getDatabaseDriver()` are the prime |
| 118 | +suspects — and convert any `synchronized`-around-JDBC to `java.util.concurrent.locks.ReentrantLock`. |
| 119 | +If the audit finds no JDBC-spanning monitor, document that and make no change (no churn). |
| 120 | +The starter does not set `spring.threads.virtual.enabled`; it only guarantees OMDB is |
| 121 | +pinning-safe when a consumer enables it, and documents the option. |
| 122 | + |
| 123 | +## Boot 3 registration fix + tidy |
| 124 | + |
| 125 | +- Add `core-spring/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports` |
| 126 | + listing `MetaDataAutoConfiguration` and `ObjectManagerAutoConfiguration`. |
| 127 | +- Delete `core-spring/src/main/resources/META-INF/spring.factories`. |
| 128 | +- Remove "OSGi-compatible" / OSGi references from `MetaDataAutoConfiguration` javadoc and |
| 129 | + any sibling that still mentions it. Refresh the `core-spring` README: document the |
| 130 | + one-dependency starter and the `metaobjects.omdb.*` properties. |
| 131 | + |
| 132 | +## Testing |
| 133 | + |
| 134 | +- **Autoconfiguration discovery** (`ApplicationContextRunner`, the gap the current |
| 135 | + `@ContextConfiguration` test masks): |
| 136 | + - With a DataSource + OMDB present → the `ObjectManagerDB` bean is created. |
| 137 | + - With no DataSource → `ObjectManagerAutoConfiguration` backs off (no bean, no error). |
| 138 | + - With a consumer-defined `ObjectManagerDB` → autoconfig backs off (`@ConditionalOnMissingBean`). |
| 139 | + - The loader autoconfig still produces its beans through real discovery. |
| 140 | +- **Driver selection** — unit-test the product-name→driver mapping and the |
| 141 | + `metaobjects.omdb.dialect` override; unknown product name → clear error. |
| 142 | +- **Transaction-binding slice** — an embedded-Derby test (Derby is a real OMDB driver, |
| 143 | + auto-detected from product name; core-spring has no Testcontainers, and full persist/read |
| 144 | + is already covered by the `integration-tests` module against Postgres/Derby). With a |
| 145 | + Derby `DataSource` + `DataSourceTransactionManager`, assert the *autoconfigured* |
| 146 | + `ObjectManagerDB`'s `getConnection()` returns the SAME physical connection Spring bound |
| 147 | + to the active transaction, and that `close()` on it is a no-op (mirrors the existing |
| 148 | + `SpringObjectConnectionTest`). This proves the new Spring-tx wiring without duplicating |
| 149 | + OMDB persistence coverage. |
| 150 | +- Existing `core-spring` tests stay green. |
| 151 | + |
| 152 | +## Sequencing |
| 153 | + |
| 154 | +1. Boot 3 registration fix + OSGi tidy (loader autoconfig only) + discovery test — |
| 155 | + smallest standalone fix; verifies the bug is closed. |
| 156 | +2. `ObjectManagerAutoConfiguration` + driver-selection helper + `metaobjects.omdb.*` |
| 157 | + properties + slice tests. |
| 158 | +3. `metaobjects-spring-boot-starter` thin module + README. |
| 159 | +4. Virtual-thread pinning audit (+ fix if found). |
| 160 | +5. Record jOOQ as a closed non-goal in CLAUDE.md open questions. |
| 161 | + |
| 162 | +## Open questions |
| 163 | + |
| 164 | +- Whether `ObjectManagerDB` requires the `MetaDataLoader` wired beyond driver + |
| 165 | + datasource for object-class binding (resolve in planning by reading the OM API). |
| 166 | +- Boot 2.x support is dropped (Boot 2 is EOL); the starter targets Boot 3.5+ only. |
0 commit comments