docs: Add Data Services and GormEnhancer documentation for multi-datasource routing#15406
docs: Add Data Services and GormEnhancer documentation for multi-datasource routing#15406jamesfredley wants to merge 3 commits intoapache:7.0.xfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds comprehensive documentation for using GORM Data Services with multiple datasources, addressing a significant gap in both the Grails User Guide and GORM Hibernate5 documentation. The changes explain the @Transactional(connection) routing pattern, how ServiceTransformation copies connection annotations, and how to use GormEnhancer.findStaticApi() for complex queries that require HQL, criteria builders, or aggregations.
Changes:
- Added "GORM Data Services and Multiple Datasources" section to both grails-doc and grails-data-hibernate5 documentation with interface + abstract class pattern examples, connection routing explanations, and
GormStaticApimethod reference tables - Added cross-reference TIPs in transactionsMultiDataSource.adoc and dataSourceNamespaces.adoc directing readers to the new comprehensive Data Services sections
- Created proper AsciiDoc anchors and cross-references to link the documentation together
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| grails-doc/.../multipleDatasources.adoc | Added comprehensive 177-line section documenting Data Services with multiple datasources, including basic pattern, connection routing mechanics, GormEnhancer usage, and method reference table |
| grails-doc/.../transactionsMultiDataSource.adoc | Added TIP cross-referencing the new Data Services section for readers using the @Service annotation |
| grails-data-hibernate5/.../services/multipleDataSources.adoc | New 191-line file mirroring grails-doc content with GORM-specific formatting and cross-references |
| grails-data-hibernate5/.../services/index.adoc | Added include and anchor for the new multipleDataSources section in the Data Services chapter |
| grails-data-hibernate5/.../multipleDataSources/index.adoc | Added cross-reference section directing readers to detailed Data Services documentation |
| grails-data-hibernate5/.../multipleDataSources/dataSourceNamespaces.adoc | Added TIP explaining GormEnhancer.findStaticApi() as @CompileStatic-friendly alternative to namespace syntax |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…iTenant routing, and CRUD connection fixes Add documentation reflecting the post-fix state after PRs apache#15393, apache#15395, and apache#15396 are merged: - Add @CompileStatic + injected @service property example (PR apache#15396) - Add Multi-Tenancy with explicit datasource section (PR apache#15393) - List all CRUD methods that respect connection routing (PR apache#15395) - Soften IMPORTANT boxes to NOTE with authoritative tone Assisted-by: Claude Code <Claude@Claude.ai>
| } | ||
| ---- | ||
|
|
||
| TIP: The namespace syntax (e.g., `ZipCode.auditing.get(42)`) uses dynamic dispatch and is not compatible with `@CompileStatic`. For statically compiled code, use `GormEnhancer.findStaticApi(ZipCode, 'auditing')` to obtain a `GormStaticApi` handle with the same methods routed to the specified datasource, or use a Data Service with `@Transactional(connection = 'auditing')` for automatic routing. See the <<dataServicesMultipleDataSources,Data Services and Multiple Datasources>> section for the full pattern. |
There was a problem hiding this comment.
Isn't this a bug? Recommending people to use GormStaticApi I think is a horrible idea. It's an implementation detail.
There was a problem hiding this comment.
I put this back in draft, let's see where we land after fixing the few open bugs and hopefully there is a normal path that is fully unlocked to do this with @CompileStatic
|
|
||
| The `@Transactional(connection = 'books')` annotation on the abstract class ensures that all auto-implemented methods (`get`, `save`, `delete`, `findBy*`, `countBy*`, etc.) route to the `books` datasource. Without this annotation, queries silently route to the default datasource. | ||
|
|
||
| NOTE: The `@Service(Book)` annotation identifies the domain class but does not determine which datasource to use. Even if `Book` declares `datasource 'books'` in its mapping block, you must specify `@Transactional(connection = 'books')` on the abstract class to route operations to the correct datasource. |
There was a problem hiding this comment.
seems like we should open a feature request.
|
|
||
| 1. Copies the `@Transactional(connection = 'books')` annotation from the abstract class to the generated implementation class | ||
| 2. For each auto-implemented method, resolves the connection identifier via `findConnectionId()` | ||
| 3. Generates method bodies that use the appropriate connection - `GormEnhancer.findStaticApi(Book, 'books')` for CRUD operations and `DetachedCriteria.withConnection('books')` for finder queries |
There was a problem hiding this comment.
I don't think we should give this implementation detail. We don't document internal classes as it could cause people to use them thinking they're a public api
| } | ||
| ---- | ||
|
|
||
| The `GormStaticApi` returned by `findStaticApi()` provides these methods, all routed to the specified datasource: |
There was a problem hiding this comment.
Again, we shouldn't be referencing the implementation detail.
|
|
||
| 1. Copies the `@Transactional(connection = 'books')` annotation from the abstract class to the generated implementation class | ||
| 2. For each auto-implemented method, resolves the connection identifier via `findConnectionId()` | ||
| 3. Generates method bodies that use the appropriate connection - `GormEnhancer.findStaticApi(Book, 'books')` for CRUD operations and `DetachedCriteria.withConnection('books')` for finder queries |
| ===== Multi-Tenancy with Multiple Datasources | ||
|
|
||
|
|
||
| Domain classes that implement `GormEntity` with the `MultiTenant` trait and declare an explicit non-default datasource (e.g., `datasource 'analytics'`) route correctly through Data Services. GORM's `GormEnhancer` preserves explicit datasource qualifiers for multi-tenant entities, so Data Services using `@Transactional(connection = 'analytics')` work the same way as for non-tenant domain classes: |
There was a problem hiding this comment.
Let's not reference the GormEnhancer in the docs. It's an implementation detail.
Summary
Adds documentation for using GORM Data Services with multiple datasources - a topic completely absent from both grails-doc and the GORM Hibernate5 docs today. Covers
@Transactional(connection)routing,GormEnhancer.findStaticApi()for statically compiled escape-hatch queries, the interface + abstract class pattern,@CompileStaticwith injected@Serviceproperties, and MultiTenant entity routing.This documentation assumes the following fix PRs are merged:
GormEnhancer.allQualifiers()preserves explicit datasource qualifiers for MultiTenant entitiessave(),delete(),get()) respect@Transactional(connection)@CompileStaticon@Serviceabstract class with injected@Serviceproperties compiles correctlyChanges
grails-doc (Grails User Guide)
multipleDatasources.adoc- New "GORM Data Services and Multiple Datasources" section with 6 subsections:@Transactional(connection = 'books'), explaining that theconnectionparameter is required (the@Serviceannotation alone does not determine datasource routing)ServiceTransformationcopies the@Transactional(connection)annotation to the generated implementation and howfindConnectionId()propagates it to auto-implemented methodsGormEnhancer.findStaticApi(Book, 'books')pattern for HQL, criteria, and aggregation queries that cannot be auto-implemented. Includes a reference table of all availableGormStaticApimethods@CompileStaticon@Serviceabstract classes that inject other@Service-typed properties (enabled by fix: @CompileStatic on @Service abstract class with injected @Service properties fails to compile #15396)transactionsMultiDataSource.adoc- Added a TIP cross-referencing the new Data Services section, explicitly listing all routed CRUD methodsgrails-data-hibernate5 (GORM Hibernate Docs)
New file:
services/multipleDataSources.adoc- Full Data Services multi-datasource documentation mirroring the grails-doc content, including:@Transactional(connection)GormEnhancer.findStaticApi()andGormStaticApimethod reference table@CompileStatic+ injected@Serviceexampleservices/index.adoc- Added include for the new section with[[dataServicesMultipleDataSources]]anchormultipleDataSources/index.adoc- Added "Using Data Services with Multiple Datasources" cross-reference section, noting support for@CompileStatic, injected@Serviceproperties, andMultiTenantdomain classesmultipleDataSources/dataSourceNamespaces.adoc- Added TIP notingGormEnhancer.findStaticApi()and Data Services as@CompileStatic-friendly alternatives to namespace syntaxWhy This Is Needed
Currently there is zero documentation connecting GORM Data Services to multi-datasource usage:
multipleDatasources.adoconly shows the oldstatic datasource = 'lookup'patternservices/docs cover Data Services in detail but never mention multi-datasourcemultipleDataSources/docs cover namespace syntax but never mention Data Services@Transactional(connection = 'xxx')for routingGormEnhancer.findStaticApi()as a public API@CompileStaticon@Serviceclasses with injected@ServicepropertiesThese are all essential for production multi-datasource applications using modern GORM patterns. Without this documentation, developers must read the
ServiceTransformationsource code to understand how connection routing works.Verified Against Source
All technical claims were verified against grails-core source:
ServiceTransformation.groovyline 253:copyAnnotations(classNode, impl)copies@Transactional(connection)to generated implementationTransactionalTransform.findTransactionalAnnotation()lines 171-176: falls back tomethodNode.getDeclaringClass()to find the connection identifierSaveImplementer,FindOneImplementer,DeleteImplementer,CountImplementer: all callfindConnectionId()and use it in generated method bodies (post fix: Auto-implemented Data Service CRUD methods ignore @Transactional(connection) #15395)GormEnhancer.findStaticApi(Class, String): returnsGormStaticApibound to the specified connectionGormEnhancer.allQualifiers(): preserves explicit datasource qualifiers for MultiTenant entities (post fix: GormEnhancer.allQualifiers() overrides explicit datasource declarations for MultiTenant entities #15393)ServiceTransformation: no longer sets lazy getter block on abstract class PropertyNode for@Service-typed properties (post fix: @CompileStatic on @Service abstract class with injected @Service properties fails to compile #15396)Files Changed
grails-doc/.../multipleDatasources.adocgrails-doc/.../transactionsMultiDataSource.adocgrails-data-hibernate5/.../services/multipleDataSources.adocgrails-data-hibernate5/.../services/index.adocgrails-data-hibernate5/.../multipleDataSources/index.adocgrails-data-hibernate5/.../multipleDataSources/dataSourceNamespaces.adocEnvironment Information