You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/CONTRIBUTING.MD
+96-1
Original file line number
Diff line number
Diff line change
@@ -197,4 +197,99 @@ go mod tidy
197
197
198
198
Contributors should close conversations when complete. Reviewers may reopen if needed.
199
199
200
-
For additional help, see our [FAQ](./FAQ.md) or open a GitHub issue.
200
+
For additional help, see our [FAQ](./FAQ.md) or open a GitHub issue.
201
+
202
+
# Contributing to GraphQL Schema and Resolvers
203
+
204
+
## Overview
205
+
206
+
Our GraphQL setup uses a **hybrid approach** to schema management. We have individual GraphQL schemas defined within each module (e.g., `modules/core/`, `modules/warehouse/`), but we also generate a **unified schema** at the top level (`graph/`) primarily to **enable correct GraphQL introspection** for development tools like Postman, Insomnia, or Apollo Sandbox.
207
+
208
+
**Why this approach?**
209
+
210
+
***Introspection:** The previous method of registering module schemas only at runtime prevented standard introspection tools from seeing the complete API schema. This made development and testing difficult.
211
+
***Unified View:** The build-time merge creates `graph/schema.merged.graphql` and generates corresponding Go code (`graph/generated.go`, `graph/models_gen.go`) that represents the entire API surface. This allows tools to introspect correctly.
212
+
***Runtime Execution:** Currently, the server *still* uses the original `app.RegisterGraphSchema()` mechanism at runtime. This means the actual query execution relies on the schemas defined and registered within each module individually. *([Note: This might be refactored in the future to use the unified schema directly at runtime, which would simplify the process]).*
213
+
214
+
## Current Limitations & Workflow
215
+
216
+
Because we merge schemas via simple concatenation for the build-time generation step, but modules still need their *own* complete schemas for runtime registration, there's a necessary, slightly awkward workflow involving commenting/uncommenting common definitions:
217
+
218
+
1.**Duplicate Definitions:** Common scalars (like `scalar Time`, `scalar Int64`) and base types (`type Query`, `type Mutation`, `type Subscription`) should ideally be defined *only once* (e.g., in `modules/core/interfaces/graph/base.graphql`).
219
+
2.**Before `go generate`:** To allow the top-level `go generate ./graph/...` to succeed using the merged schema, you **MUST temporarily comment out** any re-definitions of these common scalars/types in other modules' `.graphql` files (e.g., comment out `scalar Time` in `modules/warehouse/interfaces/graph/base.graphql`).
220
+
3.**After `go generate`:** You **MUST uncomment** those lines back in the module `.graphql` files. This is because the runtime `app.RegisterGraphSchema()` for that module needs the complete schema definition, including those scalars, to work correctly.
221
+
222
+
**Yes, this comment/uncomment step is cumbersome and error-prone.** It's a known trade-off of this hybrid approach. Adhering strictly to defining common types only once (See Solution 1 / Best Practice mentioned previously) and refactoring the runtime to use the single generated `ExecutableSchema` would eliminate this step.
223
+
224
+
## Build Process Summary
225
+
226
+
Running `go generate ./graph/...` from the project root performs these steps:
227
+
228
+
## How Resolvers Work
229
+
230
+
We have two "layers" of resolvers:
231
+
232
+
1.**Module-Specific Resolvers:**
233
+
* Located in `modules/<module_name>/interfaces/graph/*.resolvers.go`.
234
+
* These contain the **actual business logic** for fetching data, calling services, etc.
235
+
* They are associated with the schemas loaded individually at runtime via `app.RegisterGraphSchema`.
236
+
***Example:**`modules/core/interfaces/graph/users.resolvers.go` implements the logic for the `user` and `users` queries defined in `modules/core/interfaces/graph/users.graphql`.
237
+
238
+
2.**Unified Top-Level Resolvers:**
239
+
* Located in `graph/resolver.go` and `graph/*.resolvers.go`.
240
+
* These are generated based on the *merged* schema (`schema.merged.graphql`).
241
+
* The main `graph/resolver.go` defines a `Resolver` struct that holds references to the *module-specific* resolvers (or the main `app` instance).
242
+
```go
243
+
// graph/resolver.go
244
+
typeResolverstruct {
245
+
app application.Application
246
+
coreResolver *coregraph.Resolver// From modules/core/interfaces/graph
247
+
warehouseResolver *warehousegraph.Resolver// From modules/warehouse/interfaces/graph
// return warehousemappers.PositionToGraphModel(warehousePosResult), nil // Example using mapper
275
+
return (*WarehousePosition)(warehousePosResult), nil // Direct cast if structs are identical
276
+
}
277
+
```
278
+
279
+
## How to Add/Modify GraphQLFields
280
+
281
+
1. **Define Schema:** Add/modify types, queries, or mutations in the relevant module's `.graphql` file(s) (e.g., `modules/warehouse/interfaces/graph/new_feature.graphql`).
282
+
2. **Implement Logic:** Add the corresponding resolver method implementation in the module's `*.resolvers.go`file (e.g., `modules/warehouse/interfaces/graph/new_feature.resolvers.go`). This implementation should contain the actual business logic.
283
+
3. **Prepare forGeneration:** **Temporarily comment out** duplicate scalar/base type definitions in non-core `.graphql` files.
284
+
4. **Generate UnifiedCode:** Run`go generate ./graph/...` from the project root. This updates/creates:
285
+
* `graph/generated.go`
286
+
* `graph/models_gen.go`
287
+
* Stubsfor new resolvers in `graph/*.resolvers.go` (e.g., `graph/schema.merged.resolvers.go`).
288
+
5. **Restore SourceSchemas:** **Uncomment** the lines commented out in Step3.
289
+
6. **Implement Delegation:** Go to the generated stub in the top-level `graph/*.resolvers.go` file. Implement the method by:
290
+
* Getting the correct module resolver instance (e.g., `r.warehouseResolver`).
291
+
* Calling the corresponding method you implemented in the module resolver (Step 2).
292
+
* Mapping the result to the top-level generated model typeifnecessary (often a simple type cast `(*graph.MyType)(result)` works if the underlying structs are the same, otherwise use a mapper).
293
+
7. **Commit:** Commit changes to the module's `.graphql` and `*.resolvers.go` files, the top-level `graph/*.resolvers.go` file containing the delegation, and all updated generated files in `graph/`.
294
+
295
+
This workflow allows us to have working introspection while keeping the resolver logic located within the relevant module. Remember the manual comment/uncomment steps before and after generation until the runtime is potentially refactored.
0 commit comments