From d5e042001d847d4d96b34bcb238f3770dc04817b Mon Sep 17 00:00:00 2001 From: Franklin Wang Date: Wed, 20 Dec 2023 14:54:55 +1300 Subject: [PATCH 01/10] Start documenting hydration --- docs/spec/Hydration.MD | 208 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 docs/spec/Hydration.MD diff --git a/docs/spec/Hydration.MD b/docs/spec/Hydration.MD new file mode 100644 index 000000000..3297f079f --- /dev/null +++ b/docs/spec/Hydration.MD @@ -0,0 +1,208 @@ +# Hydration + +Hydration is the process where references to pieces of data are resolved. +Think of it like SQL joins but in GraphQL land. + +# Example + +```graphql +type Query { + issueById(id: ID!): Issue + userById(id: ID!): User +} +type Issue { + id: ID! + title: String + assigneeId: ID + assignee: User + @hydrated( + field: "userById" + arguments: [{name: "id" value: "$source.assigneeId"}] + ) +} +type User { + id: ID! + name: String + email: String +} +``` + +Where the `assignee` field is the hydrated field, and does not exist in any service. +It's an artificial field made up by Nadel that fetches data from `userById` once the issue is loaded. + +# Terminology + +Borrowing from the example above. + +* `Issue.assignee` is the _hydration source_ field +* `Issue.assigneeId` is the _source input_ field +* `Query.userById` is the _fulfillment_ field. +* `Query.userById.id` is the _fulfillment_ argument. + +# How it works + +Given a query + +```graphql +query { + issueById(id: 1) { + id + title + assignee { + name + } + } +} +``` + +Nadel will transform the `assignee` field to replace it with the source input fields. + +i.e. Nadel will send a query like + +```patch +query { + issueById(id: 1) { + id + title + assigneeId + } +} +``` + +Given a response + +```json +{ + "data": { + "issueById": { + "id": "1", + "title": "Write Hydration Docs", + "assigneeId": "user-256" + } + } +} +``` + +Then Nadel will retrieve the source input field(s) i.e. + +```json +{ + "assigneeId": "user-256" +} +``` + +Then execute the hydration query + +```graphql +query { + userById(userId: "user-256") { + name + } +} +``` + +Which say yields + +```json +{ + "data": { + "userById": { + "name": "2^8" + } + } +} +``` + +Then Nadel will insert that into the original response + +```json +{ + "data": { + "issueById": { + "id": "1", + "title": "Write Hydration Docs", + "assignee": { + "name": "2^8" + } + } + } +} +``` + +# Batched Hydration + +So far we have observed non-batched hydration. + +Batched hydration is where multiple IDs are resolved in one go. + +All the source input field values are gathered together, split into multiple batches according to configuration, and +then sent down to the fulfillment service. + +e.g. + +```graphql +type Query { + issueById(id: ID!): Issue + usersByIds(id: ID!): [User] +} +type Issue { + id: ID! + title: String + collaboratorIds: [ID] + collaborators: [User] + @hydrated( + field: "usersByIds" + arguments: [{name: "id" value: "$source.assigneeId"}] + inputIdentifiedBy: [ + {sourceId: "collaboratorIds" resultId: "id"} + ] + batchSize: 10 + ) +} +type User { + id: ID! + name: String + email: String +} +``` + +All the `collaboratorIds` are resolved in one go using the `usersByIds` field. + +e.g. given a query + +```graphql +query { + issueById(id: 1) { + collaborators { + name + } + } +} +``` + +and response + +```json +{ + "data": { + "issueById": { + "collaboratorIds": [ + "user-256", + "user-8", + "user-64" + ] + } + } +} +``` + +Then the fulfillment query would look like + +``` +``` + +# Index Hydration + +# Considerations + +## Abstract Types From 9cf165e6e0d48fa55a6fdf664959fa7960ff04b6 Mon Sep 17 00:00:00 2001 From: Franklin Wang Date: Wed, 20 Dec 2023 16:47:43 +1300 Subject: [PATCH 02/10] More doco --- docs/spec/Hydration.MD | 155 ++++++++++++++++- ...example-many-issues-many-collaborators.yml | 158 ++++++++++++++++++ ...ation-example-many-issues-one-assignee.yml | 140 ++++++++++++++++ ...n-example-one-issue-many-collaborators.yml | 135 +++++++++++++++ ...dration-example-one-issue-one-assignee.yml | 121 ++++++++++++++ .../spec/spec-batch-hydration-example.yml | 134 +++++++++++++++ 6 files changed, 839 insertions(+), 4 deletions(-) create mode 100644 test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example-many-issues-many-collaborators.yml create mode 100644 test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example-many-issues-one-assignee.yml create mode 100644 test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example-one-issue-many-collaborators.yml create mode 100644 test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example-one-issue-one-assignee.yml create mode 100644 test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example.yml diff --git a/docs/spec/Hydration.MD b/docs/spec/Hydration.MD index 3297f079f..a10f8a2ca 100644 --- a/docs/spec/Hydration.MD +++ b/docs/spec/Hydration.MD @@ -34,8 +34,9 @@ It's an artificial field made up by Nadel that fetches data from `userById` once Borrowing from the example above. -* `Issue.assignee` is the _hydration source_ field -* `Issue.assigneeId` is the _source input_ field +* `Issue` is the `hydration source` type or object if referring to an instance. +* `Issue.assignee` is the _hydration source_ field. +* `Issue.assigneeId` is the _source input_ field. * `Query.userById` is the _fulfillment_ field. * `Query.userById.id` is the _fulfillment_ argument. @@ -129,6 +130,36 @@ Then Nadel will insert that into the original response } ``` +# Directive Definition + +The most up-to-date definition is found in `lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt` + +```graphql +"This allows you to hydrate new values into fields" +directive @hydrated( + "The target service" + service: String! + "The target top level field" + field: String! + "How to identify matching results" + identifiedBy: String! = "id" + "How to identify matching results" + inputIdentifiedBy: [NadelBatchObjectIdentifiedBy!]! = [] + "Are results indexed" + indexed: Boolean! = false + "Is querying batched" + batched: Boolean! = false + "The batch size" + batchSize: Int! = 200 + "The timeout to use when completing hydration" + timeout: Int! = -1 + "The arguments to the hydrated field" + arguments: [NadelHydrationArgument!]! + "Specify a condition for the hydration to activate" + when: NadelHydrationCondition +) repeatable on FIELD_DEFINITION +``` + # Batched Hydration So far we have observed non-batched hydration. @@ -138,12 +169,15 @@ Batched hydration is where multiple IDs are resolved in one go. All the source input field values are gathered together, split into multiple batches according to configuration, and then sent down to the fulfillment service. +The fulfilled objects are then pulled out of the response according to the `inputIdentifiedBy` i.e. objects are resolved +where the `sourceId` matches the `resultId` value. + e.g. ```graphql type Query { issueById(id: ID!): Issue - usersByIds(id: ID!): [User] + usersByIds(id: [ID!]!): [User] } type Issue { id: ID! @@ -152,7 +186,7 @@ type Issue { collaborators: [User] @hydrated( field: "usersByIds" - arguments: [{name: "id" value: "$source.assigneeId"}] + arguments: [{name: "id" value: "$source.collaboratorIds"}] inputIdentifiedBy: [ {sourceId: "collaboratorIds" resultId: "id"} ] @@ -198,9 +232,122 @@ and response Then the fulfillment query would look like +```graphql +query { + usersByIds(ids: ["user-256", "user-8", "user-64"]) { + id + name + } +} +``` + +Notice that Nadel will inject the `id` field per the `inputIdentifiedBy` configuration. +This is required as the result _may not be ordered_. Nadel uses the `id` field to match the +`collaboratorIds` to know which object to put where. + +And say the result is + +```json +{ + "data": { + "usersByIds": [ + { + "id": "user-256", + "name": "2^8" + }, + { + "id": "user-64", + "name": "2^6" + } + ] + } +} +``` + +Then the final response is + +```json +{ + "data": { + "issueById": { + "collaborators": [ + { + "name": "2^8" + }, + null, + { + "name": "2^6" + } + ] + } + } +} +``` + +This example can be found as a test at +`test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example.yml` + +## Dimensions + +Nadel can support hydrating multiple source objects and multiple source inputs. + +e.g. + +Here we have a schema with one or multiple source objects via the `Query.topIssue` (one) or `Query.myIssues` (many) +fields. +Then for source inputs we have either `Issue.assigneeId` (one) `Issue.collaboratorIds` (many). + +```graphql +type Query { + topIssue: Issue + myIssues(n: Int! = 10): [Issue] + + usersByIds(ids: [ID]!): [User] +} +type Issue { + assigneeId: ID + assignee: User + @hydrated( + field: "usersByIds" + arguments: [{name: "ids" value: "$source.assigneeId"}] + ) + + collaboratorIds: [ID!] + collaborators: [User] + @hydrated( + field: "usersByIds" + arguments: [{name: "ids" value: "$source.collaboratorIds"}] + ) +} ``` + +Any combination of source objects and source inputs will work i.e. all four queries below are valid + +```graphql +query OneIssueOneAssignee { + topIssue { assignee { name } } +} +query OneIssueManyCollaborators { + topIssue { collaborators { name } } +} +query ManyIssuesOneAssignee { + myIssues { assignee { name } } +} +query ManyIssuesManyCollaborators { + myIssues { collaborators { name } } +} ``` +For full examples, refer to the test files + +[spec-batch-hydration-example-many-issues-many-collaborators.yml](/test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example-many-issues-many-collaborators.yml) + +[spec-batch-hydration-example-many-issues-one-assignee.yml](/test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example-many-issues-one-assignee.yml) + +[spec-batch-hydration-example-one-issue-many-collaborators.yml](/test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example-one-issue-many-collaborators.yml) + +[spec-batch-hydration-example-one-issue-one-assignee.yml](/test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example-one-issue-one-assignee.yml) + # Index Hydration # Considerations diff --git a/test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example-many-issues-many-collaborators.yml b/test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example-many-issues-many-collaborators.yml new file mode 100644 index 000000000..e3c04b5b5 --- /dev/null +++ b/test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example-many-issues-many-collaborators.yml @@ -0,0 +1,158 @@ +name: "spec batch hydration example many issues many collaborators" +enabled: true +# language=GraphQL +overallSchema: + issues: | + type Query { + myIssues(n: Int! = 10): [Issue] + } + type Issue { + title: String + collaboratorIds: [ID!] + collaborators: [User] + @hydrated( + service: "users" + field: "usersByIds" + arguments: [{name: "ids" value: "$source.collaboratorIds"}] + inputIdentifiedBy: [{sourceId: "collaboratorIds", resultId: "id"}] + ) + } + users: | + type Query { + usersByIds(ids: [ID!]!): [User] + } + type User { + id: ID! + name: String + email: String + } +# language=GraphQL +underlyingSchema: + issues: | + type Query { + topIssue: Issue + myIssues(n: Int! = 10): [Issue] + } + type Issue { + title: String + assigneeId: ID + collaboratorIds: [ID!] + } + users: | + type Query { + usersByIds(ids: [ID!]!): [User] + } + type User { + id: ID! + name: String + email: String + } +# language=GraphQL +query: | + query { + myIssues { + title + collaborators { + name + } + } + } +variables: { } +serviceCalls: + - serviceName: "issues" + request: + # language=GraphQL + query: | + { + myIssues { + __typename__batch_hydration__collaborators: __typename + batch_hydration__collaborators__collaboratorIds: collaboratorIds + title + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "myIssues": [ + { + "__typename__batch_hydration__collaborators": "Issue", + "batch_hydration__collaborators__collaboratorIds": [ + "user-256", + "user-64" + ], + "title": "Popular" + }, + { + "__typename__batch_hydration__collaborators": "Issue", + "batch_hydration__collaborators__collaboratorIds": [ + "user-8", + "user-1024" + ], + "title": "New" + } + ] + } + } + - serviceName: "users" + request: + # language=GraphQL + query: | + { + usersByIds(ids: ["user-256", "user-64", "user-8", "user-1024"]) { + batch_hydration__collaborators__id: id + name + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "usersByIds": [ + { + "batch_hydration__collaborators__id": "user-256", + "name": "2^8" + }, + { + "batch_hydration__collaborators__id": "user-1024", + "name": "Big Thousand" + }, + { + "batch_hydration__collaborators__id": "user-64", + "name": "2^6" + } + ] + }, + "extensions": {} + } +# language=JSON +response: |- + { + "data": { + "myIssues": [ + { + "title": "Popular", + "collaborators": [ + { + "name": "2^8" + }, + { + "name": "2^6" + } + ] + }, + { + "title": "New", + "collaborators": [ + null, + { + "name": "Big Thousand" + } + ] + } + ] + }, + "extensions": {} + } diff --git a/test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example-many-issues-one-assignee.yml b/test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example-many-issues-one-assignee.yml new file mode 100644 index 000000000..6c336576e --- /dev/null +++ b/test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example-many-issues-one-assignee.yml @@ -0,0 +1,140 @@ +name: "spec batch hydration example many issues one assignee" +enabled: true +# language=GraphQL +overallSchema: + issues: | + type Query { + myIssues(n: Int! = 10): [Issue] + } + type Issue { + title: String + assigneeId: ID + assignee: User + @hydrated( + service: "users" + field: "usersByIds" + arguments: [{name: "ids" value: "$source.assigneeId"}] + inputIdentifiedBy: [{sourceId: "assigneeId", resultId: "id"}] + ) + } + users: | + type Query { + usersByIds(ids: [ID!]!): [User] + } + type User { + id: ID! + name: String + email: String + } +# language=GraphQL +underlyingSchema: + issues: | + type Query { + topIssue: Issue + myIssues(n: Int! = 10): [Issue] + } + type Issue { + title: String + assigneeId: ID + collaboratorIds: [ID!] + } + users: | + type Query { + usersByIds(ids: [ID!]!): [User] + } + type User { + id: ID! + name: String + email: String + } +# language=GraphQL +query: | + query { + myIssues { + title + assignee { + name + } + } + } +variables: { } +serviceCalls: + - serviceName: "issues" + request: + # language=GraphQL + query: | + { + myIssues { + __typename__batch_hydration__assignee: __typename + batch_hydration__assignee__assigneeId: assigneeId + title + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "myIssues": [ + { + "__typename__batch_hydration__assignee": "Issue", + "batch_hydration__assignee__assigneeId": "user-256", + "title": "Popular" + }, + { + "__typename__batch_hydration__assignee": "Issue", + "batch_hydration__assignee__assigneeId": "user-1024", + "title": "New" + } + ] + } + } + - serviceName: "users" + request: + # language=GraphQL + query: | + { + usersByIds(ids: ["user-256", "user-1024"]) { + batch_hydration__assignee__id: id + name + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "usersByIds": [ + { + "batch_hydration__assignee__id": "user-256", + "name": "2^8" + }, + { + "batch_hydration__assignee__id": "user-1024", + "name": "Big Thousand" + } + ] + }, + "extensions": {} + } +# language=JSON +response: |- + { + "data": { + "myIssues": [ + { + "title": "Popular", + "assignee": { + "name": "2^8" + } + }, + { + "title": "New", + "assignee": { + "name": "Big Thousand" + } + } + ] + }, + "extensions": {} + } diff --git a/test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example-one-issue-many-collaborators.yml b/test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example-one-issue-many-collaborators.yml new file mode 100644 index 000000000..e4461c2a2 --- /dev/null +++ b/test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example-one-issue-many-collaborators.yml @@ -0,0 +1,135 @@ +name: "spec batch hydration example one issue many collaborators" +enabled: true +# language=GraphQL +overallSchema: + issues: | + type Query { + topIssue: Issue + } + type Issue { + title: String + collaboratorIds: [ID!] + collaborators: [User] + @hydrated( + service: "users" + field: "usersByIds" + arguments: [{name: "ids" value: "$source.collaboratorIds"}] + inputIdentifiedBy: [{sourceId: "collaboratorIds", resultId: "id"}] + ) + } + users: | + type Query { + usersByIds(ids: [ID!]!): [User] + } + type User { + id: ID! + name: String + email: String + } +# language=GraphQL +underlyingSchema: + issues: | + type Query { + topIssue: Issue + myIssues(n: Int! = 10): [Issue] + } + type Issue { + title: String + assigneeId: ID + collaboratorIds: [ID!] + } + users: | + type Query { + usersByIds(ids: [ID!]!): [User] + } + type User { + id: ID! + name: String + email: String + } +# language=GraphQL +query: | + query { + topIssue { + title + collaborators { + name + } + } + } +variables: { } +serviceCalls: + - serviceName: "issues" + request: + # language=GraphQL + query: | + { + topIssue { + __typename__batch_hydration__collaborators: __typename + batch_hydration__collaborators__collaboratorIds: collaboratorIds + title + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "topIssue": { + "__typename__batch_hydration__collaborators": "Issue", + "batch_hydration__collaborators__collaboratorIds": [ + "user-256", + "user-8", + "user-64" + ], + "title": "Popular" + } + } + } + - serviceName: "users" + request: + # language=GraphQL + query: | + { + usersByIds(ids: ["user-256", "user-8", "user-64"]) { + batch_hydration__collaborators__id: id + name + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "usersByIds": [ + { + "batch_hydration__collaborators__id": "user-256", + "name": "2^8" + }, + { + "batch_hydration__collaborators__id": "user-64", + "name": "2^6" + } + ] + }, + "extensions": {} + } +# language=JSON +response: |- + { + "data": { + "topIssue": { + "title": "Popular", + "collaborators": [ + { + "name": "2^8" + }, + null, + { + "name": "2^6" + } + ] + } + }, + "extensions": {} + } diff --git a/test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example-one-issue-one-assignee.yml b/test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example-one-issue-one-assignee.yml new file mode 100644 index 000000000..633aa6bf7 --- /dev/null +++ b/test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example-one-issue-one-assignee.yml @@ -0,0 +1,121 @@ +name: "spec batch hydration example one issue one assignee" +enabled: true +# language=GraphQL +overallSchema: + issues: | + type Query { + topIssue: Issue + } + type Issue { + title: String + assigneeId: ID + assignee: User + @hydrated( + service: "users" + field: "usersByIds" + arguments: [{name: "ids" value: "$source.assigneeId"}] + inputIdentifiedBy: [{sourceId: "assigneeId", resultId: "id"}] + ) + } + users: | + type Query { + usersByIds(ids: [ID!]!): [User] + } + type User { + id: ID! + name: String + email: String + } +# language=GraphQL +underlyingSchema: + issues: | + type Query { + topIssue: Issue + myIssues(n: Int! = 10): [Issue] + } + type Issue { + title: String + assigneeId: ID + collaboratorIds: [ID!] + } + users: | + type Query { + usersByIds(ids: [ID!]!): [User] + } + type User { + id: ID! + name: String + email: String + } +# language=GraphQL +query: | + query { + topIssue { + title + assignee { + name + } + } + } +variables: { } +serviceCalls: + - serviceName: "issues" + request: + # language=GraphQL + query: | + { + topIssue { + __typename__batch_hydration__assignee: __typename + batch_hydration__assignee__assigneeId: assigneeId + title + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "topIssue": { + "__typename__batch_hydration__assignee": "Issue", + "batch_hydration__assignee__assigneeId": "user-8", + "title": "Popular" + } + } + } + - serviceName: "users" + request: + # language=GraphQL + query: | + { + usersByIds(ids: ["user-8"]) { + batch_hydration__assignee__id: id + name + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "usersByIds": [ + { + "batch_hydration__assignee__id": "user-8", + "name": "2^3" + } + ] + }, + "extensions": {} + } +# language=JSON +response: |- + { + "data": { + "topIssue": { + "title": "Popular", + "assignee": { + "name": "2^3" + } + } + }, + "extensions": {} + } diff --git a/test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example.yml b/test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example.yml new file mode 100644 index 000000000..7783599fe --- /dev/null +++ b/test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example.yml @@ -0,0 +1,134 @@ +name: "spec batch hydration example" +enabled: true +# language=GraphQL +overallSchema: + issues: | + type Query { + issueById(id: ID!): Issue + } + type Issue { + id: ID! + title: String + collaboratorIds: [ID] + collaborators: [User] + @hydrated( + service: "users" + field: "usersByIds" + arguments: [{name: "ids" value: "$source.collaboratorIds"}] + inputIdentifiedBy: [ + {sourceId: "collaboratorIds" resultId: "id"} + ] + batchSize: 10 + ) + } + users: | + type Query { + usersByIds(ids: [ID!]!): [User] + } + type User { + id: ID! + name: String + email: String + } +# language=GraphQL +underlyingSchema: + issues: | + type Query { + issueById(id: ID!): Issue + } + type Issue { + id: ID! + title: String + collaboratorIds: [ID] + } + users: | + type Query { + usersByIds(ids: [ID!]!): [User] + } + type User { + id: ID! + name: String + email: String + } +# language=GraphQL +query: | + query { + issueById(id: 1) { + collaborators { + name + } + } + } +variables: { } +serviceCalls: + - serviceName: "issues" + request: + # language=GraphQL + query: | + { + issueById(id: 1) { + __typename__batch_hydration__collaborators: __typename + batch_hydration__collaborators__collaboratorIds: collaboratorIds + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "issueById": { + "__typename__batch_hydration__collaborators": "Issue", + "batch_hydration__collaborators__collaboratorIds": [ + "user-256", + "user-8", + "user-64" + ] + } + } + } + - serviceName: "users" + request: + # language=GraphQL + query: | + { + usersByIds(ids: ["user-256", "user-8", "user-64"]) { + batch_hydration__collaborators__id: id + name + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "usersByIds": [ + { + "batch_hydration__collaborators__id": "user-256", + "name": "2^8" + }, + { + "batch_hydration__collaborators__id": "user-64", + "name": "2^6" + } + ] + }, + "extensions": {} + } +# language=JSON +response: |- + { + "data": { + "issueById": { + "collaborators": [ + { + "name": "2^8" + }, + null, + { + "name": "2^6" + } + ] + } + }, + "extensions": {} + } From e78b583332d53db6eb49991edbd8ebb98c4fe7fc Mon Sep 17 00:00:00 2001 From: Franklin Wang Date: Wed, 20 Dec 2023 16:49:48 +1300 Subject: [PATCH 03/10] Use list and link other test file --- docs/spec/Hydration.MD | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/docs/spec/Hydration.MD b/docs/spec/Hydration.MD index a10f8a2ca..797d8a071 100644 --- a/docs/spec/Hydration.MD +++ b/docs/spec/Hydration.MD @@ -284,8 +284,8 @@ Then the final response is } ``` -This example can be found as a test at -`test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example.yml` +For full example, refer to test file +[spec-batch-hydration-example.yml](/test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example.yml) ## Dimensions @@ -340,13 +340,10 @@ query ManyIssuesManyCollaborators { For full examples, refer to the test files -[spec-batch-hydration-example-many-issues-many-collaborators.yml](/test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example-many-issues-many-collaborators.yml) - -[spec-batch-hydration-example-many-issues-one-assignee.yml](/test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example-many-issues-one-assignee.yml) - -[spec-batch-hydration-example-one-issue-many-collaborators.yml](/test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example-one-issue-many-collaborators.yml) - -[spec-batch-hydration-example-one-issue-one-assignee.yml](/test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example-one-issue-one-assignee.yml) +* [spec-batch-hydration-example-many-issues-many-collaborators.yml](/test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example-many-issues-many-collaborators.yml) +* [spec-batch-hydration-example-many-issues-one-assignee.yml](/test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example-many-issues-one-assignee.yml) +* [spec-batch-hydration-example-one-issue-many-collaborators.yml](/test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example-one-issue-many-collaborators.yml) +* [spec-batch-hydration-example-one-issue-one-assignee.yml](/test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example-one-issue-one-assignee.yml) # Index Hydration From 7ee45fcd593ae240d70c9efa3ab34e7677aa2eda Mon Sep 17 00:00:00 2001 From: Franklin Wang Date: Wed, 20 Dec 2023 17:02:21 +1300 Subject: [PATCH 04/10] How it works example --- docs/spec/Hydration.MD | 7 +- .../spec/spec-how-it-works-example.yml | 121 ++++++++++++++++++ 2 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 test/src/test/resources/fixtures/hydration/spec/spec-how-it-works-example.yml diff --git a/docs/spec/Hydration.MD b/docs/spec/Hydration.MD index 797d8a071..d04e3880b 100644 --- a/docs/spec/Hydration.MD +++ b/docs/spec/Hydration.MD @@ -27,8 +27,8 @@ type User { } ``` -Where the `assignee` field is the hydrated field, and does not exist in any service. -It's an artificial field made up by Nadel that fetches data from `userById` once the issue is loaded. +Where the `Issue.assignee` field is the hydrated field, and does not exist in any service. +It's an artificial field made up by Nadel that is backed by the `userById` field. # Terminology @@ -130,6 +130,9 @@ Then Nadel will insert that into the original response } ``` +For full example, refer to test file +[spec-how-it-works-example.yml](/test/src/test/resources/fixtures/hydration/spec/spec-how-it-works-example.yml) + # Directive Definition The most up-to-date definition is found in `lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt` diff --git a/test/src/test/resources/fixtures/hydration/spec/spec-how-it-works-example.yml b/test/src/test/resources/fixtures/hydration/spec/spec-how-it-works-example.yml new file mode 100644 index 000000000..24e3d3f50 --- /dev/null +++ b/test/src/test/resources/fixtures/hydration/spec/spec-how-it-works-example.yml @@ -0,0 +1,121 @@ +name: "spec how it works example" +enabled: true +# language=GraphQL +overallSchema: + issues: | + type Query { + issueById(id: ID!): Issue + } + type Issue { + id: ID! + title: String + + assigneeId: ID! + assignee: User + @hydrated( + service: "users" + field: "userById" + arguments: [{name: "id" value: "$source.assigneeId"}] + ) + } + users: | + type Query { + userById(id: ID!): User + } + type User { + id: ID! + name: String + email: String + } +# language=GraphQL +underlyingSchema: + issues: | + type Query { + issueById(id: ID!): Issue + } + type Issue { + id: ID! + title: String + + assigneeId: ID! + } + users: | + type Query { + userById(id: ID!): User + } + type User { + id: ID! + name: String + email: String + } +# language=GraphQL +query: | + query { + issueById(id: 1) { + id + title + assignee { + name + } + } + } +variables: { } +serviceCalls: + - serviceName: "issues" + request: + # language=GraphQL + query: | + { + issueById(id: 1) { + __typename__hydration__assignee: __typename + hydration__assignee__assigneeId: assigneeId + id + title + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "issueById": { + "id": "1", + "title": "Write Hydration Docs", + "__typename__hydration__assignee": "Issue", + "hydration__assignee__assigneeId": "user-256" + } + } + } + - serviceName: "users" + request: + # language=GraphQL + query: | + { + userById(id: "user-256") { + name + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "userById": { + "name": "2^8" + } + }, + "extensions": {} + } +# language=JSON +response: |- + { + "data": { + "issueById": { + "id": "1", + "title": "Write Hydration Docs", + "assignee": { + "name": "2^8" + } + } + } + } From 798a6f517b1ddddd551e10fa641bab1e545c2ed9 Mon Sep 17 00:00:00 2001 From: Franklin Wang Date: Wed, 20 Dec 2023 17:05:49 +1300 Subject: [PATCH 05/10] Add table of contents --- docs/spec/Hydration.MD | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/spec/Hydration.MD b/docs/spec/Hydration.MD index d04e3880b..ba8d70abf 100644 --- a/docs/spec/Hydration.MD +++ b/docs/spec/Hydration.MD @@ -1,3 +1,14 @@ +* [Hydration](#hydration) +* [Example](#example) +* [Terminology](#terminology) +* [How it works](#how-it-works) +* [Directive Definition](#directive-definition) +* [Batched Hydration](#batched-hydration) + * [Dimensions](#dimensions) +* [Index Hydration](#index-hydration) +* [Considerations](#considerations) + * [Abstract Types](#abstract-types) + # Hydration Hydration is the process where references to pieces of data are resolved. From 16a6c010a0dc23062bb25a07efad7f1ff87e1e3c Mon Sep 17 00:00:00 2001 From: Franklin Wang Date: Wed, 20 Dec 2023 17:09:36 +1300 Subject: [PATCH 06/10] Some more titles --- docs/spec/Hydration.MD | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/spec/Hydration.MD b/docs/spec/Hydration.MD index ba8d70abf..90210205d 100644 --- a/docs/spec/Hydration.MD +++ b/docs/spec/Hydration.MD @@ -4,8 +4,12 @@ * [How it works](#how-it-works) * [Directive Definition](#directive-definition) * [Batched Hydration](#batched-hydration) + * [Batch Size](#batch-size) * [Dimensions](#dimensions) + * [Object Matching](#object-matching) * [Index Hydration](#index-hydration) +* [Conditional Hydration](#conditional-hydration) +* [Argument Sources](#argument-sources) * [Considerations](#considerations) * [Abstract Types](#abstract-types) @@ -359,8 +363,17 @@ For full examples, refer to the test files * [spec-batch-hydration-example-one-issue-many-collaborators.yml](/test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example-one-issue-many-collaborators.yml) * [spec-batch-hydration-example-one-issue-one-assignee.yml](/test/src/test/resources/fixtures/hydration/spec/spec-batch-hydration-example-one-issue-one-assignee.yml) +## Batch Size + +## Object Matching + # Index Hydration +# Conditional Hydration + +# Argument Sources + + # Considerations ## Abstract Types From 8411c3da52d412f8a98222d17502a8908abc913c Mon Sep 17 00:00:00 2001 From: Franklin Wang Date: Wed, 20 Dec 2023 17:12:03 +1300 Subject: [PATCH 07/10] Extra header --- docs/spec/Hydration.MD | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/spec/Hydration.MD b/docs/spec/Hydration.MD index 90210205d..3ecee81b4 100644 --- a/docs/spec/Hydration.MD +++ b/docs/spec/Hydration.MD @@ -9,6 +9,7 @@ * [Object Matching](#object-matching) * [Index Hydration](#index-hydration) * [Conditional Hydration](#conditional-hydration) + * [Source Inputs](#source-inputs) * [Argument Sources](#argument-sources) * [Considerations](#considerations) * [Abstract Types](#abstract-types) @@ -371,8 +372,9 @@ For full examples, refer to the test files # Conditional Hydration -# Argument Sources +## Source Inputs +# Argument Sources # Considerations From 15875efedeac4c0d4ce016e015c71849c9eced2f Mon Sep 17 00:00:00 2001 From: Franklin Wang Date: Wed, 20 Dec 2023 17:21:05 +1300 Subject: [PATCH 08/10] Fix language --- docs/spec/Hydration.MD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/spec/Hydration.MD b/docs/spec/Hydration.MD index 3ecee81b4..01583ca18 100644 --- a/docs/spec/Hydration.MD +++ b/docs/spec/Hydration.MD @@ -76,7 +76,7 @@ Nadel will transform the `assignee` field to replace it with the source input fi i.e. Nadel will send a query like -```patch +```graphql query { issueById(id: 1) { id From 7049453e9708d742350b0194f8f81c8bebd593e8 Mon Sep 17 00:00:00 2001 From: Franklin Wang Date: Wed, 20 Dec 2023 17:22:58 +1300 Subject: [PATCH 09/10] Link NadelDirectives --- docs/spec/Hydration.MD | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/spec/Hydration.MD b/docs/spec/Hydration.MD index 01583ca18..504c52210 100644 --- a/docs/spec/Hydration.MD +++ b/docs/spec/Hydration.MD @@ -151,7 +151,8 @@ For full example, refer to test file # Directive Definition -The most up-to-date definition is found in `lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt` +The most up-to-date definition is found +in [NadelDirectives.kt](/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt) ```graphql "This allows you to hydrate new values into fields" From 7e13e7c27230ce15e97f6e9105efbac0144f12aa Mon Sep 17 00:00:00 2001 From: Franklin Wang Date: Wed, 20 Dec 2023 17:26:09 +1300 Subject: [PATCH 10/10] Update --- docs/spec/Hydration.MD | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/spec/Hydration.MD b/docs/spec/Hydration.MD index 504c52210..1dc3a41fc 100644 --- a/docs/spec/Hydration.MD +++ b/docs/spec/Hydration.MD @@ -11,6 +11,7 @@ * [Conditional Hydration](#conditional-hydration) * [Source Inputs](#source-inputs) * [Argument Sources](#argument-sources) +* [Argument Partitioning](#argument-partitioning) * [Considerations](#considerations) * [Abstract Types](#abstract-types) @@ -151,8 +152,8 @@ For full example, refer to test file # Directive Definition -The most up-to-date definition is found -in [NadelDirectives.kt](/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt) +The most up-to-date definition is found in +[NadelDirectives.kt](/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt) ```graphql "This allows you to hydrate new values into fields" @@ -234,7 +235,17 @@ query { } ``` -and response +Nadel will transform it to something like + +```graphql +query { + issueById(id: 1) { + collaboratorIds + } +} +``` + +and given a response ```json { @@ -377,6 +388,8 @@ For full examples, refer to the test files # Argument Sources +# Argument Partitioning + # Considerations ## Abstract Types