The Problem
The original system was a single Micronaut 1 monolith, and everything about its data layer assumed exactly one deployable would ever touch the database. That assumption held until the platform needed to grow past it: new services were arriving on Micronaut 4 and 5, and every one of them needed the same tenants, records, documents, and credentials the monolith already owned. A big-bang rewrite of a live production data layer was not on the table. What we needed instead was a seam: a way for the monolith and the new services to share one PostgreSQL database without either side reaching directly into the other’s code, and without forking the source of truth into two copies that would drift apart.
The harder problem sat underneath that one. Whatever came out of this extraction was going to become the foundation the rest of the strangler program stood on, and the entity model’s own test suite was not trustworthy yet. Running the suite in parallel against a shared schema produced foreign key violations and duplicate-key errors, roughly 80 to 90 percent flaky. A foundation nobody believes cannot support a migration. Before extracting anything further, the suite itself needed to become deterministic.
The Approach
I split the work into two services, built in sequence: first the entity model, then the access layer on top of it.
The entity library came first, and it is schema-first and entity-only by design. It holds 75 @Entity classes, 9 @MappedSuperclass base types, and 44 enums, matched field-for-field against the live database rather than the other way around: the schema is the source of truth, and entities change to match it, never the reverse. The library carries zero business logic: no @Repository, no @Service, nothing transactional. Consuming services own their own data access; the library only owns the shape of the data. It is 133 of 138 commits, mine, and of the 75 entities, all but one are mine too. A teammate added the remaining entity under the same schema-first discipline I had set for the library, which is exactly the outcome I wanted from writing the rule down in the first place.
Keeping the entities honest meant reconciling against the live database directly, not against the migration history. Schema drift showed up twice as the monolith kept evolving underneath the extraction, and rather than infer the changes from the Flyway migration scripts, I pulled the actual schema with pg_dump and information_schema queries and reconciled the entities against what the database really looked like. The migration scripts describe intent; the live schema is the fact, and only one of those is safe to build on.
Determinism came before extraction went any further. The flaky suite turned out to be two separable problems: tests were not transactionally isolated from each other, and they were racing each other for the same rows under parallel execution. The fix was a two-part change, transactional rollback on every test plus serial execution, and it took the suite from that roughly 80 to 90 percent flaky baseline to 424 of 424 deterministic in October 2025. The suite has grown since, and now stands at 458 of 458, still fully deterministic.
With a trustworthy entity library in place, the bridge itself is a gRPC service that lets both framework generations read and write the same rows. It exposes seven services covering records, credentials, documents, tenants, intake, jobs, and dashboard metrics (that last one was the last of the seven to land), giving the Micronaut 1 monolith and the newer Micronaut 4 and 5 services a shared data-access surface instead of two independent ones. Job claiming uses FOR UPDATE SKIP LOCKED so concurrent workers pull distinct rows without blocking each other. Document writes use a two-phase idempotent pattern: an advisory-lock find-or-create step followed by a separate confirmation step, so a retried upload lands exactly once instead of creating duplicates. The bridge is 37 of 37 commits, sole author.
Results
The entity library and the gRPC bridge together give the monolith and the newer services a real coexistence layer: one shared schema, one set of entity definitions, and a data-access surface neither side has to fork around. Per the cross-repo cutover audit, one of the newer services is already live for its own email-intake traffic through this layer.
The decomposition is not finished, and it was never solo. Cutting the rest of the monolith’s call sites over to the bridge, so all of that traffic moves off the old path and not just one service’s slice of it, has not been scheduled, even though finishing it would be a massive benefit to the platform. A producer-side kill switch already exists in the monolith that would flip the remaining traffic over; it merged, but it ships dormant, default off, waiting on that cutover work. This is one seam in an estate that still has more of them. Strangle at the data seam, and make the tests deterministic before you pull.