You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by bo...@apache.org on 2026/10/22 06:27:27 UTC
(grails-core) 01/02: docs: add the mongodb-developer skill and scoped agent guides for build-logic and grails-gradle
This is an automated email from the ASF dual-hosted git repository.
borinquenkid pushed a commit to branch docs/agent-skills-mongodb-8.0.x
in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit 0eca1e5fc107c17ce59adb8641f6b6101eec0159
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Tue Oct 20 20:43:51 2026 -0500
docs: add the mongodb-developer skill and scoped agent guides for build-logic and grails-gradle
Salvaged from #15977 and checked against 8.0.x. The mongodb-developer skill documents the nine subprojects of
grails-data-mongodb (the embedded subproject included), the mapping context, the codec pipeline and the Testcontainers
test pattern, with the per-subproject test configuration as it is today. The two scoped AGENTS.md files state what differs
from the root guide in those builds: plain @CompileStatic, javax.inject being JSR-330, versions managed by the root's
dependencies.gradle, and how each build is tested and checked. The root guide lists the skill and links the scoped guides.
---
.agents/skills/mongodb-developer/SKILL.md | 122 ++++++++++++++++++++++++++++++
AGENTS.md | 6 +-
build-logic/AGENTS.md | 56 ++++++++++++++
grails-gradle/AGENTS.md | 55 ++++++++++++++
4 files changed, 237 insertions(+), 2 deletions(-)
diff --git a/.agents/skills/mongodb-developer/SKILL.md b/.agents/skills/mongodb-developer/SKILL.md
new file mode 100644
index 0000000000..9b613731b5
--- /dev/null
+++ b/.agents/skills/mongodb-developer/SKILL.md
@@ -0,0 +1,122 @@
+---
+name: mongodb-developer
+description: Guide for working in the grails-data-mongodb module (GORM for MongoDB) — codec-based document mapping, GeoJSON, embedded documents, TTL indexes, multi-tenancy, and Testcontainers-backed tests. Use this when changing code or tests under grails-data-mongodb.
+license: Apache-2.0
+---
+<!--
+SPDX-License-Identifier: Apache-2.0
+
+Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements; and to You under the Apache License, Version 2.0.
+-->
+
+## What I Do
+
+- Provide repository-specific guidance for the `grails-data-mongodb` project (GORM for MongoDB — a document-database GORM implementation, not a relational one; do not bring Hibernate/binder mental models here).
+- Guide changes around `MongoMappingContext`, `BsonPersistentEntityCodec`/`PersistentEntityCodec`, and the codec-registry-based encode/decode pipeline.
+- Keep changes aligned with the Testcontainers-backed testing pattern this module actually uses.
+
+## When to Use Me
+
+Activate this skill when working on the MongoDB module, especially for:
+
+- Changes under `grails-data-mongodb/**` (any of its 9 subprojects — see Module Structure).
+- Document/collection mapping, codec, or GeoJSON-type work.
+- Index (including TTL) creation/reconciliation logic.
+- Multi-tenancy or multi-connection MongoDB work.
+
+## Module Context
+
+`grails-data-mongodb` has no `settings.gradle` of its own — it's part of the root aggregate build, same as `grails-data-hibernate7`. All of root `AGENTS.md`'s rules (jakarta, `@GrailsCompileStatic`, CodeNarc/Checkstyle/PMD/SpotBugs, `dependencies.gradle`/BOM) apply here unmodified.
+
+Nine Gradle projects live under this directory, mapped to real Gradle project names (not always `grails-data-mongodb-<dirname>` — check before assuming):
+
+| Directory | Gradle project | Role |
+|---|---|---|
+| `core/` | `:grails-data-mongodb-core` | The actual GORM-for-MongoDB implementation: `MongoDatastore`, `MongoMappingContext`, `MongoQuery`, GeoJSON types |
+| `bson/` | `:grails-data-mongodb-bson` | Low-level, GORM-independent BSON codec machinery (`BsonPersistentEntityCodec`, per-type property codecs, hand-rolled JSON tokenizer) |
+| `ext/` | `:grails-data-mongodb-ext` | A single file, Groovy extension methods on the raw MongoDB driver (`distinct`, `watch`/change streams, `deleteMany`) |
+| `boot-plugin/` | `:grails-data-mongodb-spring-boot` | Spring Boot auto-configuration (`MongoDbGormAutoConfiguration`) |
+| `spring-data/` | `:grails-data-mongodb-spring-data` | Lets GORM-for-MongoDB and Spring Data MongoDB share a `MongoClient`/codecs/transaction |
+| `grails-plugin/` | `:grails-data-mongodb` (note: not `-grails-plugin` suffixed) | Classic Grails plugin descriptor (`MongodbGrailsPlugin`); also ships the app-facing `grails.test.mongodb.MongoSpec` test base via `testFixtures` |
+| `gson-templates/` | `:grails-data-mongodb-gson-templates` | JSON Views (`.gson`) for `ObjectId` and GeoJSON types |
+| `embedded/` | `:grails-data-mongodb-embedded` | Starts a real `mongod` (Flapdoodle, a `compileOnly` dependency the application adds itself) before the application context refreshes and publishes its URL into the configured properties; for applications, not for this module's own tests |
+| `docs/` | `:grails-data-mongodb-docs` | Asciidoc manual + groovydoc aggregation only, no source |
+
+## Key Classes and Responsibilities
+
+There is no single `GrailsDomainBinder`-equivalent class — the responsibility splits across three cooperating layers:
+
+### Mapping (the binder equivalent)
+
+- `MongoMappingContext` (`core/.../mongo/config/MongoMappingContext.java`) — owns the private inner `MongoDocumentMappingFactory extends AbstractGormMappingFactory<MongoCollection, MongoAttribute>`, which is the actual binder: forces the `_id` field name, applies the global `stringIdDefaultStoredAs` default, wires codec-registry-backed custom types, and registers all GeoJSON custom types.
+- `MongoCollection` (`core/.../config/MongoCollection.groovy`) — entity-level mapping (collection name, database, `writeConcern`, indices, sort).
+- `MongoAttribute` (`core/.../config/MongoAttribute.groovy`) — property-level mapping (`reference` for DBRef vs. embed, `field`, `geoIndex`, `index`, `indexAttributes`).
+- `MappingBuilder.document { ... }` (`core/.../grails/mongodb/mapping/MappingBuilder.groovy`) — the programmatic DSL entry point.
+
+### Codec pipeline (the runtime read/write engine)
+
+- `BsonPersistentEntityCodec` (`bson/.../BsonPersistentEntityCodec.groovy`) and its Mongo subclass `PersistentEntityCodec` (`core/.../engine/codecs/PersistentEntityCodec.groovy`) — a static `ENCODERS`/`DECODERS` registry keyed by `PersistentProperty` subtype (`Identity`, `TenantId`, `Simple`, `Embedded`, `EmbeddedCollection`, `Custom`, `Basic`, plus Mongo-specific `OneToOne`/`ManyToOne`/`OneToMany`/`ManyToMany`). `encode`/`decode` walk `entity.persistentProperties` and dispatch per prope [...]
+
+### Datastore
+
+- `MongoDatastore` (`core/.../mongo/MongoDatastore.java`) — owns index creation/reconciliation against live MongoDB and transaction-capability detection based on cluster topology.
+
+## Confirmed MongoDB-Specific Concepts (implemented — don't assume beyond this list)
+
+- **Embedded documents & embedded collections** — `Embedded`/`EmbeddedCollection` property kinds, with dedicated in-place `$set`/`$unset` update logic distinct from top-level property updates.
+- **GeoJSON types** — `grails.mongodb.geo.{Point,LineString,Polygon,MultiPoint,MultiLineString,MultiPolygon,GeometryCollection,Box,Circle,Sphere,Shape}`, each with a custom-type marshaller. Query support: `near`, `nearSphere`, `withinCircle`, `withinBox`, `withinPolygon`, `geoWithin`, `geoIntersects`. Legacy `2d`/`2dsphere` indexes via `MongoAttribute.geoIndex(String)`.
+- **TTL indexes** — property-level `indexAttributes: [expireAfterSeconds: N]` materializes a MongoDB TTL index, reconciled in place via `collMod` rather than drop/rebuild (see Pitfalls).
+- **DBRef vs. embedding** — `MongoAttribute.reference` toggles whether a `ToOne`/`ToMany` association is inline-embedded or a `com.mongodb.DBRef`.
+- **Schemaless/dynamic attributes** — `DynamicAttributes` trait support baked into `BsonPersistentEntityCodec` and `MongoEntity`.
+- **Change streams** — `MongoExtensions.watch(...)`, a thin Groovy-extension wrapper over the driver's native `watch()`, not a GORM-level abstraction.
+- **Multi-document transactions** — opt-in via `grails.mongodb.transactional`, gated on cluster topology (`REPLICA_SET`/`SHARDED`/`LOAD_BALANCED` supported; `STANDALONE` falls back with a one-time warning).
+- **Multi-tenancy / multiple named connections** — supported (discriminator-based multi-tenancy, `grails.mongodb.connections`).
+
+### Not implemented — do not document or rely on these
+
+- **`shard "name"` in the mapping DSL is a no-op.** There is no `shard` method on `Collection`/`MongoCollection`. The call silently falls through to the generic `Entity.methodMissing` catch-all, which just records it as an ad hoc property config — it never issues a MongoDB `shardCollection` command. Verified: zero `shardCollection` calls anywhere in `core/src/main`. If you see `shard "..."` in a mapping block, treat it as dead syntax, not a real feature.
+- **Capped collections** — not implemented anywhere in this module.
+
+## Testing Rules
+
+The module's own tests use a real MongoDB via Testcontainers; the `embedded` subproject is a runtime feature for applications, not a test fallback. Docker or Podman must be running on the host.
+
+- **`GrailsDataTckSpec<GrailsDataMongoTckManager>`** is the dominant pattern (111+ specs) — the same shared TCK harness the Hibernate modules use (`grails-datamapping-tck`), parameterized with `GrailsDataMongoTckManager` (`core/src/test/groovy/.../GrailsDataMongoTckManager.groovy`), which starts a real `MongoDBContainer` in `setupSpec()`/stops it in `cleanupSpec()`, and drops all non-system databases after each test.
+ ```groovy
+ class DocumentMappingSpec extends GrailsDataTckSpec<GrailsDataMongoTckManager> {
+ void setupSpec() {
+ manager.registerDomainClasses(CustomMapping)
+ }
+ void "test custom document mapping"() { ... }
+ }
+ ```
+ Domain classes are top-level classes in the same spec file, registered via `manager.registerDomainClasses(...)` in `setupSpec()` — same convention as `HibernateGormDatastoreSpec`.
+- **`AutoStartedMongoSpec`** (`grails-testing-support-mongodb`) — a lighter-weight base used by a handful of specs; a Spock global extension auto-injects `@Shared MongoDBContainer dbContainer` and auto-constructs a `@Shared MongoDatastore` field if present.
+- **`grails.test.mongodb.MongoSpec`** — a separate, *app-facing* base class shipped via `testFixtures` in `grails-plugin/`, for Grails-application-level Mongo integration tests. Don't confuse it with the two internal patterns above — a spec being both a `MongoSpec` and an `AutoStartedMongoSpec` is explicitly forbidden.
+- The MongoDB image version comes from `-PmongodbContainerVersion`, which CI always passes; see `gradle/mongodb-forked-test-config.gradle`.
+- `core` applies `gradle/mongodb-forked-test-config.gradle` (`maxParallelForks = rootProject.ext.configuredTestParallel`, the forked, parallel layout); every other subproject (`bson`, `ext`, `embedded`, `spring-data`, `boot-plugin`, `grails-plugin`, `gson-templates`) applies `gradle/mongodb-test-config.gradle`, which runs serially (`maxParallelForks = 1`). Check which file a subproject applies before changing its parallelism.
+- `-PskipMongodbTests` / `-PonlyMongodbTests` gate these tests at the root, matching the `Container missing` row in root `AGENTS.md`'s Common Issues table — use `-PskipMongodbTests` when Docker isn't available.
+
+## Change Workflow
+
+1. Identify which layer owns the behavior: mapping (`MongoMappingContext`/`MongoCollection`/`MongoAttribute`) vs. codec/runtime (`BsonPersistentEntityCodec`/`PersistentEntityCodec`) vs. datastore-level (`MongoDatastore`, index/transaction handling).
+2. Check whether the change affects the general `bson/` codec machinery (used by any BSON-backed consumer) or is genuinely Mongo-specific (`core/`) — don't put Mongo-only logic in `bson/`.
+3. Update or add specs via `GrailsDataTckSpec<GrailsDataMongoTckManager>`, registering any new domain classes as top-level classes in the same spec file.
+4. Run the affected subproject's tests with Docker/Podman running; expect `core` tests to run serially.
+
+## Pitfalls to Avoid
+
+- Do not treat `shard "name"` as a functioning DSL keyword — it's a documented no-op (see above). Don't build new features assuming it works, and flag it if you're asked to "fix" sharding — the DSL surface exists but the implementation doesn't.
+- Do not change a subproject's test parallelism without reading the test config file it applies; the serial ones share one container.
+- TTL index changes go through `collMod`, not drop/rebuild — a single-field `expireAfterSeconds` change should reconcile in place. MongoDB silently ignores the TTL option on a compound index; don't declare one there.
+- The global `stringIds.defaultStoredAs` setting must be read by `MongoMappingContext` **before** `initialize(classes)` runs (`createIdentity` is invoked during entity registration and depends on it) — if you touch mapping-context initialization order, this dependency is easy to break silently.
+- Known unaddressed gaps (real `// TODO`s in the encode/decode path, not exhaustive but worth checking before assuming behavior): unprocessed `OneToMany` associations in `encodeUpdate` (`PersistentEntityCodec.groovy`), `Map` handling in the embedded-collection update path, and embedded-collection support in the base `BsonPersistentEntityCodec.encodeUpdate` (only the Mongo subclass handles it).
+
+## Known Status and Constraints
+
+- Multi-document transactions require positive cluster-topology detection (`REPLICA_SET`/`SHARDED`/`LOAD_BALANCED`); a `STANDALONE` cluster falls back to legacy client-side flush behavior with a one-time warning, not a hard failure.
+- `MongoDbGormAutoConfiguration` only closes a `MongoClient` it created itself, not one supplied as an external bean — preserve that ownership tracking if you touch auto-configuration shutdown logic.
+
+## Source of Truth
+
+This skill is the repository guidance for `grails-data-mongodb` work. When module conventions change, update this skill directly so agents load current rules from `.agents/skills/mongodb-developer/SKILL.md`.
diff --git a/AGENTS.md b/AGENTS.md
index 48727f5967..f07a125633 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -75,6 +75,7 @@ export GRADLE_OPTS="-Dorg.gradle.jvmargs=-Xmx4G"
> - Writing Hibernate code → Read `.agents/skills/hibernate-developer/SKILL.md`
> - Fixing style/analysis violations → Read `.agents/skills/violation-fixer/SKILL.md`
> - Fixing broken test → Read `.agents/skills/test-fixer/SKILL.md`
+> - Working in GORM for MongoDB → Read `.agents/skills/mongodb-developer/SKILL.md`
> - Integrating a standalone repository -> Read `.agents/skills/mono-repo-integration/SKILL.md`
>
> Use your file reading capability to load the skill content before proceeding with any code changes.
@@ -89,6 +90,7 @@ export GRADLE_OPTS="-Dorg.gradle.jvmargs=-Xmx4G"
| **hibernate-developer** | `.agents/skills/hibernate-developer/SKILL.md` | Hibernate 7 mapping, binders, generators |
| **violation-fixer** | `.agents/skills/violation-fixer/SKILL.md` | Fix style/analysis violations (CodeNarc, Checkstyle, PMD, SpotBugs) |
| **test-fixer** | `.agents/skills/test-fixer/SKILL.md` | Aggregate and fix test failures |
+| **mongodb-developer** | `.agents/skills/mongodb-developer/SKILL.md` | GORM for MongoDB: codec-based mapping, GeoJSON, indexes, multi-tenancy, Testcontainers tests |
| **mono-repo-integration** | `.agents/skills/mono-repo-integration/SKILL.md` | Merge a standalone Grails plugin repository into this monorepo |
## Repository Conventions
@@ -130,8 +132,8 @@ This repository contains multiple independent Gradle projects:
| Project | Description | Build Command |
|---------|-------------|---------------|
| **grails-core** (root) | Main framework with 60+ modules | `./gradlew build` |
-| **build-logic/** | Gradle convention plugins for the build | `cd build-logic && ./gradlew build` |
-| **grails-gradle/** | Grails Gradle plugins | `cd grails-gradle && ./gradlew build` |
+| **build-logic/** | Gradle convention plugins for the build; see [`build-logic/AGENTS.md`](build-logic/AGENTS.md) | `cd build-logic && ./gradlew build` |
+| **grails-gradle/** | Grails Gradle plugins; see [`grails-gradle/AGENTS.md`](grails-gradle/AGENTS.md) | `cd grails-gradle && ./gradlew build` |
| **grails-forge/** | Application generator (like Spring Initializr) | `cd grails-forge && ./gradlew build` |
| **end-to-end/** | End-to-end tests consuming published Grails artifacts (see `end-to-end/README.md` for required setup) | `cd end-to-end && ./gradlew check` |
diff --git a/build-logic/AGENTS.md b/build-logic/AGENTS.md
new file mode 100644
index 0000000000..9cf5534b85
--- /dev/null
+++ b/build-logic/AGENTS.md
@@ -0,0 +1,56 @@
+<!--
+SPDX-License-Identifier: Apache-2.0
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+# Agent Guide for build-logic
+
+> **IMPORTANT**: `build-logic` is the Gradle convention-plugin layer that the root build, `grails-gradle` and
+> `grails-forge` consume through `includeBuild`. It is build tooling, not Grails application code, so two of the
+> root rules do not apply here (see below). Root [`AGENTS.md`](../AGENTS.md) still governs PR, branch and review
+> conventions and repository-wide policy.
+
+## Quick Reference
+
+Run from inside `build-logic/`:
+
+```bash
+./gradlew build # all three projects
+./gradlew :build-logic:test # the convention plugins (Spock and TestKit specs)
+./gradlew :grails-docs-core:test # the documentation engine
+```
+
+## Rules That Differ From Root
+
+1. **Use plain `@CompileStatic`, not `@GrailsCompileStatic`.** Nothing here is a Grails artefact; no source under this directory uses `@GrailsCompileStatic`.
+2. **`javax.inject.Inject` is correct here.** Gradle's constructor injection uses JSR-330's `javax.inject`, which is unrelated to the Jakarta EE migration. Do not change it to `jakarta.*`.
+3. **Versions are not pinned here.** `plugins/build.gradle` and `docs-core/build.gradle` apply the root's `dependencies.gradle`; do not hardcode a version in this build.
+4. **There is no dedicated CI job for this build.** Every other build includes it and exercises the plugins through its own CI runs, so a missing job is not a sign of neglect.
+
+## Project Structure
+
+| Directory | Gradle project | Role |
+|---|---|---|
+| `plugins/` | `:build-logic` | The convention plugins (19 plugin ids registered in `plugins/build.gradle`), the repository-conventions and violation-aggregation tasks |
+| `docs-core/` | `:grails-docs-core` | The user-guide generation engine and the BOM extraction used by the documentation builds |
+| `vulnerability-scan/` | `:vulnerability-scan` | The dependency vulnerability scan run by the Vulnerability Scan workflow |
+
+The `gradle-developer` skill (`../.agents/skills/gradle-developer/SKILL.md`) covers the convention plugins, the BOM
+platforms and the Gradle 9 task-configuration rules; read it before changing a plugin.
+
+## Keep Other Documents In Step
+
+Root `AGENTS.md` and the `hibernate-developer` and `violation-fixer` skills describe behaviour implemented by plugins in
+this build: the code-analysis and violation reports, the repository-conventions check, JaCoCo aggregation. When a plugin's
+behaviour changes, update those documents in the same change.
diff --git a/grails-gradle/AGENTS.md b/grails-gradle/AGENTS.md
new file mode 100644
index 0000000000..acad93cd11
--- /dev/null
+++ b/grails-gradle/AGENTS.md
@@ -0,0 +1,55 @@
+<!--
+SPDX-License-Identifier: Apache-2.0
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+# Agent Guide for grails-gradle
+
+> **IMPORTANT**: `grails-gradle` holds the Grails Gradle plugins that applications apply. It has its own
+> `settings.gradle` and its own CI jobs, but it shares the root's dependency management: `build.gradle` applies the
+> root's `dependencies.gradle`, so versions are managed there. It is build tooling, not Grails application code, so
+> two of the root rules do not apply here (see below). Root [`AGENTS.md`](../AGENTS.md) still governs PR, branch and
+> review conventions and repository-wide policy.
+
+## Quick Reference
+
+Run from inside `grails-gradle/`:
+
+```bash
+./gradlew build --continue
+./gradlew build -PskipTests -PskipCodeStyle
+./gradlew validateDependencyVersions --continue
+```
+
+## Rules That Differ From Root
+
+1. **Use plain `@CompileStatic`, not `@GrailsCompileStatic`.** The plugins are Gradle code, not Grails artefacts; the only `@GrailsCompileStatic` mentions in this build are documentation of what a plugin configures for an application.
+2. **`javax.inject.Inject` and `javax.xml.*` are correct here.** They are JSR-330 and JDK APIs, not Jakarta EE packages. The `jakarta.*` references in this build configure a downstream application's dependencies.
+3. **Versions are not pinned here.** `gradle.properties` holds daemon and cache settings only; `build.gradle` applies the root's `dependencies.gradle`. Bump versions in the root file.
+4. **Tests are Gradle TestKit tests.** Plugin specs extend `GradleSpecification` (`plugins/src/test/groovy/org/grails/gradle/plugin/core/GradleSpecification.groovy`) and run a real Gradle build against a generated project, not a mocked Grails application.
+
+## Project Structure
+
+| Directory | Gradle project | Role |
+|---|---|---|
+| `bom/` | `:grails-gradle-bom` | A `java-platform` constraining the sibling projects |
+| `common/` | `:grails-gradle-common` | Shared utilities |
+| `model/` | `:grails-gradle-model` | The subset of Grails runtime classes the plugins need at build time, before the framework is on the classpath |
+| `plugins/` | `:grails-gradle-plugins` | The `Plugin<Project>` implementations |
+| `tasks/` | `:grails-gradle-tasks` | The task classes |
+
+## CI
+
+The build-health, code-style, code-analysis, coverage and release workflows under `.github/workflows` each have a job
+with `working-directory: grails-gradle`, so this build is checked on its own on every push and pull request.