From 6233d0307bc49c394b7974ed21991f5dd3e53f56 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Thu, 22 Aug 2024 22:28:48 +0200 Subject: [PATCH 01/60] outline --- ...ode_Service_Offline_EntryField_Nullable.md | 80 +++++++++++++ ...fline_SubField_Nullable_Parent_Nullable.md | 107 ++++++++++++++++++ .../2024-08-30-hot-chocolate-14.0.0.md | 26 +++++ 3 files changed, 213 insertions(+) create mode 100644 src/HotChocolate/Fusion/test/Core.Tests/__snapshots__/TransportErrorTests.Resolve_Node_Service_Offline_EntryField_Nullable.md create mode 100644 src/HotChocolate/Fusion/test/Core.Tests/__snapshots__/TransportErrorTests.Resolve_Sequence_Node_Second_Service_Offline_SubField_Nullable_Parent_Nullable.md create mode 100644 website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md diff --git a/src/HotChocolate/Fusion/test/Core.Tests/__snapshots__/TransportErrorTests.Resolve_Node_Service_Offline_EntryField_Nullable.md b/src/HotChocolate/Fusion/test/Core.Tests/__snapshots__/TransportErrorTests.Resolve_Node_Service_Offline_EntryField_Nullable.md new file mode 100644 index 00000000000..0858279e2d4 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Core.Tests/__snapshots__/TransportErrorTests.Resolve_Node_Service_Offline_EntryField_Nullable.md @@ -0,0 +1,80 @@ +# Resolve_Node_Service_Offline_EntryField_Nullable + +## Result + +```json +{ + "errors": [ + { + "message": "Unexpected Execution Error", + "locations": [ + { + "line": 2, + "column": 3 + } + ], + "path": [ + "node" + ] + } + ], + "data": { + "node": null + } +} +``` + +## Request + +```graphql +{ + node(id: "QnJhbmQ6MQ==") { + id + ... on Brand { + name + } + } +} +``` + +## QueryPlan Hash + +```text +32501CA9B2CFE1072BCA51CC37D3C65085CC9CB5 +``` + +## QueryPlan + +```json +{ + "document": "{ node(id: \u0022QnJhbmQ6MQ==\u0022) { id ... on Brand { name } } }", + "rootNode": { + "type": "Sequence", + "nodes": [ + { + "type": "ResolveNode", + "selectionId": 0, + "responseName": "node", + "branches": [ + { + "type": "Brand", + "node": { + "type": "Resolve", + "subgraph": "Subgraph_1", + "document": "query fetch_node_1 { node(id: \u0022QnJhbmQ6MQ==\u0022) { ... on Brand { id name __typename } } }", + "selectionSetId": 0 + } + } + ] + }, + { + "type": "Compose", + "selectionSetIds": [ + 0 + ] + } + ] + } +} +``` + diff --git a/src/HotChocolate/Fusion/test/Core.Tests/__snapshots__/TransportErrorTests.Resolve_Sequence_Node_Second_Service_Offline_SubField_Nullable_Parent_Nullable.md b/src/HotChocolate/Fusion/test/Core.Tests/__snapshots__/TransportErrorTests.Resolve_Sequence_Node_Second_Service_Offline_SubField_Nullable_Parent_Nullable.md new file mode 100644 index 00000000000..cb16b1cd5fe --- /dev/null +++ b/src/HotChocolate/Fusion/test/Core.Tests/__snapshots__/TransportErrorTests.Resolve_Sequence_Node_Second_Service_Offline_SubField_Nullable_Parent_Nullable.md @@ -0,0 +1,107 @@ +# Resolve_Sequence_Node_Second_Service_Offline_SubField_Nullable_Parent_Nullable + +## Result + +```json +{ + "errors": [ + { + "message": "Unexpected Execution Error", + "locations": [ + { + "line": 6, + "column": 7 + } + ], + "path": [ + "product", + "brand", + "name" + ] + } + ], + "data": { + "product": { + "id": "1", + "brand": { + "id": "1", + "name": null + } + } + } +} +``` + +## Request + +```graphql +{ + product { + id + brand { + id + name + } + } +} +``` + +## QueryPlan Hash + +```text +D3BBE380CDE08C00EE4F104AAD03C78AC29E4B9C +``` + +## QueryPlan + +```json +{ + "document": "{ product { id brand { id name } } }", + "rootNode": { + "type": "Sequence", + "nodes": [ + { + "type": "Resolve", + "subgraph": "Subgraph_1", + "document": "query fetch_product_1 { product { id brand { id __fusion_exports__1: id } } }", + "selectionSetId": 0, + "provides": [ + { + "variable": "__fusion_exports__1" + } + ] + }, + { + "type": "Compose", + "selectionSetIds": [ + 0 + ] + }, + { + "type": "Resolve", + "subgraph": "Subgraph_2", + "document": "query fetch_product_2($__fusion_exports__1: ID!) { node(id: $__fusion_exports__1) { ... on Brand { name } } }", + "selectionSetId": 2, + "path": [ + "node" + ], + "requires": [ + { + "variable": "__fusion_exports__1" + } + ] + }, + { + "type": "Compose", + "selectionSetIds": [ + 2 + ] + } + ] + }, + "state": { + "__fusion_exports__1": "Brand_id" + } +} +``` + diff --git a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md new file mode 100644 index 00000000000..fe59fa5a2d2 --- /dev/null +++ b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md @@ -0,0 +1,26 @@ +--- +path: "/blog/2024/08/30/new-in-hot-chocolate-13" +date: "2023-02-08" +title: "What's new for Hot Chocolate 13" +tags: ["hotchocolate", "graphql", "dotnet", "aspnetcore"] +featuredImage: "hot-chocolate-13-banner.png" +author: Michael Staib +authorUrl: https://github.com/michaelstaib +authorImageUrl: https://avatars1.githubusercontent.com/u/9714350?s=100&v=4 +--- + +Ease of use + +Dependency Injection + +Layering + +Pagination + +DataLoader + +Fusion + +Composite Schema Specification + +Community From d07d0ba368099844d935744d8b260eb6cfebc468 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Fri, 23 Aug 2024 00:49:40 +0200 Subject: [PATCH 02/60] wip --- .../2024-08-30-hot-chocolate-14.0.0.md | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md index fe59fa5a2d2..87462faa1d6 100644 --- a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md +++ b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md @@ -9,6 +9,37 @@ authorUrl: https://github.com/michaelstaib authorImageUrl: https://avatars1.githubusercontent.com/u/9714350?s=100&v=4 --- +We are almost ready to release a new major version of Hot Chocolate and with it come so many exiting new features. We have been working on this release for quite some time and we are very excited to share it with you. In this blog post we will give you a sneak peak of what you can expect in Hot Chocolate 14. + +In this post I will be focusing on Hot Chocolate server but we have also been busy working on Hot Chocolate Fusion and the Composite Schema Specification. We will be releasing more information on these projects in the coming weeks. + +## Ease of use + +We have been working on making Hot Chocolate easier to use and more intuitive. We have added a lot of new features that will make your life easier when working with Hot Chocolate. This will be apparent right out of the gate when you start using Hot Chocolate 14. One major area where you can see that is with dependency injection. Hot Chocolate 13 was super flexible in this are and allowed you to configure which dependencies could be used by the GraphQL execution engine with multiple resolvers at the same time and for which services the execution engine has to synchronize. This was a powerful feature but also a bit complex to use, especially when we throw DataLoader into the mix. + +You either ended up with long configuration code that in essence re-declared all dependencies or you would end up with ver busy looking resolvers. + +With Hot Chocolate 14 we have thrown all of this out and instead have put the DI on auto-pilot. When you write your resolvers you now can simply inject the services without telling Hot Chocolate that they are services. + +```csharp +public static IQueryable GetSessions( + ApplicationDbContext context) + => context.Sessions.OrderBy(s => s.Title); +``` + +This leads to clearer code that is more understandable and easier to maintain. The above resolver for instance injects the `ApplicationDbContext`. There is no need to tell Hot Chocolate that this is a service or what characteristics this services has, it will just work. + + + + + + + +When I talk about ease of use here I am also including security into that. GraphQL security was a major struggle for many developers and with vers + + +Security + Ease of use Dependency Injection From 6aa9060e2e0237b896333c101c49e70ea176adf2 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Fri, 23 Aug 2024 00:50:03 +0200 Subject: [PATCH 03/60] wip --- .../2024-08-30-hot-chocolate-14.0.0.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md index 87462faa1d6..bcab06481bf 100644 --- a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md +++ b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md @@ -55,3 +55,5 @@ Fusion Composite Schema Specification Community + +Source Generators From 2f24242d1ed5c9c4e06c69fd7ad4c4582332c522 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Fri, 23 Aug 2024 15:19:42 +0200 Subject: [PATCH 04/60] fixed --- .../2024-08-30-hot-chocolate-14.0.0.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md index bcab06481bf..c64bd2f455e 100644 --- a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md +++ b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md @@ -9,17 +9,17 @@ authorUrl: https://github.com/michaelstaib authorImageUrl: https://avatars1.githubusercontent.com/u/9714350?s=100&v=4 --- -We are almost ready to release a new major version of Hot Chocolate and with it come so many exiting new features. We have been working on this release for quite some time and we are very excited to share it with you. In this blog post we will give you a sneak peak of what you can expect in Hot Chocolate 14. +We are almost ready to release a new major version of Hot Chocolate, and with it come many exciting new features. We have been working on this release for quite some time, and we are thrilled to share it with you. In this blog post, we will give you a sneak peek at what you can expect with Hot Chocolate 14. -In this post I will be focusing on Hot Chocolate server but we have also been busy working on Hot Chocolate Fusion and the Composite Schema Specification. We will be releasing more information on these projects in the coming weeks. +In this post, I will be focusing on the Hot Chocolate server, but we have also been busy working on Hot Chocolate Fusion and the Composite Schema Specification. We will be releasing more information on these projects in the coming weeks. ## Ease of use -We have been working on making Hot Chocolate easier to use and more intuitive. We have added a lot of new features that will make your life easier when working with Hot Chocolate. This will be apparent right out of the gate when you start using Hot Chocolate 14. One major area where you can see that is with dependency injection. Hot Chocolate 13 was super flexible in this are and allowed you to configure which dependencies could be used by the GraphQL execution engine with multiple resolvers at the same time and for which services the execution engine has to synchronize. This was a powerful feature but also a bit complex to use, especially when we throw DataLoader into the mix. +We have focused on making Hot Chocolate easier to use and more intuitive. To achieve this, we have added many new features that will simplify your work. This will be apparent right from the start when you begin using Hot Chocolate 14. One major area where you can see this improvement is in dependency injection. Hot Chocolate 13 was incredibly flexible in this area, allowing you to configure which dependencies could be used by the GraphQL execution engine with multiple resolvers simultaneously, and to specify which services the execution engine needed to synchronize or pool. While this was a powerful feature, it could be somewhat complex to use, especially when incorporating DataLoader into the mix. -You either ended up with long configuration code that in essence re-declared all dependencies or you would end up with ver busy looking resolvers. +You either ended up with lengthy configuration code that essentially re-declared all dependencies, or you ended up with very cluttered resolvers. -With Hot Chocolate 14 we have thrown all of this out and instead have put the DI on auto-pilot. When you write your resolvers you now can simply inject the services without telling Hot Chocolate that they are services. +With Hot Chocolate 14, we have simplified this process by putting dependency injection on auto-pilot. Now, when you write your resolvers, you can simply inject services without needing to explicitly tell Hot Chocolate that they are services. ```csharp public static IQueryable GetSessions( @@ -27,8 +27,13 @@ public static IQueryable GetSessions( => context.Sessions.OrderBy(s => s.Title); ``` -This leads to clearer code that is more understandable and easier to maintain. The above resolver for instance injects the `ApplicationDbContext`. There is no need to tell Hot Chocolate that this is a service or what characteristics this services has, it will just work. +This leads to clearer code that is more understandable and easier to maintain. The above resolver for instance injects the `ApplicationDbContext`. There is no need to tell Hot Chocolate that this is a service or what characteristics this services has, it will just work. This is because we have simplified the way Hot Chocolate interacts with the dependency injection system. +In GraphQL we in essence have two execution algorithms, the first one that is used for queries allows for parallelization to optimize data fetching. This allows us to enqueue transparently data fetching requests and execute them in parallel. The second algorithm is used for mutations and is a sequential algorithm that executes one mutation after the other. + +So, how is this related to DI? In Hot Chocolate 14 if we have an async resolver that requires services from the DI we will create a service scope around it. Ensuring that the service you use in the resolver are not used by other resolvers concurrently. Since query resolvers are per spec defined as side-effect free this is a great default behavior. + +For mutations this is very different, as we are cause with the mutation a side-effect and you might want to use for instance a shared DBContext between two mutations, or y @@ -57,3 +62,5 @@ Composite Schema Specification Community Source Generators + +IsSelected From 1f9b670feca9275b57ab7723118c00f7f7858d3a Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Fri, 23 Aug 2024 16:30:58 +0200 Subject: [PATCH 05/60] eidts --- .../2024-08-30-hot-chocolate-14.0.0.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md index c64bd2f455e..2b1ee6fdd67 100644 --- a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md +++ b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md @@ -27,13 +27,13 @@ public static IQueryable GetSessions( => context.Sessions.OrderBy(s => s.Title); ``` -This leads to clearer code that is more understandable and easier to maintain. The above resolver for instance injects the `ApplicationDbContext`. There is no need to tell Hot Chocolate that this is a service or what characteristics this services has, it will just work. This is because we have simplified the way Hot Chocolate interacts with the dependency injection system. +This leads to clearer code that is more understandable and easier to maintain. For instance, the resolver above injects the ApplicationDbContext. There is no need to tell Hot Chocolate that this is a service or what characteristics this service has; it will just work. This is because we have simplified the way Hot Chocolate interacts with the dependency injection system. -In GraphQL we in essence have two execution algorithms, the first one that is used for queries allows for parallelization to optimize data fetching. This allows us to enqueue transparently data fetching requests and execute them in parallel. The second algorithm is used for mutations and is a sequential algorithm that executes one mutation after the other. +In GraphQL, we essentially have two execution algorithms. The first, used for queries, allows for parallelization to optimize data fetching. This enables us to enqueue data fetching requests transparently and execute them in parallel. The second algorithm, used for mutations, is a sequential algorithm that executes one mutation after another. -So, how is this related to DI? In Hot Chocolate 14 if we have an async resolver that requires services from the DI we will create a service scope around it. Ensuring that the service you use in the resolver are not used by other resolvers concurrently. Since query resolvers are per spec defined as side-effect free this is a great default behavior. +So, how is this related to DI? In Hot Chocolate 14, if we have an async resolver that requires services from the DI, we create a service scope around it, ensuring that the services you use in the resolver are not used concurrently by other resolvers. Since query resolvers are, by specification, defined as side-effect-free, this is an excellent default behavior. -For mutations this is very different, as we are cause with the mutation a side-effect and you might want to use for instance a shared DBContext between two mutations, or y +For mutations, the situation is different, as mutations inherently cause side effects. For instance, you might want to use a shared DbContext between two mutations, or y From 2b41440e37987dff06ed467a9a38fb5811257c12 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Fri, 23 Aug 2024 16:42:07 +0200 Subject: [PATCH 06/60] eidts --- .../2024-08-30-hot-chocolate-14.0.0.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md index 2b1ee6fdd67..567e53e0c05 100644 --- a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md +++ b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md @@ -27,20 +27,27 @@ public static IQueryable GetSessions( => context.Sessions.OrderBy(s => s.Title); ``` -This leads to clearer code that is more understandable and easier to maintain. For instance, the resolver above injects the ApplicationDbContext. There is no need to tell Hot Chocolate that this is a service or what characteristics this service has; it will just work. This is because we have simplified the way Hot Chocolate interacts with the dependency injection system. +This leads to clearer code that is more understandable and easier to maintain. For instance, the resolver above injects the `ApplicationDbContext`. There is no need to tell Hot Chocolate that this is a service or what characteristics this service has; it will just work. This is because we have simplified the way Hot Chocolate interacts with the dependency injection system. In GraphQL, we essentially have two execution algorithms. The first, used for queries, allows for parallelization to optimize data fetching. This enables us to enqueue data fetching requests transparently and execute them in parallel. The second algorithm, used for mutations, is a sequential algorithm that executes one mutation after another. -So, how is this related to DI? In Hot Chocolate 14, if we have an async resolver that requires services from the DI, we create a service scope around it, ensuring that the services you use in the resolver are not used concurrently by other resolvers. Since query resolvers are, by specification, defined as side-effect-free, this is an excellent default behavior. +So, how is this related to DI? In Hot Chocolate 14, if we have an async resolver that requires services from the DI, we create a service scope around it, ensuring that the services you use in the resolver are not used concurrently by other resolvers. Since query resolvers are, by specification, defined as side-effect-free, this is an excellent default behavior where you as the developer can just focus on writing code without concerning your self with concurrency. -For mutations, the situation is different, as mutations inherently cause side effects. For instance, you might want to use a shared DbContext between two mutations, or y +For mutations, the situation is different, as mutations inherently cause side effects. For instance, you might want to use a shared DbContext between two mutations. When execution mutations Hot Chocolate will use the default request scope as its guaranteed that when a mutation is execution only this mutation resolver is really running for the operation that is being executed. +So, the new default execution behavior is much more opinionated but leads to an easier default experience. However, we recognize that there are reasons to maybe use the request DI scope everywhere and you can, as you can configure the default DI scope behavior with the default schema options. +EXAMPLE. +Also, you can override the default, whatever your default may be on a per resolver basis. +EXAMPLE. +## Pagination -When I talk about ease of use here I am also including security into that. GraphQL security was a major struggle for many developers and with vers +## DataLoader + +## Projections Security @@ -51,8 +58,6 @@ Dependency Injection Layering -Pagination - DataLoader Fusion From fd40c4b4f45d701e2fc4011daa6a9810f55671a0 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Fri, 23 Aug 2024 21:08:27 +0200 Subject: [PATCH 07/60] edits --- .../2024-08-30-hot-chocolate-14.0.0.md | 175 +++++++++++++++++- 1 file changed, 173 insertions(+), 2 deletions(-) diff --git a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md index 567e53e0c05..b209a51eca7 100644 --- a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md +++ b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md @@ -43,19 +43,190 @@ Also, you can override the default, whatever your default may be on a per resolv EXAMPLE. +## Query Inspection + +## Query Errors + ## Pagination +// talk about the non-layered paging providers +// sorting expression interception +// default sorting +// benefits of keyset pagination + +```csharp +public sealed class BrandService(CatalogContext context) +{ + public async Task> GetBrandsAsync( + PagingArguments args, + CancellationToken ct = default) + => await context.Brands + .AsNoTracking() + .OrderBy(t => t.Name) + .ThenBy(t => t.Id) + .ToPageAsync(args, ct); +} +``` + +```csharp +public sealed class ProductDataLoader +{ + [DataLoader] + public static async Task>> GetProductsByBrandIdAsync( + IReadOnlyList keys, + PagingArguments pagingArguments, + CatalogContext context, + CancellationToken ct) + => await context.Products + .AsNoTracking() + .Where(p => keys.Contains(p.BrandId)) + .OrderBy(p => p.Name).ThenBy(p => p.Id) + .ToBatchPageAsync(t => t.BrandId, pagingArguments, ct); +} +``` + +```csharp +public static class BrandNode +{ + public static async Task GetBrandByIdAsync( + [Parent] Brand brand, + PagingArguments args, + BrandByIdDataLoader brandById, + CancellationToken cancellationToken) + => await brandById + .Select(selection) + .WithPagingArguments(args) + .LoadAsync(id, cancellationToken); +} +``` + ## DataLoader +```csharp +internal static class BrandDataLoader +{ + [DataLoader] + public static async Task> GetBrandByIdAsync( + IReadOnlyList ids, + CatalogContext context, + CancellationToken ct) + => await context.Brands + .AsNoTracking() + .Where(t => ids.Contains(t.Id)) + .ToDictionaryAsync(t => t.Id, ct); +} +``` + +```csharp +internal static class BrandDataLoader +{ + [DataLoader(Lookups = [nameof(CreateBrandByIdLookup)])] + public static async Task> GetBrandByIdAsync( + IReadOnlyList ids, + CatalogContext context, + CancellationToken ct) + => await context.Brands + .AsNoTracking() + .Where(t => ids.Contains(t.Id)) + .ToDictionaryAsync(t => t.Id, ct); + + private static int CreateBrandByIdLookup(Brand brand) => brand.Id; + + [DataLoader(Lookups = [nameof(CreateBrandByNameLookup)])] + public static async Task> GetBrandByNameAsync( + IReadOnlyList names, + CatalogContext context, + CancellationToken ct) + => await context.Brands + .AsNoTracking() + .Where(t => names.Contains(t.Name)) + .ToDictionaryAsync(t => t.Name, ct); + + private static string CreateBrandByNameLookup(Brand brand) => brand.Name; +} +``` + +```csharp +public sealed class BrandService(CatalogContext context) +{ + public async Task> GetBrandByIdAsync( + PagingArguments args, + BrandByIdDataLoader brandById, + CancellationToken ct = default) + => await brandById + .AsNoTracking() + .Include(b => b.Products) + .ToPageAsync(args, ct); +} +``` + +```csharp +internal static class ProductDataLoader +{ + [DataLoader(Lookups = [nameof(CreateProductByIdLookups)])] + public static async Task> GetProductByIdAsync( + => ... + + private static IEnumerable> CreateProductByIdLookups(Brand brand) + => brand.Products.Select(p => new KeyValuePair(p.Id, p)); +} +``` + ## Projections +```csharp +internal static class BrandDataLoader +{ + [DataLoader(Lookups = [nameof(CreateBrandByIdLookup)])] + public static async Task> GetBrandByIdAsync( + IReadOnlyList ids, + CatalogContext context, + ISelectorBuilder selector, + CancellationToken ct) + => await context.Brands + .AsNoTracking() + .Select(selector, key: b => b.Id) + .ToDictionaryAsync(b => b.Id, ct); +} +``` + +```csharp +public class Query +{ + public async Task GetBrandByIdAsync( + int id, + ISelection selection, + BrandByIdDataLoader brandById, + CancellationToken cancellationToken) + => await brandById + .Select(selection) + .LoadAsync(id, cancellationToken); +} +``` + +```csharp +public class Query +{ + public async Task GetBrandByIdAsync( + int id, + ISelection selection, + BrandByIdDataLoader brandById, + CancellationToken cancellationToken) + => await brandById + .Select(selection) + .Include(b => b.Products) + .LoadAsync(id, cancellationToken); +} +``` + +## Source Generators + +// from ExtendObjectType to ObjectType Security Ease of use -Dependency Injection - Layering DataLoader From 130c8bd62a43e4a8253c14d6b1b09881a00117e5 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Fri, 23 Aug 2024 21:14:11 +0200 Subject: [PATCH 08/60] edits --- .../2024-08-30-hot-chocolate-14.0.0.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md index b209a51eca7..e64631ab733 100644 --- a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md +++ b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md @@ -240,3 +240,15 @@ Community Source Generators IsSelected + +Root Fields [Query, Mutation, Subscription] + +OpenTelemetry/BCP + +GraphQL Semantic Operation Routes + +Variable and Request Batching + +Cost Analysis + +Schema Registry From 68b1176c409e30972c4123cf72fbcc8773fab3f8 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Fri, 23 Aug 2024 21:26:16 +0200 Subject: [PATCH 09/60] edits --- .../2024-08-30-hot-chocolate-14.0.0.md | 55 ++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md index e64631ab733..0e20f6226aa 100644 --- a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md +++ b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md @@ -45,7 +45,58 @@ EXAMPLE. ## Query Inspection -## Query Errors +Another area where we have made significant improvements is in query inspection. With Hot Chocolate 14, its now super simple to check what fields are being requested within the resolver without the need for complex syntax tree traversals. You now can formulate a pattern with the GraphQL selection syntax and let the executer inject you with a simple boolean that tells you if your pattern matched the user query. + +```csharp +public sealed class BrandService(CatalogContext context) +{ + public async Task GetBrandAsync( + int id, + [IsSelected("products { details }")] + bool includeProductDetails, + CancellationToken ct = default) + { + var query = context.Brands + .AsNoTracking() + .OrderBy(t => t.Name) + .ThenBy(t => t.Id); + + if(includeProductDetails) + { + query = query.Include(t => t.Products.Details); + } + + return await query.FirstOrDefaultAsync(ct); + } +} +``` + +The patterns also support inline fragments to match abstract types. But even with these complex patterns, sometimes its just great if you can write your own traversal logic but without complex trees. For this you can now simple inject the resolver context and use our fluent selector inspection API. + +```csharp +public sealed class BrandService(CatalogContext context) +{ + public async Task GetBrandAsync( + int id, + IResolverContext context, + CancellationToken ct = default) + { + var query = context.Brands + .AsNoTracking() + .OrderBy(t => t.Name) + .ThenBy(t => t.Id); + + if(context.Select("products").IsSelected(details)) + { + query = query.Include(t => t.Products.Details); + } + + return await query.FirstOrDefaultAsync(ct); + } +} +``` + +If you want to go full in and have all the power of the operation executor then you still can inject `ISelection` and traverse the compiled operation tree. ## Pagination @@ -221,6 +272,8 @@ public class Query ## Source Generators +## Query Errors + // from ExtendObjectType to ObjectType Security From 1dad892d5fe60ccdc42ba47d57487ca7bd9417b3 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Fri, 23 Aug 2024 21:27:07 +0200 Subject: [PATCH 10/60] edits --- .../2024-08-30-hot-chocolate-14.0.0.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md index 0e20f6226aa..34d653250f2 100644 --- a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md +++ b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md @@ -1,9 +1,9 @@ --- -path: "/blog/2024/08/30/new-in-hot-chocolate-13" -date: "2023-02-08" -title: "What's new for Hot Chocolate 13" +path: "/blog/2024/08/30/new-in-hot-chocolate-14" +date: "2024-08-30" +title: "What's new for Hot Chocolate 14" tags: ["hotchocolate", "graphql", "dotnet", "aspnetcore"] -featuredImage: "hot-chocolate-13-banner.png" +featuredImage: "hot-chocolate-14-banner.png" author: Michael Staib authorUrl: https://github.com/michaelstaib authorImageUrl: https://avatars1.githubusercontent.com/u/9714350?s=100&v=4 From 170ac5b0991a8a0a1886753d69f013f960e878f2 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Fri, 23 Aug 2024 21:29:02 +0200 Subject: [PATCH 11/60] edits --- .../2024-08-30-hot-chocolate-14.0.0.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md index 34d653250f2..982e7cb615a 100644 --- a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md +++ b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md @@ -225,6 +225,9 @@ internal static class ProductDataLoader ## Projections +// GreenDonut +// Isolated Code Generation for layered code. + ```csharp internal static class BrandDataLoader { From 1c83b8fc6be5c5abb52b2e9684d569349a8557b9 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Fri, 23 Aug 2024 21:29:17 +0200 Subject: [PATCH 12/60] edits --- .../2024-08-30-hot-chocolate-14.0.0.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md index 982e7cb615a..e6a7978c62f 100644 --- a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md +++ b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md @@ -308,3 +308,5 @@ Variable and Request Batching Cost Analysis Schema Registry + +DomeTrain Course From 2a8a17453f5ce5e231144bb14b165ef92d80facc Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Fri, 23 Aug 2024 21:52:55 +0200 Subject: [PATCH 13/60] edits --- .../2024-08-30-hot-chocolate-14.0.0.md | 219 +++++++++++++++++- 1 file changed, 218 insertions(+), 1 deletion(-) diff --git a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md index e6a7978c62f..649ce15450e 100644 --- a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md +++ b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md @@ -43,6 +43,8 @@ Also, you can override the default, whatever your default may be on a per resolv EXAMPLE. +TODO : Mention DataLoader DI handling!!! + ## Query Inspection Another area where we have made significant improvements is in query inspection. With Hot Chocolate 14, its now super simple to check what fields are being requested within the resolver without the need for complex syntax tree traversals. You now can formulate a pattern with the GraphQL selection syntax and let the executer inject you with a simple boolean that tells you if your pattern matched the user query. @@ -102,7 +104,7 @@ If you want to go full in and have all the power of the operation executor then // talk about the non-layered paging providers // sorting expression interception -// default sorting +// default sorting / IsDefined! // benefits of keyset pagination ```csharp @@ -153,6 +155,9 @@ public static class BrandNode ## DataLoader +/ ContextData + + ```csharp internal static class BrandDataLoader { @@ -279,7 +284,11 @@ public class Query // from ExtendObjectType to ObjectType +NodeIdSerializer (composite identifiers) + Security + - no introspection + - cost and stuff Ease of use @@ -291,6 +300,8 @@ Fusion Composite Schema Specification +Source Schema Package + Community Source Generators @@ -302,6 +313,7 @@ Root Fields [Query, Mutation, Subscription] OpenTelemetry/BCP GraphQL Semantic Operation Routes + - persisted operations and more Variable and Request Batching @@ -310,3 +322,208 @@ Cost Analysis Schema Registry DomeTrain Course + +Executable / Cosmos Driver / EF Driver + +Azure Data API Builder + +HotChocolate.Transport + +Transport Layer Changes and GraphQL over HTTP Spec + +HotChocolate 15 + + - Focus + - .NET 8 / 9 + - DataLoader + - Projections Engine + + +* Fixed Various Issues with the new Resolver Compiler by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7177 +* Detect Pure Resolver Properly with new Resolver Compiler by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7181 +* Added support for NodeResolvers with new Resolver Compiler. by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7185 +* Remove Pure Resolver Context by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7186 +* Fixed SQLite file access issue on Windows by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7183 +* Replaced FieldCoordinate with SchemaCoordinate by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7182 +* Apply Class Attributes to ObjectTypeAttribute by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7188 +* Add IBM cost analysis by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7146 +* Fixed Resolver Compiler Issue When Only Having a Node Resolver by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7195 +* Fixed Resolver Compiler Issue with Properties by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7196 +* Add Cost Defaults for Offset Pagination by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7197 +* Resolve issue where an Entity is required in subgraphs and remove non-resolvable from _entity output by @danielreynolds1 in https://github.com/ChilliCream/graphql-platform/pull/7165 +* Removed legacy code from HotChocolate.Language by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7193 +* Fixed websocket transport client by @PascalSenn in https://github.com/ChilliCream/graphql-platform/pull/7201 +* Improve CostAnalyzer Error Details by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7202 +* Set correct SDK for OpenAPI tests by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7191 +* Updated specified-by section for OneOf errors by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7184 +* Removed legacy paging support by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7206 +* Allow GraphQLConfig.Documents to be an array by @GuilhermeScotti in https://github.com/ChilliCream/graphql-platform/pull/7205 +* Moved InternalsVisibleTo to csproj files by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7210 +* Reworked the handling of async results within the resolver task by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7152 +* Made Cost Metrics Class Public by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7213 +* Aligned Builder Structure by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7219 +* Added NodeIdSerializer Service by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7220 +* Fixed `where` argument coercion by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7212 +* [Fusion] Correctly handle null items in ResolveByKey by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7217 +* Fixed Persisted Operation Naming by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7221 +* Allow multiple slicing arguments when all of them are variables. by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7222 +* Allow access from BCP services fusion to internals by @PascalSenn in https://github.com/ChilliCream/graphql-platform/pull/7225 +* Return NotAuthenticated in case we're not authenticated by @huysentruitw in https://github.com/ChilliCream/graphql-platform/pull/6908 +* Improved Execute_CoerceWhereArgument_MatchesSnapshot test by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7228 +* Added type conversion in Apollo Federation ArgumentParser#TryGetValue by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7229 +* Reorganized Paging Packages by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7230 +* Added XML Docs to Naming Conventions Interface by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7232 +* Reintegrated DBContext Factory Support by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7233 +* Resolver Task Cleanup Refinements by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7234 +* Fixes issues with scalar directives. by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7241 +* Added support for resolving nodes via an interface method by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7242 +* Added support for input types of records configured with ignored optional properties by @sunghwan2789 in https://github.com/ChilliCream/graphql-platform/pull/7239 +* Removed requirement that relative URIs start with a slash by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7243 +* Switched to central package management by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7240 +* Added AddObjectTypeExtension methods to IRequestExecutorBuilder by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7235 +* Excluded non-writable properties when inferring fields for input objects by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7236 +* Ensure that executables are disposed by the execution engine. by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7244 +* Removed reference to HotChocolate.Data.EntityFramework.Helpers by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7245 +* Added Interface Field Inheritance by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7237 +* Fixed LegacyNodeIdSerializer Registration by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7246 +* Added Fusion Source Schema Package for Hot Chocolate by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7203 +* Updated node ID serializers to allow internal IDs to be empty strings by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7249 +* Updated providers to throw an exception instead of reporting errors by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7248 +* Switched F# projects to central package management by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7250 +* Fixed Cost Analyzer Span by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7251 +* [Fusion] Add tests for various error cases by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7006 +* Reworked the GlobalIdInputValueFormatter by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7255 +* Restructured GlobalIdInputValueFormater by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7256 +* Upgraded System.Text.Json to 8.0.4 by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7253 +* Fixed Resolve Parallel SharedEntryField Tests by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7257 +* Revert "Upgraded System.Text.Json to 8.0.4 (#7253)" by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7259 +* Fixed issue with the error handling. by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7260 +* Updated IdAttributeTests to test optional arguments and input fields by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7258 +* Updated more code to use C# collection expressions (IDE0300) by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7265 +* Enabled TreatWarningsAsErrors in CI, and disabled it elsewhere by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7263 +* Make Cursor Key Serializers Configurable by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7266 +* Fixed Issue with Field Deprecations on Type Extensions by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7270 +* Removed CCN from Hot Chocolate by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7267 +* Fixed issue with the operation compiler that duplicated selections. by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7278 +* Reworked Handling of SelectionSetOptimizers by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7279 +* Updated xunit (2.4.1 -> 2.9.0) by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7275 +* Updated the CI configuration to run the jobs for draft PRs by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7277 +* Fixed StrawberryShake snapshot by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7276 +* Fixed issue with DL MaxBatchSize 0 not reusing the current batch object by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7274 +* Fixed postgres subscription issues v14 by @PascalSenn in https://github.com/ChilliCream/graphql-platform/pull/7164 +* Updated NameUtils.GetEnumValue to preserve leading underscore by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7272 +* Added more cursor key serializers by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7281 +* Updated more code to use C# collection expressions (IDE0301) by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7286 +* Fixed issue with operation optimizers in fusion. by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7291 +* Add ModifyPagingOptions by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7285 +* Updated QueryCacheMiddleware to not cache query results with errors by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7294 +* Return false when not found for TryGetValue by @7rakir in https://github.com/ChilliCream/graphql-platform/pull/7293 +* Support legacy strongly typed Ids by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7238 +* Introduce Connection Events to Interceptor by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7295 +* Added DataLoader source generator snapshot tests by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7290 +* Fixed warning CS0618 by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7299 +* Fixed warning CS0067 by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7300 +* Fixed warning CS8632 by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7297 +* Fixed F# tests by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7296 +* Removed `--property WarningLevel=0` in CI builds by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7298 +* Fix off-by-one error in middleware cleanup tasks by @leddt in https://github.com/ChilliCream/graphql-platform/pull/7301 +* Added Support colons in legacy GIDs by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7304 +* [Fusion] Add FusionGatewayBuilder.UseRequest by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7283 +* Added support for multiple Guid formats for global IDs by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7302 +* Added CursorKeySerializer registration by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7305 +* Fixed whitespace by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7318 +* Enabled ImplicitUsings for all projects by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7319 +* Fixed issue with abstract types in operation compiler. by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7321 +* Further optimize filter expressions by @nikolai-mb in https://github.com/ChilliCream/graphql-platform/pull/7311 +* Fixed SetPagingOptions by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7312 +* Added the ability to specify a Markdown language for snapshot segments by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7314 +* Added support for IDictionary & IReadOnlyDictionary to ListTypeConverter by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7309 +* Fixed WebSocket test by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7327 +* Set Markdown language for generated source snapshots by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7328 +* Updated the DateTime scalar to enforce a specific format by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7329 +* Removed some unused snapshot files by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7326 +* Fixed MongoDB test by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7330 +* Added support for error filters to Fusion by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7190 +* Adds support for more complex order by keys in pagination by @PascalSenn in https://github.com/ChilliCream/graphql-platform/pull/7331 +* Updated URLs in docs by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7334 +* Removed Neo4J docs by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7336 +* Fixed cycle detection in AnyType and ObjectToDictionaryConverter by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7262 +* Removed Neo4J references from ExcludedCover by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7333 +* Fixed Fusion compose of object beneath shared field in non-null violation case by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7271 +* Check in current Fusion error snapshots and unskip tests by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7341 +* Added test for DefaultNodeIdParser by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7342 +* Reintegrated Apollo Federation tests by @danielreynolds1 in https://github.com/ChilliCream/graphql-platform/pull/7339 +* Updated Entities in Entity Resolver error tests to implement Node interface by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7350 +* Allow to incrementally adopt new ID format in distributed system by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7343 +* Updated extending-filtering.md by @den4ik124 in https://github.com/ChilliCream/graphql-platform/pull/7335 +* Updated the Getting Started documentation by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7338 +* Centralized Nullable configuration by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7332 +* Fixed Apollo Federation v1 schema output by @danielreynolds1 in https://github.com/ChilliCream/graphql-platform/pull/7349 +* Fixed subgraph error test failure by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7351 +* Improve activity reporting by @PascalSenn in https://github.com/ChilliCream/graphql-platform/pull/7354 +* Adds logging blogpost by @PascalSenn in https://github.com/ChilliCream/graphql-platform/pull/7356 +* Added DateTime scalar breaking change to migration guide by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7359 +* Added Fusion tests for `@skip` and fixed some by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7353 +* Expose v14 migration guide in sidebar and make some amendments by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7361 +* Fixed ContinuousTask test by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7316 +* Enabled nullable reference types in test projects by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7355 +* Added inlining of total count when using cursor pagination. by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7366 +* Reworked error behavior for SingleOrDefault. by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7371 +* Changed source-generated DataLoaders to be scoped by default by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7370 +* Fixed RavenDB tests by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7364 +* Removed nodes field from fusion graph. by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7284 +* Included schema file for C# client generation (#7368) by @Socolin in https://github.com/ChilliCream/graphql-platform/pull/7369 +* Fix typo in test by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7373 +* Updated Fusion snapshot by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7374 +* Added configuration of result buffers by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7375 +* Fixed issue with composite type detection on select by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7376 +* Added DevContainer Config by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7379 +* Updated Cookie Crumble to ensure that snapshot files end with a newline by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7378 +* Added DataLoader source generator improvements by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7377 +* Disallow Generic DataLoader when using the source generator by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7381 +* Added observable DataLoader by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7382 +* Added DataLoader auto-caching by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7383 +* Fixed generator issue that affected group and cache DataLoader by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7384 +* Fixed DataLoader Generator Issues by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7385 +* Added transient DataLoader state by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7387 +* Fixed issue with GroupDataLoader using an extension method in the sourcegen by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7388 +* Added DataLoader Projections by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7389 +* Add support for bind attributes to source generator. by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7392 +* Optimize dataloader fetching for lists by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7393 +* Fixed Compiler Warning by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7394 +* Add PagingOptions.IncludeNodesField option by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7396 +* Associate subgraph transport errors with fields by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7347 + +## New Contributors +* @aokellermann made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6664 +* @timward60 made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6481 +* @tnc1997 made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6699 +* @PHILLIPS71 made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6705 +* @nikolai-mb made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6711 +* @meenakshi-dhanani made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6715 +* @jkonecki made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6780 +* @sunsided made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/5866 +* @dariuszkuc made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6864 +* @thompson-tomo made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6879 +* @cmeeren made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6883 +* @Cheesebaron made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6892 +* @whirgod made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6956 +* @DaveRMaltby made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6922 +* @SeanTAllen made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6988 +* @timerplayer made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/7031 +* @kiangkuang made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/7056 +* @Pankraty made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/7065 +* @DanielZuerrer made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/7078 +* @Enterprize1 made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/7094 +* @kasperk81 made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6730 +* @rowe-stamy made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6995 +* @McP4nth3r made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/7138 +* @danielreynolds1 made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/7140 +* @VaclavSir made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6474 +* @GuilhermeScotti made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/7205 +* @7rakir made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/7293 +* @leddt made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/7301 +* @den4ik124 made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/7335 +* @Socolin made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/7369 + +**Full Changelog**: https://github.com/ChilliCream/graphql-platform/compare/13.7.0...14.0.0-rc.0 From 40fce099aae157e24d2cef177095f7b7bf3de582 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Fri, 23 Aug 2024 21:54:20 +0200 Subject: [PATCH 14/60] edits --- .../2024-08-30-hot-chocolate-14.0.0.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md index 649ce15450e..bb149f56696 100644 --- a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md +++ b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md @@ -282,6 +282,8 @@ public class Query ## Query Errors +Interface Resolver + // from ExtendObjectType to ObjectType NodeIdSerializer (composite identifiers) @@ -339,13 +341,6 @@ HotChocolate 15 - Projections Engine -* Fixed Various Issues with the new Resolver Compiler by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7177 -* Detect Pure Resolver Properly with new Resolver Compiler by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7181 -* Added support for NodeResolvers with new Resolver Compiler. by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7185 -* Remove Pure Resolver Context by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7186 -* Fixed SQLite file access issue on Windows by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7183 -* Replaced FieldCoordinate with SchemaCoordinate by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7182 -* Apply Class Attributes to ObjectTypeAttribute by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7188 * Add IBM cost analysis by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7146 * Fixed Resolver Compiler Issue When Only Having a Node Resolver by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7195 * Fixed Resolver Compiler Issue with Properties by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7196 From c1d640aa061c6c7af1d54d4390986e83cd279027 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Fri, 23 Aug 2024 22:14:07 +0200 Subject: [PATCH 15/60] edits --- .../2024-08-30-hot-chocolate-14.0.0.md | 193 +----------------- 1 file changed, 9 insertions(+), 184 deletions(-) diff --git a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md index bb149f56696..99504bb47c9 100644 --- a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md +++ b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md @@ -106,6 +106,8 @@ If you want to go full in and have all the power of the operation executor then // sorting expression interception // default sorting / IsDefined! // benefits of keyset pagination +// cursor key serialization +// * Added inlining of total count when using cursor pagination. by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7366 ```csharp public sealed class BrandService(CatalogContext context) @@ -156,6 +158,7 @@ public static class BrandNode ## DataLoader / ContextData +/ DataLoader auto-caching ```csharp @@ -306,6 +309,9 @@ Source Schema Package Community + * Further optimize filter expressions by @nikolai-mb in https://github.com/ChilliCream/graphql-platform/pull/7311 + * DevContainer + Source Generators IsSelected @@ -331,7 +337,9 @@ Azure Data API Builder HotChocolate.Transport -Transport Layer Changes and GraphQL over HTTP Spec +Transport Layer Changes and GraphQL over HTTP Spec / Fixed NotAuthenticated + +Null Bubbling Mode and CCN HotChocolate 15 @@ -339,186 +347,3 @@ HotChocolate 15 - .NET 8 / 9 - DataLoader - Projections Engine - - -* Add IBM cost analysis by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7146 -* Fixed Resolver Compiler Issue When Only Having a Node Resolver by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7195 -* Fixed Resolver Compiler Issue with Properties by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7196 -* Add Cost Defaults for Offset Pagination by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7197 -* Resolve issue where an Entity is required in subgraphs and remove non-resolvable from _entity output by @danielreynolds1 in https://github.com/ChilliCream/graphql-platform/pull/7165 -* Removed legacy code from HotChocolate.Language by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7193 -* Fixed websocket transport client by @PascalSenn in https://github.com/ChilliCream/graphql-platform/pull/7201 -* Improve CostAnalyzer Error Details by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7202 -* Set correct SDK for OpenAPI tests by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7191 -* Updated specified-by section for OneOf errors by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7184 -* Removed legacy paging support by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7206 -* Allow GraphQLConfig.Documents to be an array by @GuilhermeScotti in https://github.com/ChilliCream/graphql-platform/pull/7205 -* Moved InternalsVisibleTo to csproj files by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7210 -* Reworked the handling of async results within the resolver task by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7152 -* Made Cost Metrics Class Public by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7213 -* Aligned Builder Structure by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7219 -* Added NodeIdSerializer Service by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7220 -* Fixed `where` argument coercion by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7212 -* [Fusion] Correctly handle null items in ResolveByKey by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7217 -* Fixed Persisted Operation Naming by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7221 -* Allow multiple slicing arguments when all of them are variables. by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7222 -* Allow access from BCP services fusion to internals by @PascalSenn in https://github.com/ChilliCream/graphql-platform/pull/7225 -* Return NotAuthenticated in case we're not authenticated by @huysentruitw in https://github.com/ChilliCream/graphql-platform/pull/6908 -* Improved Execute_CoerceWhereArgument_MatchesSnapshot test by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7228 -* Added type conversion in Apollo Federation ArgumentParser#TryGetValue by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7229 -* Reorganized Paging Packages by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7230 -* Added XML Docs to Naming Conventions Interface by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7232 -* Reintegrated DBContext Factory Support by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7233 -* Resolver Task Cleanup Refinements by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7234 -* Fixes issues with scalar directives. by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7241 -* Added support for resolving nodes via an interface method by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7242 -* Added support for input types of records configured with ignored optional properties by @sunghwan2789 in https://github.com/ChilliCream/graphql-platform/pull/7239 -* Removed requirement that relative URIs start with a slash by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7243 -* Switched to central package management by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7240 -* Added AddObjectTypeExtension methods to IRequestExecutorBuilder by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7235 -* Excluded non-writable properties when inferring fields for input objects by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7236 -* Ensure that executables are disposed by the execution engine. by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7244 -* Removed reference to HotChocolate.Data.EntityFramework.Helpers by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7245 -* Added Interface Field Inheritance by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7237 -* Fixed LegacyNodeIdSerializer Registration by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7246 -* Added Fusion Source Schema Package for Hot Chocolate by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7203 -* Updated node ID serializers to allow internal IDs to be empty strings by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7249 -* Updated providers to throw an exception instead of reporting errors by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7248 -* Switched F# projects to central package management by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7250 -* Fixed Cost Analyzer Span by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7251 -* [Fusion] Add tests for various error cases by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7006 -* Reworked the GlobalIdInputValueFormatter by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7255 -* Restructured GlobalIdInputValueFormater by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7256 -* Upgraded System.Text.Json to 8.0.4 by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7253 -* Fixed Resolve Parallel SharedEntryField Tests by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7257 -* Revert "Upgraded System.Text.Json to 8.0.4 (#7253)" by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7259 -* Fixed issue with the error handling. by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7260 -* Updated IdAttributeTests to test optional arguments and input fields by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7258 -* Updated more code to use C# collection expressions (IDE0300) by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7265 -* Enabled TreatWarningsAsErrors in CI, and disabled it elsewhere by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7263 -* Make Cursor Key Serializers Configurable by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7266 -* Fixed Issue with Field Deprecations on Type Extensions by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7270 -* Removed CCN from Hot Chocolate by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7267 -* Fixed issue with the operation compiler that duplicated selections. by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7278 -* Reworked Handling of SelectionSetOptimizers by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7279 -* Updated xunit (2.4.1 -> 2.9.0) by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7275 -* Updated the CI configuration to run the jobs for draft PRs by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7277 -* Fixed StrawberryShake snapshot by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7276 -* Fixed issue with DL MaxBatchSize 0 not reusing the current batch object by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7274 -* Fixed postgres subscription issues v14 by @PascalSenn in https://github.com/ChilliCream/graphql-platform/pull/7164 -* Updated NameUtils.GetEnumValue to preserve leading underscore by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7272 -* Added more cursor key serializers by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7281 -* Updated more code to use C# collection expressions (IDE0301) by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7286 -* Fixed issue with operation optimizers in fusion. by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7291 -* Add ModifyPagingOptions by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7285 -* Updated QueryCacheMiddleware to not cache query results with errors by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7294 -* Return false when not found for TryGetValue by @7rakir in https://github.com/ChilliCream/graphql-platform/pull/7293 -* Support legacy strongly typed Ids by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7238 -* Introduce Connection Events to Interceptor by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7295 -* Added DataLoader source generator snapshot tests by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7290 -* Fixed warning CS0618 by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7299 -* Fixed warning CS0067 by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7300 -* Fixed warning CS8632 by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7297 -* Fixed F# tests by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7296 -* Removed `--property WarningLevel=0` in CI builds by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7298 -* Fix off-by-one error in middleware cleanup tasks by @leddt in https://github.com/ChilliCream/graphql-platform/pull/7301 -* Added Support colons in legacy GIDs by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7304 -* [Fusion] Add FusionGatewayBuilder.UseRequest by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7283 -* Added support for multiple Guid formats for global IDs by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7302 -* Added CursorKeySerializer registration by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7305 -* Fixed whitespace by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7318 -* Enabled ImplicitUsings for all projects by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7319 -* Fixed issue with abstract types in operation compiler. by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7321 -* Further optimize filter expressions by @nikolai-mb in https://github.com/ChilliCream/graphql-platform/pull/7311 -* Fixed SetPagingOptions by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7312 -* Added the ability to specify a Markdown language for snapshot segments by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7314 -* Added support for IDictionary & IReadOnlyDictionary to ListTypeConverter by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7309 -* Fixed WebSocket test by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7327 -* Set Markdown language for generated source snapshots by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7328 -* Updated the DateTime scalar to enforce a specific format by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7329 -* Removed some unused snapshot files by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7326 -* Fixed MongoDB test by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7330 -* Added support for error filters to Fusion by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7190 -* Adds support for more complex order by keys in pagination by @PascalSenn in https://github.com/ChilliCream/graphql-platform/pull/7331 -* Updated URLs in docs by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7334 -* Removed Neo4J docs by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7336 -* Fixed cycle detection in AnyType and ObjectToDictionaryConverter by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7262 -* Removed Neo4J references from ExcludedCover by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7333 -* Fixed Fusion compose of object beneath shared field in non-null violation case by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7271 -* Check in current Fusion error snapshots and unskip tests by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7341 -* Added test for DefaultNodeIdParser by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7342 -* Reintegrated Apollo Federation tests by @danielreynolds1 in https://github.com/ChilliCream/graphql-platform/pull/7339 -* Updated Entities in Entity Resolver error tests to implement Node interface by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7350 -* Allow to incrementally adopt new ID format in distributed system by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7343 -* Updated extending-filtering.md by @den4ik124 in https://github.com/ChilliCream/graphql-platform/pull/7335 -* Updated the Getting Started documentation by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7338 -* Centralized Nullable configuration by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7332 -* Fixed Apollo Federation v1 schema output by @danielreynolds1 in https://github.com/ChilliCream/graphql-platform/pull/7349 -* Fixed subgraph error test failure by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7351 -* Improve activity reporting by @PascalSenn in https://github.com/ChilliCream/graphql-platform/pull/7354 -* Adds logging blogpost by @PascalSenn in https://github.com/ChilliCream/graphql-platform/pull/7356 -* Added DateTime scalar breaking change to migration guide by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7359 -* Added Fusion tests for `@skip` and fixed some by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7353 -* Expose v14 migration guide in sidebar and make some amendments by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7361 -* Fixed ContinuousTask test by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7316 -* Enabled nullable reference types in test projects by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7355 -* Added inlining of total count when using cursor pagination. by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7366 -* Reworked error behavior for SingleOrDefault. by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7371 -* Changed source-generated DataLoaders to be scoped by default by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7370 -* Fixed RavenDB tests by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7364 -* Removed nodes field from fusion graph. by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7284 -* Included schema file for C# client generation (#7368) by @Socolin in https://github.com/ChilliCream/graphql-platform/pull/7369 -* Fix typo in test by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7373 -* Updated Fusion snapshot by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7374 -* Added configuration of result buffers by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7375 -* Fixed issue with composite type detection on select by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7376 -* Added DevContainer Config by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7379 -* Updated Cookie Crumble to ensure that snapshot files end with a newline by @glen-84 in https://github.com/ChilliCream/graphql-platform/pull/7378 -* Added DataLoader source generator improvements by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7377 -* Disallow Generic DataLoader when using the source generator by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7381 -* Added observable DataLoader by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7382 -* Added DataLoader auto-caching by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7383 -* Fixed generator issue that affected group and cache DataLoader by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7384 -* Fixed DataLoader Generator Issues by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7385 -* Added transient DataLoader state by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7387 -* Fixed issue with GroupDataLoader using an extension method in the sourcegen by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7388 -* Added DataLoader Projections by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7389 -* Add support for bind attributes to source generator. by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7392 -* Optimize dataloader fetching for lists by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7393 -* Fixed Compiler Warning by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7394 -* Add PagingOptions.IncludeNodesField option by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7396 -* Associate subgraph transport errors with fields by @tobias-tengler in https://github.com/ChilliCream/graphql-platform/pull/7347 - -## New Contributors -* @aokellermann made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6664 -* @timward60 made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6481 -* @tnc1997 made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6699 -* @PHILLIPS71 made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6705 -* @nikolai-mb made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6711 -* @meenakshi-dhanani made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6715 -* @jkonecki made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6780 -* @sunsided made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/5866 -* @dariuszkuc made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6864 -* @thompson-tomo made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6879 -* @cmeeren made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6883 -* @Cheesebaron made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6892 -* @whirgod made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6956 -* @DaveRMaltby made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6922 -* @SeanTAllen made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6988 -* @timerplayer made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/7031 -* @kiangkuang made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/7056 -* @Pankraty made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/7065 -* @DanielZuerrer made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/7078 -* @Enterprize1 made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/7094 -* @kasperk81 made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6730 -* @rowe-stamy made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6995 -* @McP4nth3r made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/7138 -* @danielreynolds1 made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/7140 -* @VaclavSir made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/6474 -* @GuilhermeScotti made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/7205 -* @7rakir made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/7293 -* @leddt made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/7301 -* @den4ik124 made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/7335 -* @Socolin made their first contribution in https://github.com/ChilliCream/graphql-platform/pull/7369 - -**Full Changelog**: https://github.com/ChilliCream/graphql-platform/compare/13.7.0...14.0.0-rc.0 From d8b6ab686ffd5631f702d6364e8be1585c395a71 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Mon, 26 Aug 2024 09:52:53 +0200 Subject: [PATCH 16/60] more --- .../2024-08-30-hot-chocolate-14.0.0.md | 108 +++++++++++++++++- 1 file changed, 102 insertions(+), 6 deletions(-) diff --git a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md index 99504bb47c9..a3147866c0c 100644 --- a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md +++ b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md @@ -102,12 +102,11 @@ If you want to go full in and have all the power of the operation executor then ## Pagination -// talk about the non-layered paging providers -// sorting expression interception -// default sorting / IsDefined! -// benefits of keyset pagination -// cursor key serialization -// * Added inlining of total count when using cursor pagination. by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7366 +Pagination is a common requirement in GraphQL APIs, and Hot Chocolate 14 makes it easier than ever to implement no matter if you are building layered applications or if you are using DbContext right in your resolver. + +For layered application patterns like DDD, CQRS or Clean Architecture, we have built a brand new paging API that is completely separate from the HotChocolate GraphQL core. When building layered application pagination should be a business concern and handled in your repository or services layer. Doing so brings some unique concerns with, like how the abstraction of a page looks like. For this we have introduced a couple of new primitives like `Page`, `PagingArguments` and others that allow you to build your own paging API that fits your needs and interfaces well with GraphQL. + +We also have implemented keyset pagination for Entity Framework core which you can use in your infrastructure layer. The Entity Framework team is planing to have at some point a paging API for keyset pagination natively integrated into EF Core (LINK). Until then you can use our API to get the best performance out of your EF Core queries when using pagination. ```csharp public sealed class BrandService(CatalogContext context) @@ -123,6 +122,63 @@ public sealed class BrandService(CatalogContext context) } ``` +We are focusing on keyset pagination because its the better way to do pagination as performance is constant per progression to the pages as opposed to growing linearly with offset pagination. Apart form the better performance keyset pagination also allows to have stable pagination result even if the underlying data changes. + +We also worked hard to allow for pagination in your DataLoader. In GraphQL where nested pagination is a common thing having the capability to in essence batch multiple nested paging request in one database query is essential. + +Lets assume we have the following query and we are using a layered architecture approach. + +```graphql +query GetBrands { + brands(first: 10) { + nodes { + id + name + products(first: 10) { + nodes { + id + name + } + } + } + } +} +``` + +Let's assume we have the following two resolvers for the above query fetching the brands and the products. + +```csharp +[UsePaging] +public static async Task> GetBrandsAsync( + PagingArguments pagingArguments, + BrandService brandService, + CancellationToken cancellationToken) + => await brandService.GetBrandsAsync(pagingArguments, cancellationToken).ToConnectionAsync(); + + +[UsePaging] +public static async Task> GetProductsAsync( + [Parent] Brand brand, + PagingArguments pagingArguments, + ProductService productService, + CancellationToken cancellationToken) + => await productService.GetProductsByBrandAsync(brand.Id, pagingArguments, cancellationToken).ToConnectionAsync(); +``` + +With the above resolvers the execution engine would call first the `BrandService` and then for each `Brand` would call the `ProductService` to get the products per brand. This would lead to a N+1 query problem within our GraphQL server. To solve this we can use a `DataLoader` within our `ProductService` and batch the product requests. + +To allow this we have worked a lot on `DataLoader` and now support stateful DataLoader. This means we can pass on state to a `DataLoader` separate from the keys. If we would peek into the `ProductService` we would see something like this: + +```csharp +public async Task> GetProductsByBrandAsync( + int brandId, + PagingArguments args, + CancellationToken ct = default) + => await productsByBrandId.WithPagingArguments(args).LoadAsync(brandId, ct); +``` + +Our DataLoader in this case would look like the following: + ```csharp public sealed class ProductDataLoader { @@ -140,6 +196,46 @@ public sealed class ProductDataLoader } ``` +The `ToBatchPageAsync` extension would rewrite the paging query so that each `brandId` would be a separate page allowing us to do one database call to get in this case 10 products per brand for 10 brands. + +Important in keyset pagination is a stable order, that needs to have a unique key at the end. In the above case we order by `Name` and then chain in at the end the primary key `Id`. This ensures that the order is stable even if the `Name` is not unique. + +> If you want to read more about keyset pagination you can do so [here](LINK). + +We have brought the same capabilities also to non-layered applications where you now have for EF Core a new paging provider that allows for transparent keyset pagination. + +So if you are doing something like this in your resolver: + +```csharp +[UsePaging] +public static async IQueryable GetBrandsAsync( + PagingArguments pagingArguments, + CatalogContext context) + => context.Brands.OrderBy(t => t.Name).ThenBy(t => t.Id); +``` + +Then by default we would emulate cursor pagination by using skip/take underneath. However, as I said we have a new keyset pagination provider for EF core that you now can opt-in to. Its not the default btw as it is not compatible with SQLite. + +```csharp +builder.Services + .AddGraphQLServer() + .AddQueryType() + + +``` + + + + + + +// talk about the non-layered paging providers +// sorting expression interception +// default sorting / IsDefined! +// benefits of keyset pagination +// cursor key serialization +// * Added inlining of total count when using cursor pagination. by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7366 + ```csharp public static class BrandNode { From 31493b91d475c1959138d96774bba7b38e35892d Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Mon, 26 Aug 2024 10:17:16 +0200 Subject: [PATCH 17/60] more --- .../2024-08-30-hot-chocolate-14.0.0.md | 106 +++++++++++------- 1 file changed, 67 insertions(+), 39 deletions(-) diff --git a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md index a3147866c0c..28b0cd695c4 100644 --- a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md +++ b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md @@ -47,7 +47,7 @@ TODO : Mention DataLoader DI handling!!! ## Query Inspection -Another area where we have made significant improvements is in query inspection. With Hot Chocolate 14, its now super simple to check what fields are being requested within the resolver without the need for complex syntax tree traversals. You now can formulate a pattern with the GraphQL selection syntax and let the executer inject you with a simple boolean that tells you if your pattern matched the user query. +Another area where we have made significant improvements is in query inspection. With Hot Chocolate 14, it’s now incredibly simple to check which fields are being requested within the resolver without the need for complex syntax tree traversals. You can now formulate a pattern with the GraphQL selection syntax and let the executor inject a simple boolean that tells you if your pattern matched the user query. ```csharp public sealed class BrandService(CatalogContext context) @@ -73,7 +73,7 @@ public sealed class BrandService(CatalogContext context) } ``` -The patterns also support inline fragments to match abstract types. But even with these complex patterns, sometimes its just great if you can write your own traversal logic but without complex trees. For this you can now simple inject the resolver context and use our fluent selector inspection API. +The patterns also support inline fragments to match abstract types. However, even with these complex patterns, it can be beneficial to write your own traversal logic without dealing with complex trees. For this, you can now simply inject the resolver context and use our fluent selector inspection API. ```csharp public sealed class BrandService(CatalogContext context) @@ -98,15 +98,15 @@ public sealed class BrandService(CatalogContext context) } ``` -If you want to go full in and have all the power of the operation executor then you still can inject `ISelection` and traverse the compiled operation tree. +If you want to go all in and have the full power of the operation executor, you can still inject `ISelection` and traverse the compiled operation tree. ## Pagination -Pagination is a common requirement in GraphQL APIs, and Hot Chocolate 14 makes it easier than ever to implement no matter if you are building layered applications or if you are using DbContext right in your resolver. +Pagination is a common requirement in GraphQL APIs, and Hot Chocolate 14 makes it easier than ever to implement, no matter if you are building layered applications or using `DbContext` right in your resolver. -For layered application patterns like DDD, CQRS or Clean Architecture, we have built a brand new paging API that is completely separate from the HotChocolate GraphQL core. When building layered application pagination should be a business concern and handled in your repository or services layer. Doing so brings some unique concerns with, like how the abstraction of a page looks like. For this we have introduced a couple of new primitives like `Page`, `PagingArguments` and others that allow you to build your own paging API that fits your needs and interfaces well with GraphQL. +For layered application patterns like DDD, CQRS, or Clean Architecture, we have built a brand new paging API that is completely separate from the Hot Chocolate GraphQL core. When building layered applications, pagination should be a business concern and handled in your repository or services layer. Doing so brings some unique concerns, like how the abstraction of a page looks. For this, we have introduced a couple of new primitives like `Page`, `PagingArguments`, and others that allow you to build your own paging API that fits your needs and interfaces well with GraphQL. -We also have implemented keyset pagination for Entity Framework core which you can use in your infrastructure layer. The Entity Framework team is planing to have at some point a paging API for keyset pagination natively integrated into EF Core (LINK). Until then you can use our API to get the best performance out of your EF Core queries when using pagination. +We have also implemented keyset pagination for Entity Framework Core, which you can use in your infrastructure layer. The Entity Framework team is planning to have, at some point, a paging API for keyset pagination natively integrated into EF Core (LINK). Until then, you can use our API to get the best performance out of your EF Core queries when using pagination. ```csharp public sealed class BrandService(CatalogContext context) @@ -122,11 +122,11 @@ public sealed class BrandService(CatalogContext context) } ``` -We are focusing on keyset pagination because its the better way to do pagination as performance is constant per progression to the pages as opposed to growing linearly with offset pagination. Apart form the better performance keyset pagination also allows to have stable pagination result even if the underlying data changes. +We are focusing on keyset pagination because it’s the better way to do pagination, as performance is constant per progression to the pages, as opposed to growing linearly with offset pagination. Apart from the better performance, keyset pagination also allows for stable pagination results even if the underlying data changes. -We also worked hard to allow for pagination in your DataLoader. In GraphQL where nested pagination is a common thing having the capability to in essence batch multiple nested paging request in one database query is essential. +We also worked hard to allow for pagination in your `DataLoader`. In GraphQL, where nested pagination is a common requirement, having the capability to batch multiple nested paging requests into one database query is essential. -Lets assume we have the following query and we are using a layered architecture approach. +Let’s assume we have the following query and we are using a layered architecture approach. ```graphql query GetBrands { @@ -145,7 +145,7 @@ query GetBrands { } ``` -Let's assume we have the following two resolvers for the above query fetching the brands and the products. +Let's assume we have the following two resolvers for the above query, fetching the brands and the products. ```csharp [UsePaging] @@ -165,9 +165,9 @@ public static async Task> GetProductsAsync( => await productService.GetProductsByBrandAsync(brand.Id, pagingArguments, cancellationToken).ToConnectionAsync(); ``` -With the above resolvers the execution engine would call first the `BrandService` and then for each `Brand` would call the `ProductService` to get the products per brand. This would lead to a N+1 query problem within our GraphQL server. To solve this we can use a `DataLoader` within our `ProductService` and batch the product requests. +With the above resolvers, the execution engine would first call the `BrandService`, and then for each `Brand`, it would call the `ProductService` to get the products per brand. This would lead to an N+1 query problem within our GraphQL server. To solve this, we can use a `DataLoader` within our `ProductService` and batch the product requests. -To allow this we have worked a lot on `DataLoader` and now support stateful DataLoader. This means we can pass on state to a `DataLoader` separate from the keys. If we would peek into the `ProductService` we would see something like this: +To enable this, we have worked extensively on `DataLoader` and now support stateful `DataLoader`. This means we can pass on state to a `DataLoader` separate from the keys. If we were to peek into the `ProductService`, we would see something like this: ```csharp public async Task> GetProductsByBrandAsync( @@ -177,7 +177,7 @@ public async Task> GetProductsByBrandAsync( => await productsByBrandId.WithPagingArguments(args).LoadAsync(brandId, ct); ``` -Our DataLoader in this case would look like the following: +Our `DataLoader` in this case would look like the following: ```csharp public sealed class ProductDataLoader @@ -196,67 +196,95 @@ public sealed class ProductDataLoader } ``` -The `ToBatchPageAsync` extension would rewrite the paging query so that each `brandId` would be a separate page allowing us to do one database call to get in this case 10 products per brand for 10 brands. +The `ToBatchPageAsync` extension would rewrite the paging query so that each `brandId` would be a separate page, allowing us to make one database call to get, in this case, 10 products per brand for 10 brands. -Important in keyset pagination is a stable order, that needs to have a unique key at the end. In the above case we order by `Name` and then chain in at the end the primary key `Id`. This ensures that the order is stable even if the `Name` is not unique. +An important aspect of keyset pagination is maintaining a stable order, which requires a unique key at the end. In the above case, we order by `Name` and then chain the primary key `Id` at the end. This ensures that the order remains stable even if the `Name` is not unique. -> If you want to read more about keyset pagination you can do so [here](LINK). +> If you want to read more about keyset pagination, you can do so [here](LINK). -We have brought the same capabilities also to non-layered applications where you now have for EF Core a new paging provider that allows for transparent keyset pagination. +We have brought the same capabilities to non-layered applications, where you now have a new paging provider for EF Core that allows for transparent keyset pagination. So if you are doing something like this in your resolver: ```csharp [UsePaging] -public static async IQueryable GetBrandsAsync( +public static async IQueryable GetBrands( PagingArguments pagingArguments, CatalogContext context) => context.Brands.OrderBy(t => t.Name).ThenBy(t => t.Id); ``` -Then by default we would emulate cursor pagination by using skip/take underneath. However, as I said we have a new keyset pagination provider for EF core that you now can opt-in to. Its not the default btw as it is not compatible with SQLite. +By default, this would emulate cursor pagination by using `skip/take` underneath. However, as I mentioned, we now have a new keyset pagination provider for EF Core that you can opt-in to. It's not the default, by the way, as it is not compatible with SQLite. ```csharp builder.Services .AddGraphQLServer() - .AddQueryType() - - + ... + .AddDbContextCursorPagingProvider(); ``` +But what about user-controlled sorting? The above example would fall apart when using `[UseSorting]`, as we could not guarantee that the order is stable. To address this, we have added a couple of helpers to the `ISortingContext` that allow you to manipulate the sorting expression. +```csharp +[UsePaging] +[UseSorting] +public static async IQueryable GetBrands( + CatalogContext context, + ISortingContext sorting) +{ + // this signals that the expression was not handled within the resolver + // and the sorting middleware should take over. + sorting.Handled(false); + sorting.OnAfterSortingApplied>( + static (sortingApplied, query) => + { + if (sortingApplied && query is IOrderedQueryable ordered) + { + return ordered.ThenBy(b => b.Id); + } + return query.OrderBy(b => b.Id); + }); + return context.Brands; +} +``` -// talk about the non-layered paging providers -// sorting expression interception -// default sorting / IsDefined! -// benefits of keyset pagination -// cursor key serialization -// * Added inlining of total count when using cursor pagination. by @michaelstaib in https://github.com/ChilliCream/graphql-platform/pull/7366 +With the `ISortingContext`, we now have a hook that is executed after the user sorting has been applied. This allows us to append a stable order to the user sorting. Typically, this could be generalized and moved into a user extension method to make the resolver look cleaner. ```csharp -public static class BrandNode +[UsePaging] +[UseSorting] +public static async IQueryable GetBrands( + CatalogContext context, + ISortingContext sorting) { - public static async Task GetBrandByIdAsync( - [Parent] Brand brand, - PagingArguments args, - BrandByIdDataLoader brandById, - CancellationToken cancellationToken) - => await brandById - .Select(selection) - .WithPagingArguments(args) - .LoadAsync(id, cancellationToken); + sorting.AppendStableOrder(b => b.Id); + return context.Brands; } ``` +You even could go further and bake it into a custom middleware. + +```csharp +[UsePaging] +[UseCustomSorting] +public static async IQueryable GetBrands( + CatalogContext context, + ISortingContext sorting) + => context.Products; +``` + +With the new paging providers, we now also inline the total count into the database query that slices the page, meaning you have a single call to the database. The paging middleware will inspect what data is actually needed and either fetch the page and the total count in one database query, just the page if the total count is not needed, or just the total count if the page is not needed. All of this is built on top of the new `IsSelected` query inspection API. + ## DataLoader +Let's talk about `DataLoader`, as we already touched on how `DataLoader` is now more flexible with pagination. + / ContextData / DataLoader auto-caching - ```csharp internal static class BrandDataLoader { From 090336453256de760ef91e1a00893030ff0a5d72 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Mon, 26 Aug 2024 15:06:55 +0200 Subject: [PATCH 18/60] more --- .../2024-08-30-hot-chocolate-14.0.0.md | 183 ++++++++++++------ 1 file changed, 127 insertions(+), 56 deletions(-) diff --git a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md index 28b0cd695c4..2a96b355655 100644 --- a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md +++ b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md @@ -124,7 +124,7 @@ public sealed class BrandService(CatalogContext context) We are focusing on keyset pagination because it’s the better way to do pagination, as performance is constant per progression to the pages, as opposed to growing linearly with offset pagination. Apart from the better performance, keyset pagination also allows for stable pagination results even if the underlying data changes. -We also worked hard to allow for pagination in your `DataLoader`. In GraphQL, where nested pagination is a common requirement, having the capability to batch multiple nested paging requests into one database query is essential. +We also worked hard to allow for pagination in your DataLoader. In GraphQL, where nested pagination is a common requirement, having the capability to batch multiple nested paging requests into one database query is essential. Let’s assume we have the following query and we are using a layered architecture approach. @@ -155,7 +155,6 @@ public static async Task> GetBrandsAsync( CancellationToken cancellationToken) => await brandService.GetBrandsAsync(pagingArguments, cancellationToken).ToConnectionAsync(); - [UsePaging] public static async Task> GetProductsAsync( [Parent] Brand brand, @@ -165,9 +164,9 @@ public static async Task> GetProductsAsync( => await productService.GetProductsByBrandAsync(brand.Id, pagingArguments, cancellationToken).ToConnectionAsync(); ``` -With the above resolvers, the execution engine would first call the `BrandService`, and then for each `Brand`, it would call the `ProductService` to get the products per brand. This would lead to an N+1 query problem within our GraphQL server. To solve this, we can use a `DataLoader` within our `ProductService` and batch the product requests. +With the above resolvers, the execution engine would first call the `BrandService`, and then for each `Brand`, it would call the `ProductService` to get the products per brand. This would lead to an N+1 query problem within our GraphQL server. To solve this, we can use a DataLoader within our `ProductService` and batch the product requests. -To enable this, we have worked extensively on `DataLoader` and now support stateful `DataLoader`. This means we can pass on state to a `DataLoader` separate from the keys. If we were to peek into the `ProductService`, we would see something like this: +To enable this, we have worked extensively on DataLoader and now support stateful DataLoader. This means we can pass on state to a DataLoader separate from the keys. If we were to peek into the `ProductService`, we would see something like this: ```csharp public async Task> GetProductsByBrandAsync( @@ -177,7 +176,7 @@ public async Task> GetProductsByBrandAsync( => await productsByBrandId.WithPagingArguments(args).LoadAsync(brandId, ct); ``` -Our `DataLoader` in this case would look like the following: +Our DataLoader in this case would look like the following: ```csharp public sealed class ProductDataLoader @@ -280,10 +279,111 @@ With the new paging providers, we now also inline the total count into the datab ## DataLoader -Let's talk about `DataLoader`, as we already touched on how `DataLoader` is now more flexible with pagination. +Let's talk about DataLoader, as we already touched on how DataLoader is now more flexible with pagination, whats underneath is the new state that can be associated with DataLoader. Since DataLoader can be accessed from multiple threads concurrently and also be dispatched at multiple points during execution you have unreliable state that can be used when its there but should not make the DataLoader fail. But you also can have state that is used to branch a DataLoader and where the state is guaranteed in that branch. + +Let me give you some examples. In the following example we are fetching brands for id 1 and 2. We also provide some state when we ask for brand 2. The state is guaranteed to be there when I fetch the second brand but could bne there for the first brand, this all depends on the dispatcher. + +```csharp +var task1 = brandById.LoadAsync(1); +var task2 = brandById.SetState("some-state", "some-value").LoadAsync(2); +Task.WaitAll(task1, task2); +``` + +However, in some cases like paging I want the state to be guaranteed, in these cases we can branch a DataLoader and into this branch we pass in some data that make up the context of this branch. + +```csharp +var branch = brandById + .Branch("SomeKey") + .SetState("some-state", "some-value"); + +var task1 = branch.LoadAsync(1); +var task2 = branch.LoadAsync(2); +Task.WaitAll(task1, task2); +``` + +When we look at paging for instance than we use the paging arguments to create a branch key, so whenever you pass in the same paging arguments you will get the same branch. This allows us to batch the paging requests for the same paging arguments. + +```csharp +productsByBrandId.WithPagingArguments(args).LoadAsync(brandId, ct); +``` + +We also use the same state mechanism for DataLoader with projections. + +```csharp +public class Query +{ + public async Task GetBrandByIdAsync( + int id, + ISelection selection, + BrandByIdDataLoader brandById, + CancellationToken cancellationToken) + => await brandById + .Select(selection) + .LoadAsync(id, cancellationToken); +} +``` + +Where you can pass in an `ISelection` into the DataLoader. Any selection that structurally equivalent will point to the same DataLoader branch and be batched together. We can even add to that state thing we might want to include on top, like things we want to be guaranteed when fetching the entity. + +```csharp +public class Query +{ + public async Task GetBrandByIdAsync( + int id, + ISelection selection, + BrandByIdDataLoader brandById, + CancellationToken cancellationToken) + => await brandById + .Select(selection) + .Include(b => b.Products) + .LoadAsync(id, cancellationToken); +} +``` + +From the DataLoader side we can inject these selections and apply them to our queryable. + +```csharp +internal static class BrandDataLoader +{ + [DataLoader(Lookups = [nameof(CreateBrandByIdLookup)])] + public static async Task> GetBrandByIdAsync( + IReadOnlyList ids, + CatalogContext context, + ISelectorBuilder selector, + CancellationToken ct) + => await context.Brands + .AsNoTracking() + .Select(selector, key: b => b.Id) + .ToDictionaryAsync(b => b.Id, ct); +} +``` + +When using our DataLoader projections we are using a new projection engine that is separate from HotChocolate.Data and we are using this to redefine what projections are in Hot Chocolate. This is why `IsProjectedAttribute` is not supported. Instead we have modified the `ParentAttribute` to specify requirements. + +```csharp +public static class ProductExtensions +{ + [UsePaging] + public static async Task> GetProductsAsync( + [Parent(nameof(Brand.Id))] Brand brand, + PagingArguments pagingArguments, + ProductService productService, + CancellationToken cancellationToken) + => await productService.GetProductsByBrandAsync(brand.Id, pagingArguments, cancellationToken).ToConnectionAsync(); +} +``` + +The optional argument on the `ParentAttribute` specifies a selection set which describes the requirements for the parent object. In the case above it just defines that the brand id is required. But you could also specify that you need the ids of the products as well `Id Products { Id }`. The parent we inject is the guaranteed to have the properties filled with the required data. We evaluate this string the in source generator and if it does not match the object structure it would yield a compile time error. The whole DataLoader projections engine marked as experimental and we are looking for feedback. -/ ContextData -/ DataLoader auto-caching +Apart from this we have invested a lot into GreenDonut to make sure that you can use the source generated DataLoader without any dependencies on HotChocolate. Since DataLoader ideally are used between the business layer and the data layer and are transparent to the REST or GraphQL Layer. + +With Hot Chocolate 14 you can now add the `HotChocolate.Types.Analyzers` package and the `GreenDonut` package into your data layer. The analyzers package is just the source generator and will not be a dependency of your package. We will generate the DataLoader code plus the dependency injection code for registering your DataLoader. Yous simply need to add to your project the `DataLoaderModuleAttribute` like the following: + +```csharp +[assembly: DataLoaderModule("CatalogDataLoader")] +``` + +Lastly, on the topic of DataLoader we have made the DataLoader cache observable allowing you to share entities between DataLoader for even more efficient caching. Lets for say that we have two brand DataLoader, one fetches the entity by id and the other one by name. ```csharp internal static class BrandDataLoader @@ -297,9 +397,23 @@ internal static class BrandDataLoader .AsNoTracking() .Where(t => ids.Contains(t.Id)) .ToDictionaryAsync(t => t.Id, ct); + + [DataLoader] + public static async Task> GetBrandByNameAsync( + IReadOnlyList names, + CatalogContext context, + CancellationToken ct) + => await context.Brands + .AsNoTracking() + .Where(t => names.Contains(t.Name)) + .ToDictionaryAsync(t => t.Name, ct); + + private static string CreateBrandByNameLookup(Brand brand) => brand.Name; } ``` +If I would use these two DataLoader within a single request we would in fact fetch the same entity twice. But with Hot Chocolate 14 we can now share the entities between the two DataLoader. + ```csharp internal static class BrandDataLoader { @@ -329,6 +443,10 @@ internal static class BrandDataLoader } ``` +This can be easily done by writing to observer methods which create a new cache lookup for the same object. So at the moment one of the DataLoader is instantiated it will subscribe for `Brand` entities on the cache and create lookups. After that the DataLoader will receive real-time notifications if any other DataLoader has fetched a `Brand` entity and will be able to use the cached entity. + +Where this really shines is with optional includes, where when we use for instance the `BrandByIdDataLoader` we could do an include to load in one request already the products because we know that we will need them. + ```csharp public sealed class BrandService(CatalogContext context) { @@ -355,55 +473,8 @@ internal static class ProductDataLoader } ``` -## Projections - -// GreenDonut -// Isolated Code Generation for layered code. - -```csharp -internal static class BrandDataLoader -{ - [DataLoader(Lookups = [nameof(CreateBrandByIdLookup)])] - public static async Task> GetBrandByIdAsync( - IReadOnlyList ids, - CatalogContext context, - ISelectorBuilder selector, - CancellationToken ct) - => await context.Brands - .AsNoTracking() - .Select(selector, key: b => b.Id) - .ToDictionaryAsync(b => b.Id, ct); -} -``` - -```csharp -public class Query -{ - public async Task GetBrandByIdAsync( - int id, - ISelection selection, - BrandByIdDataLoader brandById, - CancellationToken cancellationToken) - => await brandById - .Select(selection) - .LoadAsync(id, cancellationToken); -} -``` +In this case we can subscribe to `Brand` entities on the cache and check if they have the products list populated. If they have we can create lookups for the products. -```csharp -public class Query -{ - public async Task GetBrandByIdAsync( - int id, - ISelection selection, - BrandByIdDataLoader brandById, - CancellationToken cancellationToken) - => await brandById - .Select(selection) - .Include(b => b.Products) - .LoadAsync(id, cancellationToken); -} -``` ## Source Generators From a67905a887efcc61d5303d89d1600a2496b35094 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Mon, 26 Aug 2024 15:11:43 +0200 Subject: [PATCH 19/60] more --- .../2024-08-30-hot-chocolate-14.0.0.md | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md index 2a96b355655..6571891cf5a 100644 --- a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md +++ b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md @@ -279,9 +279,9 @@ With the new paging providers, we now also inline the total count into the datab ## DataLoader -Let's talk about DataLoader, as we already touched on how DataLoader is now more flexible with pagination, whats underneath is the new state that can be associated with DataLoader. Since DataLoader can be accessed from multiple threads concurrently and also be dispatched at multiple points during execution you have unreliable state that can be used when its there but should not make the DataLoader fail. But you also can have state that is used to branch a DataLoader and where the state is guaranteed in that branch. +Let's talk about `DataLoader`. As we already touched on how `DataLoader` is now more flexible with pagination, what's underneath is the new state that can be associated with `DataLoader`. Since `DataLoader` can be accessed from multiple threads concurrently and also be dispatched at multiple points during execution, you have unreliable state that can be used when it's available but should not cause the `DataLoader` to fail. However, you can also have state that is used to branch a `DataLoader`, where the state is guaranteed within that branch. -Let me give you some examples. In the following example we are fetching brands for id 1 and 2. We also provide some state when we ask for brand 2. The state is guaranteed to be there when I fetch the second brand but could bne there for the first brand, this all depends on the dispatcher. +Let me give you some examples. In the following example, we are fetching brands for ID 1 and 2. We also provide some state when we ask for brand 2. The state is guaranteed to be there when I fetch the second brand, but it could be there for the first brand—this all depends on the dispatcher. ```csharp var task1 = brandById.LoadAsync(1); @@ -289,7 +289,7 @@ var task2 = brandById.SetState("some-state", "some-value").LoadAsync(2); Task.WaitAll(task1, task2); ``` -However, in some cases like paging I want the state to be guaranteed, in these cases we can branch a DataLoader and into this branch we pass in some data that make up the context of this branch. +However, in some cases like paging, I want the state to be guaranteed. In these cases, we can branch a `DataLoader`, and into this branch, we pass in some data that make up the context of this branch. ```csharp var branch = brandById @@ -301,13 +301,13 @@ var task2 = branch.LoadAsync(2); Task.WaitAll(task1, task2); ``` -When we look at paging for instance than we use the paging arguments to create a branch key, so whenever you pass in the same paging arguments you will get the same branch. This allows us to batch the paging requests for the same paging arguments. +When we look at paging, for instance, we use the paging arguments to create a branch key. So, whenever you pass in the same paging arguments, you will get the same branch. This allows us to batch the paging requests for the same paging arguments. ```csharp productsByBrandId.WithPagingArguments(args).LoadAsync(brandId, ct); ``` -We also use the same state mechanism for DataLoader with projections. +We also use the same state mechanism for `DataLoader` with projections. ```csharp public class Query @@ -323,7 +323,7 @@ public class Query } ``` -Where you can pass in an `ISelection` into the DataLoader. Any selection that structurally equivalent will point to the same DataLoader branch and be batched together. We can even add to that state thing we might want to include on top, like things we want to be guaranteed when fetching the entity. +You can pass an `ISelection` into the `DataLoader`. Any selection that is structurally equivalent will point to the same `DataLoader` branch and be batched together. We can even add to that state things we might want to include on top, like elements we want to be guaranteed when fetching the entity. ```csharp public class Query @@ -340,7 +340,7 @@ public class Query } ``` -From the DataLoader side we can inject these selections and apply them to our queryable. +From the `DataLoader` side, we can inject these selections and apply them to our queryable. ```csharp internal static class BrandDataLoader @@ -358,7 +358,7 @@ internal static class BrandDataLoader } ``` -When using our DataLoader projections we are using a new projection engine that is separate from HotChocolate.Data and we are using this to redefine what projections are in Hot Chocolate. This is why `IsProjectedAttribute` is not supported. Instead we have modified the `ParentAttribute` to specify requirements. +When using our `DataLoader` projections, we are utilizing a new projection engine that is separate from `HotChocolate.Data`, and we are using this to redefine what projections are in Hot Chocolate. This is why `IsProjectedAttribute` is not supported. Instead, we have modified the `ParentAttribute` to specify requirements. ```csharp public static class ProductExtensions @@ -373,11 +373,11 @@ public static class ProductExtensions } ``` -The optional argument on the `ParentAttribute` specifies a selection set which describes the requirements for the parent object. In the case above it just defines that the brand id is required. But you could also specify that you need the ids of the products as well `Id Products { Id }`. The parent we inject is the guaranteed to have the properties filled with the required data. We evaluate this string the in source generator and if it does not match the object structure it would yield a compile time error. The whole DataLoader projections engine marked as experimental and we are looking for feedback. +The optional argument on the `ParentAttribute` specifies a selection set that describes the requirements for the parent object. In the example above, it defines that the brand ID is required. However, you could also specify that you need the IDs of the products as well, such as `Id Products { Id }`. The parent we inject is guaranteed to have the properties filled with the required data. We evaluate this string in the source generator, and if it does not match the object structure, it would yield a compile-time error. The whole `DataLoader` projections engine is marked as experimental, and we are looking for feedback. -Apart from this we have invested a lot into GreenDonut to make sure that you can use the source generated DataLoader without any dependencies on HotChocolate. Since DataLoader ideally are used between the business layer and the data layer and are transparent to the REST or GraphQL Layer. +Apart from this, we have invested a lot into `GreenDonut` to ensure that you can use the source-generated `DataLoader` without any dependencies on `HotChocolate`. Since `DataLoader` is ideally used between the business layer and the data layer and is transparent to the REST or GraphQL layer. -With Hot Chocolate 14 you can now add the `HotChocolate.Types.Analyzers` package and the `GreenDonut` package into your data layer. The analyzers package is just the source generator and will not be a dependency of your package. We will generate the DataLoader code plus the dependency injection code for registering your DataLoader. Yous simply need to add to your project the `DataLoaderModuleAttribute` like the following: +With Hot Chocolate 14, you can now add the `HotChocolate.Types.Analyzers` package and the `GreenDonut` package to your data layer. The analyzers package is just the source generator and will not be a dependency of your package. We will generate the `DataLoader` code plus the dependency injection code for registering your `DataLoader`. You simply need to add the `DataLoaderModuleAttribute` to your project like the following: ```csharp [assembly: DataLoaderModule("CatalogDataLoader")] @@ -412,7 +412,7 @@ internal static class BrandDataLoader } ``` -If I would use these two DataLoader within a single request we would in fact fetch the same entity twice. But with Hot Chocolate 14 we can now share the entities between the two DataLoader. +Lastly, on the topic of `DataLoader`, we have made the `DataLoader` cache observable, allowing you to share entities between `DataLoader` instances for even more efficient caching. Let's say that we have two brand `DataLoader` instances: one fetches the entity by ID, and the other one by name. ```csharp internal static class BrandDataLoader @@ -443,9 +443,9 @@ internal static class BrandDataLoader } ``` -This can be easily done by writing to observer methods which create a new cache lookup for the same object. So at the moment one of the DataLoader is instantiated it will subscribe for `Brand` entities on the cache and create lookups. After that the DataLoader will receive real-time notifications if any other DataLoader has fetched a `Brand` entity and will be able to use the cached entity. +This can be easily done by writing two observer methods that create a new cache lookup for the same object. So, at the moment one of the `DataLoader` instances is instantiated, it will subscribe for `Brand` entities on the cache and create lookups. After that, the `DataLoader` will receive real-time notifications if any other `DataLoader` has fetched a `Brand` entity and will be able to use the cached entity. -Where this really shines is with optional includes, where when we use for instance the `BrandByIdDataLoader` we could do an include to load in one request already the products because we know that we will need them. +Where this really shines is with optional includes. For instance, when using the `BrandByIdDataLoader`, we could include the products in one request because we know that we will need them. ```csharp public sealed class BrandService(CatalogContext context) @@ -473,7 +473,7 @@ internal static class ProductDataLoader } ``` -In this case we can subscribe to `Brand` entities on the cache and check if they have the products list populated. If they have we can create lookups for the products. +In this case, we can subscribe to `Brand` entities on the cache and check if they have the products list populated. If they do, we can create lookups for the products. ## Source Generators From e56361211dd872b30402320501250f26194a938a Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Mon, 26 Aug 2024 19:48:17 +0200 Subject: [PATCH 20/60] more --- .../2024-08-30-hot-chocolate-14.0.0.md | 135 ++++++++++++++++-- 1 file changed, 124 insertions(+), 11 deletions(-) diff --git a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md index 6571891cf5a..526790a0046 100644 --- a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md +++ b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md @@ -475,13 +475,136 @@ internal static class ProductDataLoader In this case, we can subscribe to `Brand` entities on the cache and check if they have the products list populated. If they do, we can create lookups for the products. - ## Source Generators +With Hot Chocolate 14, we have started to expand our use of source-generated code. We have already used source generators in the past to automatically register types or generate the boilerplate code for `DataLoader`. With Hot Chocolate 14, we are now beginning to use source generators to generate resolvers. This feature is opt-in and, at the moment, only available for our new type extension API. + +The new `ObjectType` attribute will, over the next few versions, replace the `ExtendObjectType` attribute. The new attribute works only in combination with the source generator and combines the power of the implementation-first approach with the code-first fluent API. + +```csharp +[ObjectType] +public static partial class BrandNode +{ + static partial void Configure(IObjectTypeDescriptor descriptor) + { + descriptor.Ignore(t => t.Subscriptions); + } + + [UsePaging] + public static async Task> GetProductsAsync( + [Parent] Brand brand, + PagingArguments pagingArguments, + ProductService productService, + CancellationToken cancellationToken) + => await productService.GetProductsByBrandAsync(brand.Id, pagingArguments, cancellationToken).ToConnectionAsync(); +} +``` + +The beauty of the source generator is that, in contrast to expression compilation, the results are fully inspectable, and we can guide you by issuing compile-time warnings and errors. The source generator output can be viewed within your IDE and is debuggable. + +IMAGE + +With the new type extension API we also allow for new ways to declare root fields and colocate queries, mutations and subscriptions. + +```csharp +public static class Operations +{ + [Query] + public static async Task> GetBrandsAsync( + BrandService brandService, + PagingArguments pagingArgs, + CancellationToken ct) + => await brandService.GetBrandsAsync(pagingArgs, ct); + + [Mutation] + public static async Task CreateBrand( + CreateBrandInput input, + BrandService brandService, + CancellationToken ct) + => await brandService.CreateBrandAsync(input, ct); +} +``` + +Operation fields also can be collocated into extension types. + +```csharp +[ObjectType] +public static partial class BrandNode +{ + static partial void Configure(IObjectTypeDescriptor descriptor) + { + descriptor.Ignore(t => t.Subscriptions); + } + + [UsePaging] + public static async Task> GetProductsAsync( + [Parent] Brand brand, + PagingArguments pagingArguments, + ProductService productService, + CancellationToken cancellationToken) + => await productService.GetProductsByBrandAsync(brand.Id, pagingArguments, cancellationToken).ToConnectionAsync(); + + [Query] + public static async Task> GetBrandsAsync( + BrandService brandService, + PagingArguments pagingArgs, + CancellationToken ct) + => await brandService.GetBrandsAsync(pagingArgs, ct); + + [Mutation] + public static async Task CreateBrand( + CreateBrandInput input, + BrandService brandService, + CancellationToken ct) + => await brandService.CreateBrandAsync(input, ct); +} +``` + +This allows for more flexibility in addition to the already established `QueryTypeAttribute`, `MutationTypeAttribute`, and `SubscriptionTypeAttribute`. + +With the new version of Hot Chocolate, we are also introducing a new type extension for interfaces which allow to introduce base resolvers for common functionality. Think of this like base classes. + +```csharp +public interface IEntity +{ + [ID] int Id { get; } +} + +[InterfaceType] +public static partial class EntityInterface +{ + public static string SomeField([Parent] IEntity entity) + => ...; +} +``` + +The field definition and the resolver are inherited by all implementing object types. So, if a object type does not declare `someField`, in this case it will get the resolver inherited from the interface declaration. + +This API os also available through the fluent API, where you now have `Resolve` descriptors on interface fields. + +## Relay Support + +With Hot Chocolate 14, we have also improved our Relay support. We have made it easier to integrate aggregations to the connection type and also to add custom data to edges. You also have now more control over the shape of the connection type where you can disable the `nodes` field to either remove it as unnecessary or to replace it with a custom field. + +Apart from this we have reworked the node id serializers to be extendable and support composite identifiers. + +```csharp +EXAMPLE NODE ID SERIALIZER REGISTRATION +``` + +The new serializer is more efficient and aligns better with the ID serialization format of other GraphQL servers where the encoded id as the following format `{TypeName}:{Id}`. + +The new serializer still allows for the old format to be passed in and you can also register the legacy serializer if you prefer the way we handled it before. + + + + ## Query Errors Interface Resolver +Null Bubbling Mode and CCN + // from ExtendObjectType to ObjectType NodeIdSerializer (composite identifiers) @@ -490,12 +613,6 @@ Security - no introspection - cost and stuff -Ease of use - -Layering - -DataLoader - Fusion Composite Schema Specification @@ -507,10 +624,6 @@ Community * Further optimize filter expressions by @nikolai-mb in https://github.com/ChilliCream/graphql-platform/pull/7311 * DevContainer -Source Generators - -IsSelected - Root Fields [Query, Mutation, Subscription] OpenTelemetry/BCP From 242c34332846fbe44ac3b3bf2e86878bddbe646e Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Mon, 26 Aug 2024 20:06:19 +0200 Subject: [PATCH 21/60] more --- .../2024-08-30-hot-chocolate-14.0.0.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md index 526790a0046..9f60072ece7 100644 --- a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md +++ b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md @@ -596,8 +596,16 @@ The new serializer is more efficient and aligns better with the ID serialization The new serializer still allows for the old format to be passed in and you can also register the legacy serializer if you prefer the way we handled it before. +Relay is still the best GraphQL client library out there with other still trying to catch up by copying Relay concepts. We always have been very vocal about this and are using Relay as our first choice in customer projects. Relay is a smart GraphQL client which would immensely benefit from a feature called fragment isolation where an error in one fragment would not cause erasure of data from a colocated fragment. +The issue here is that the GraphQL specification defines that if a non-null field either returns null or throws an error, the selection-set is erased and the error is porpagated upwards. This is a problem for Relay because it would cause the erasure of data from colocated fragments. +We have been working on a solution for this problem for years now within the GraphQL foundation and Hot Chocolate implemented for the past versions a proposal called CCN (Client-Controlled-Nullability) where the user could change the nullability of fields. +However there is now a new push that we call the true-nullability proposal which allows smart clients to simply disable null bubbling. In this case a smart client could create a sort of fragment isolation on the client side by only deleting the fragment that would be affected by an error or non-null violation. + +With Hot Chocolate 14 We have decided to remove CCN and add a new HTTP header `hc-disable-null-bubbling` that allows you to disable null bubbling for a request. This is a first step towards true-nullability which would also introduce a new semantic nullability kind. + +We have prefixed the header with `hc-` to signal that this is a Hot Chocolate specific header and not to collide with the eventual GraphQL specification. ## Query Errors @@ -605,10 +613,6 @@ Interface Resolver Null Bubbling Mode and CCN -// from ExtendObjectType to ObjectType - -NodeIdSerializer (composite identifiers) - Security - no introspection - cost and stuff From fd507d58258c95d116465feb704e0c2c3fd4ad52 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Mon, 26 Aug 2024 21:01:59 +0200 Subject: [PATCH 22/60] more --- .../2024-08-30-hot-chocolate-14.0.0.md | 58 +++++++++---------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md index 9f60072ece7..3963cfe2850 100644 --- a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md +++ b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md @@ -607,53 +607,51 @@ With Hot Chocolate 14 We have decided to remove CCN and add a new HTTP header `h We have prefixed the header with `hc-` to signal that this is a Hot Chocolate specific header and not to collide with the eventual GraphQL specification. -## Query Errors - -Interface Resolver - -Null Bubbling Mode and CCN - -Security - - no introspection - - cost and stuff - -Fusion +## Data -Composite Schema Specification +- Executable / Cosmos Driver / EF Driver -Source Schema Package +## Query Errors -Community +## Transport - * Further optimize filter expressions by @nikolai-mb in https://github.com/ChilliCream/graphql-platform/pull/7311 - * DevContainer +- Semantic Routes +- Variable Batching +- Transport Layer Changes +- GraphQL over HTTP Spec +- Fixed NotAuthenticated +- Variable and Request Batching -Root Fields [Query, Mutation, Subscription] +## Security -OpenTelemetry/BCP +- Cost Analysis +- Introspection -GraphQL Semantic Operation Routes - - persisted operations and more +## Fusion -Variable and Request Batching +- Source Schema Package +- Composite Schema Specification -Cost Analysis +## Client -Schema Registry +- HotChocolate.Transport -DomeTrain Course +## GraphQL Cockpit -Executable / Cosmos Driver / EF Driver +- OpenTelemetry/BCP +- Schema Registry -Azure Data API Builder +## Community -HotChocolate.Transport + * Further optimize filter expressions by @nikolai-mb in https://github.com/ChilliCream/graphql-platform/pull/7311 + * DevContainer + * Azure Data API Builder -Transport Layer Changes and GraphQL over HTTP Spec / Fixed NotAuthenticated +## Documentation and Courses -Null Bubbling Mode and CCN + - DomeTrain Course -HotChocolate 15 +## Hot Chocolate 15 - Focus - .NET 8 / 9 From 4f6210902599797f29119c32e51cb6eda22cf4bb Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Mon, 26 Aug 2024 21:06:47 +0200 Subject: [PATCH 23/60] more --- .../2024-08-30-hot-chocolate-14.0.0.md | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md index 3963cfe2850..eef99ab62f9 100644 --- a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md +++ b/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md @@ -504,7 +504,7 @@ The beauty of the source generator is that, in contrast to expression compilatio IMAGE -With the new type extension API we also allow for new ways to declare root fields and colocate queries, mutations and subscriptions. +With the new type extension API, we also allow for new ways to declare root fields and colocate queries, mutations, and subscriptions. ```csharp public static class Operations @@ -525,7 +525,7 @@ public static class Operations } ``` -Operation fields also can be collocated into extension types. +Operation fields can also be colocated into extension types. ```csharp [ObjectType] @@ -562,7 +562,7 @@ public static partial class BrandNode This allows for more flexibility in addition to the already established `QueryTypeAttribute`, `MutationTypeAttribute`, and `SubscriptionTypeAttribute`. -With the new version of Hot Chocolate, we are also introducing a new type extension for interfaces which allow to introduce base resolvers for common functionality. Think of this like base classes. +With the new version of Hot Chocolate, we are also introducing a new type extension for interfaces, which allows you to introduce base resolvers for common functionality. Think of this like base classes. ```csharp public interface IEntity @@ -578,34 +578,35 @@ public static partial class EntityInterface } ``` -The field definition and the resolver are inherited by all implementing object types. So, if a object type does not declare `someField`, in this case it will get the resolver inherited from the interface declaration. +The field definition and the resolver are inherited by all implementing object types. So, if an object type does not declare `someField`, it will inherit the resolver from the interface declaration. -This API os also available through the fluent API, where you now have `Resolve` descriptors on interface fields. +This API is also available through the fluent API, where you now have `Resolve` descriptors on interface fields. ## Relay Support -With Hot Chocolate 14, we have also improved our Relay support. We have made it easier to integrate aggregations to the connection type and also to add custom data to edges. You also have now more control over the shape of the connection type where you can disable the `nodes` field to either remove it as unnecessary or to replace it with a custom field. +With Hot Chocolate 14, we have also improved our Relay support. We have made it easier to integrate aggregations into the connection type and to add custom data to edges. You now have more control over the shape of the connection type, allowing you to disable the `nodes` field—either to remove it as unnecessary or to replace it with a custom field. -Apart from this we have reworked the node id serializers to be extendable and support composite identifiers. +Additionally, we have reworked the node ID serializers to be extendable and support composite identifiers. ```csharp EXAMPLE NODE ID SERIALIZER REGISTRATION ``` -The new serializer is more efficient and aligns better with the ID serialization format of other GraphQL servers where the encoded id as the following format `{TypeName}:{Id}`. +The new serializer is more efficient and aligns better with the ID serialization format of other GraphQL servers, where the encoded ID has the following format: `{TypeName}:{Id}`. -The new serializer still allows for the old format to be passed in and you can also register the legacy serializer if you prefer the way we handled it before. +The new serializer still allows for the old format to be passed in, and you can also register the legacy serializer if you prefer the way we handled it before. -Relay is still the best GraphQL client library out there with other still trying to catch up by copying Relay concepts. We always have been very vocal about this and are using Relay as our first choice in customer projects. Relay is a smart GraphQL client which would immensely benefit from a feature called fragment isolation where an error in one fragment would not cause erasure of data from a colocated fragment. -The issue here is that the GraphQL specification defines that if a non-null field either returns null or throws an error, the selection-set is erased and the error is porpagated upwards. This is a problem for Relay because it would cause the erasure of data from colocated fragments. +Relay remains the best GraphQL client library, with others still trying to catch up by copying Relay concepts. We have always been very vocal about this and use Relay as our first choice in customer projects. Relay is a smart GraphQL client that would immensely benefit from a feature called fragment isolation, where an error in one fragment would not cause the erasure of data from a colocated fragment. -We have been working on a solution for this problem for years now within the GraphQL foundation and Hot Chocolate implemented for the past versions a proposal called CCN (Client-Controlled-Nullability) where the user could change the nullability of fields. +The issue here is that the GraphQL specification defines that if a non-null field either returns null or throws an error, the selection set is erased, and the error is propagated upwards. This is a problem for Relay because it would cause the erasure of data from colocated fragments. -However there is now a new push that we call the true-nullability proposal which allows smart clients to simply disable null bubbling. In this case a smart client could create a sort of fragment isolation on the client side by only deleting the fragment that would be affected by an error or non-null violation. +We have been working on a solution to this problem for years now within the GraphQL foundation, and Hot Chocolate has implemented, in past versions, a proposal called CCN (Client-Controlled-Nullability) where the user could change the nullability of fields. -With Hot Chocolate 14 We have decided to remove CCN and add a new HTTP header `hc-disable-null-bubbling` that allows you to disable null bubbling for a request. This is a first step towards true-nullability which would also introduce a new semantic nullability kind. +However, there is now a new push called the true-nullability proposal, which allows smart clients to simply disable null bubbling. In this case, a smart client could create a sort of fragment isolation on the client side by only deleting the fragment affected by an error or non-null violation. -We have prefixed the header with `hc-` to signal that this is a Hot Chocolate specific header and not to collide with the eventual GraphQL specification. +With Hot Chocolate 14, we have decided to remove CCN and add a new HTTP header `hc-disable-null-bubbling` that allows you to disable null bubbling for a request. This is a first step towards true-nullability, which would also introduce a new semantic nullability kind. + +We have prefixed the header with `hc-` to signal that this is a Hot Chocolate-specific header and to avoid collision with the eventual GraphQL specification. ## Data From 5a320b4456d8e01602fcf336996249b9e5bfa536 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Mon, 26 Aug 2024 23:09:04 +0200 Subject: [PATCH 24/60] more --- .../2024-08-30-hot-chocolate-14.md} | 85 ++++++++++++++++++- 1 file changed, 81 insertions(+), 4 deletions(-) rename website/src/blog/{2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md => 2024-08-30-hot-chocolate-14/2024-08-30-hot-chocolate-14.md} (90%) diff --git a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md b/website/src/blog/2024-08-30-hot-chocolate-14/2024-08-30-hot-chocolate-14.md similarity index 90% rename from website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md rename to website/src/blog/2024-08-30-hot-chocolate-14/2024-08-30-hot-chocolate-14.md index eef99ab62f9..55eb5722428 100644 --- a/website/src/blog/2024-08-30-hot-chocolate-14.0.0/2024-08-30-hot-chocolate-14.0.0.md +++ b/website/src/blog/2024-08-30-hot-chocolate-14/2024-08-30-hot-chocolate-14.md @@ -1,9 +1,8 @@ --- -path: "/blog/2024/08/30/new-in-hot-chocolate-14" +path: "/blog/2024/08/30/hot-chocolate-14" date: "2024-08-30" title: "What's new for Hot Chocolate 14" tags: ["hotchocolate", "graphql", "dotnet", "aspnetcore"] -featuredImage: "hot-chocolate-14-banner.png" author: Michael Staib authorUrl: https://github.com/michaelstaib authorImageUrl: https://avatars1.githubusercontent.com/u/9714350?s=100&v=4 @@ -610,9 +609,87 @@ We have prefixed the header with `hc-` to signal that this is a Hot Chocolate-sp ## Data -- Executable / Cosmos Driver / EF Driver +To make it easier to integrate new data sources into Hot Chocolate, we have made our `IExecutable` abstraction easier to implement and integrated it more fully into our resolver pipeline. This allows for easier integration of IQueryable based data drivers, like Entity Framework Core or Cosmos DB without the need of branching the whole data provider in Hot Chocolate. -## Query Errors +We have integrated the current Cosmos DB driver with the new `HotChocolate.Data.Cosmos` package and adds the new `AsCosmosExecutable` extension method to the `IQueryable` interface. This allows you to easily convert your Cosmos DB queryable into an `IExecutable` that can be used within the default Filter, Sorting and Projection middleware. + +```csharp +[QueryType] +public static class Query +{ + [UsePaging] + [UseFiltering] + [UseSorting] + public static IExecutable GetBooks(Container container) + => container + .GetItemLinqQueryable(allowSynchronousQueryExecution: true) + .AsCosmosExecutable(); +} +``` + +However, if you are already trying out EF Core 9 then you should give the new Cosmos driver within EF Core a second look as it was rewritten from the ground up and is now on par with the Cosmos DB SDK driver. + +## Query Conventions + +Our mutation conventions were received very well by the community back when we introduced them. They help to implement a complex GraphQL pattern around mutations and errors. With mutation convention we provided consistency and removed the boilerplate from your code. + +Ever since we have introduced the mutation conventions, we have been asked to provide a similar pattern for queries. While in most cases I actually would not resort to error patterns like for mutations because queries are typically side-effect-free and should be easily queried without care for complex result types. However, there are these cases where you want to return a domain error as part of your query. For these cases we did recognize the need for a consistent pattern. + +However, queries are different from mutations and there is a better pattern here than introducing payloadesque types. With our new query conventions we are embracing a union type as result type where the first entry in the union represents success and the following entries represent errors. + +```graphql +type Query { + book(id: ID!): BookResult +} + +union BookResult = Book | BookNotFound | BookAccessDenied +``` + +This allows as to query like the following: + +```graphql +query { + book(id: "1") { + ... on Book { + title + } + ... on Error { + code : __typename + message + } + ... on BookNotFound { + bookId + } + ... on BookAccessDenied { + requiredRoles + } + } +} +``` + +To opt-in to the query conventions you can chain into the configuration builder `AddQueryConventions`. + +```csharp +builder + .AddGraphQL() + .AddTypes() + .AddQueryConventions(); +``` + +This in turn allows you like with mutations to annotate errors on your resolver or use the `FieldResult` type. + +```csharp +public class Query +{ + [Error] + [Error] + public async Task GetBook( + int id, + BookService bookService, + CancellationToken ct) + => await bookService.GetBookAsync(id, ct); + +``` ## Transport From 144ff2f03be995687d4b816e1bc5a85b7fa48f8b Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Mon, 26 Aug 2024 23:42:09 +0200 Subject: [PATCH 25/60] more --- .../2024-08-30-hot-chocolate-14.md | 85 +++++++++++++++---- 1 file changed, 70 insertions(+), 15 deletions(-) diff --git a/website/src/blog/2024-08-30-hot-chocolate-14/2024-08-30-hot-chocolate-14.md b/website/src/blog/2024-08-30-hot-chocolate-14/2024-08-30-hot-chocolate-14.md index 55eb5722428..4e83e00d88c 100644 --- a/website/src/blog/2024-08-30-hot-chocolate-14/2024-08-30-hot-chocolate-14.md +++ b/website/src/blog/2024-08-30-hot-chocolate-14/2024-08-30-hot-chocolate-14.md @@ -654,7 +654,7 @@ query { title } ... on Error { - code : __typename + code: __typename message } ... on BookNotFound { @@ -693,15 +693,70 @@ public class Query ## Transport -- Semantic Routes -- Variable Batching -- Transport Layer Changes -- GraphQL over HTTP Spec -- Fixed NotAuthenticated -- Variable and Request Batching +Lets talk about the GraphQL transport layer and what has changed with Hot Chocolate 14. The GraphQL over HTTP spec is now in its final stages and we have been adopting the latest changes. This means that we no longer return status code 500 when the full result has be erased due to a non-null violation. Instead we return status code 200 and a JSON body that contains the error information and data as null. + +If you are interested into the spec, you can find the current version [here](LINK). + +We have also reintroduced the error code for not authenticated errors to make it easier for authentication flows. This was something we originally dropped in Hot Chocolate 13 but because a lot of you have struggled with this we have reintroduced it. + +Apart from these smaller bits an pieces we have completely rewritten our persisted operation aka trusted document pipeline to introduce end-2-end tracability across the whole transport layer. We have done this by implementing a feature we call semantic routes. The idea here is that each operation has a unique uri that is derived from the document has and the operation name. + +This new persisted operation transport pipeline can be mapped separatly like the following. + +```csharp +app.MapGraphQLPersistedOperations(); +``` + +By default we would map the persisted operations to `/graphql/persisted/{documentHash}/{operationName}` but you can change the root for this path. + +Now with this only the variables and extensions are posted to the server or when you are using a query you can also use a get request like the following: + +```csharp +GET /graphql/persisted/1234/GetBook?variables={id:1} +``` + +This also makes it so much easier to work with CDNs or to reroute certain operation to different servers. + +For this release we have also reimplementd our batching transport layer and support now variable batching and request batching. Variable batching is a new batching proposal we have created for the upcoming Composite Schema Specification to transparently use batching in combination with standard GraphQL queries instead of relying on special field like the `_entities` field. + +With variable batching you can batch for the same operation multiple sets of variables. + +```json +{ + "query": "query GetBooks($id: ID!) { book(id: $id) { title } }", + "variables": [{ "id": "1" }, { "id": "2" }] +} +``` + +Since a variable batch request has the same structure than a standard GraphQL request, except for the variable field which in this case is a list, we can also batch these within a batch request. + +```json +[ + { + "query": "query GetBooks($id: ID!) { book(id: $id) { title } }", + "variables": [{ "id": "1" }, { "id": "2" }] + }, + { + "query": "query GetBooks($id: ID!) { book(id: $id) { title } }", + "variables": { "id": "3" } + } +] +``` + +This new batching API with in your backend allows for new use-cases and is a great way to optimize your GraphQL server. ## Security +One of our greatest investments we have done with version 14 is into security. We have seen countless GraphQL servers with our customers and in 90% of the cases they were not configured in a secure way. This was not for the lack of functionality in Hot Chocolate but for the lack of knowledge how to configure things. Often there is also a lack of understanding why certain things are important with GraphQL. + +GraphQL as facebook created and used it was built around flexibility at dev time and persisted operations at production. This means the moment facebook deploys to production the GraphQL server becomes in essence a REST server that has now way to freely create new operations. The GraphQL server is only able to execute whatever frontend engineers used within in their frontends. On production build the operations were stripped from the frontend code and stored in an operations store. The frontend on production would send to the GraphQL server a hash instead of a full operation and the GraphQL server would only execute operations that are stored in the operations store. + +This is as of today still the best way to do GraphQL and with Banana Cake Pop you can setup a schema registry and an operation store in less than 5 minutes. Have a look [here](LINK) for more information. + +But we wanted to create a new setup for Hot Chocolate no matter if you have any clue about all of this you will end up with a secure GraphQL server. This is why we have implemented the IBM cost specification to weight your request and restrict expensive operations right from the start. With Hot Chocolate 14 you basically have two profiles to run the server, with IBM cost spec or with persisted operations. + + + - Cost Analysis - Introspection @@ -721,17 +776,17 @@ public class Query ## Community - * Further optimize filter expressions by @nikolai-mb in https://github.com/ChilliCream/graphql-platform/pull/7311 - * DevContainer - * Azure Data API Builder +- Further optimize filter expressions by @nikolai-mb in https://github.com/ChilliCream/graphql-platform/pull/7311 +- DevContainer +- Azure Data API Builder ## Documentation and Courses - - DomeTrain Course +- DomeTrain Course ## Hot Chocolate 15 - - Focus - - .NET 8 / 9 - - DataLoader - - Projections Engine +- Focus +- .NET 8 / 9 +- DataLoader +- Projections Engine From 408a4bbf5ca10ee2cab4bfd74d351612598206b6 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Tue, 27 Aug 2024 09:50:21 +0200 Subject: [PATCH 26/60] more --- .../2024-08-30-hot-chocolate-14.md | 84 +++++++++++++++++-- 1 file changed, 78 insertions(+), 6 deletions(-) diff --git a/website/src/blog/2024-08-30-hot-chocolate-14/2024-08-30-hot-chocolate-14.md b/website/src/blog/2024-08-30-hot-chocolate-14/2024-08-30-hot-chocolate-14.md index 4e83e00d88c..78daf8064ac 100644 --- a/website/src/blog/2024-08-30-hot-chocolate-14/2024-08-30-hot-chocolate-14.md +++ b/website/src/blog/2024-08-30-hot-chocolate-14/2024-08-30-hot-chocolate-14.md @@ -747,18 +747,90 @@ This new batching API with in your backend allows for new use-cases and is a gre ## Security -One of our greatest investments we have done with version 14 is into security. We have seen countless GraphQL servers with our customers and in 90% of the cases they were not configured in a secure way. This was not for the lack of functionality in Hot Chocolate but for the lack of knowledge how to configure things. Often there is also a lack of understanding why certain things are important with GraphQL. +We have seen countless GraphQL servers over the last year as part of our consulting engagements and in many cases they were not configured in a secure way. This was not for the lack of functionality in Hot Chocolate but because engineers that were transitioning to GraphQL often did not know good security practices in GraphQL. -GraphQL as facebook created and used it was built around flexibility at dev time and persisted operations at production. This means the moment facebook deploys to production the GraphQL server becomes in essence a REST server that has now way to freely create new operations. The GraphQL server is only able to execute whatever frontend engineers used within in their frontends. On production build the operations were stripped from the frontend code and stored in an operations store. The frontend on production would send to the GraphQL server a hash instead of a full operation and the GraphQL server would only execute operations that are stored in the operations store. +GraphQL as facebook created and used it was built around flexibility at dev time and persisted operations at production. This means the moment facebook deploys to production the GraphQL server becomes in essence a REST server. There is no GraphQL in production. The GraphQL server is only able to execute trusted operations that were exported from the various frontends into an operation store. The way this works is that in the build pipeline operations are stripped from the frontend code and replaced with a unique identifier. The stripped operation documents are stored in an operation store. The frontend on production would send to the GraphQL server the unique identifier instead of a full operation and the GraphQL server would only execute operations that are stored in the operations store. -This is as of today still the best way to do GraphQL and with Banana Cake Pop you can setup a schema registry and an operation store in less than 5 minutes. Have a look [here](LINK) for more information. +This is the best way to do GraphQL and gives you the best story for schema evolvability as used operations are centrally known and can be statically analyzed. It also makes sure that you know the performance characteristics and the impact of operations to your backend, With Banana Cake Pop you can setup a schema registry and an operation store in less than 5 minutes. Have a look [here](LINK) for more information. -But we wanted to create a new setup for Hot Chocolate no matter if you have any clue about all of this you will end up with a secure GraphQL server. This is why we have implemented the IBM cost specification to weight your request and restrict expensive operations right from the start. With Hot Chocolate 14 you basically have two profiles to run the server, with IBM cost spec or with persisted operations. +However, most new developers are not aware how to do this or do not understand why they should. The other problem is that there is now easy path from an open GraphQL server to a closed system once you have clients working against your API. +With Hot Chocolate 14 we wanted to make sure that your servers are secure even if you do not configure a single setting, and even if you do not know about persisted operations or you explicitly want an open GraphQL server. Going forward we have built into the core of Hot Chocolate the IBM cost specification to weight the impact of your requests and to restrict expensive operations right from the start. +When you export your schema with Hot Chocolate 14 you will see that we have added cost directives to certain fields. We estimate cost automatically so that you do not have to do this manually. You can override where we are wrong. The IBM cost spec has to weights it calculates. The type cost, which estimates the objects being produced, in essence the data cost. Secondly, it estimates the field cost, this basically is the computational cost. -- Cost Analysis -- Introspection +> With Hot Chocolate 14 we have implemented the static analysis but will add runtime analysis and result analysis as opt-ins with Hot Chocolate 15. + +The static analysis will go for maximums, which means if you say you want 50 elements in a list it will estimate 50 elements on not the actual number of elements. This makes sure that you do not overwhelm your server with a single request and have a good estimate what the request could mean to your backend. + +You can combine the cost analysis scores with rate limiting to ensure that a use stays in cost boundaries over time. + +```csharp +.UseRequest(next => +{ + var rateLimiter = new SlidingWindowRateLimiter( + new SlidingWindowRateLimiterOptions + { + PermitLimit = 10000, + Window = TimeSpan.FromHours(1), + SegmentsPerWindow = 6, // 10-minute segments + QueueProcessingOrder = QueueProcessingOrder.OldestFirst, + QueueLimit = 1, + }); + + return async context => + { + if (context.ContextData.TryGetValue(WellKnownContextData.CostMetrics, out var value) + && value is CostMetrics metrics) + { + using RateLimitLease lease = await rateLimiter.AcquireAsync( + permitCount: (int)metrics.TypeCost, + context.RequestAborted); + + if (!lease.IsAcquired) + { + context.Result = + OperationResultBuilder.New() + .AddError(ErrorBuilder.New() + .SetMessage("Rate limit exceeded.") + .SetCode("RATE_LIMIT_EXCEEDED") + .Build()) + .SetContextData( + WellKnownContextData.HttpStatusCode, + HttpStatusCode.TooManyRequests) + .Build(); + return; + } + } + + await next(context); + }; +}) +``` + +While you would need it a bit more sophisticated in production, with redis to have a distributed rate limiter, this is a good start to ensure that your server is not overwhelmed. + +With the cost spec you can also estimate a request impact without doing the actual request by sending in the header `GraphQL-Cost:validate` or if you want the request to be executed but to still the the cost even if the request is valid then you can send in the header `GraphQL-Cost:report`. + +With the IBM cost spec backed into the core its always on and this will make your GraphQL server more secure and more predictable. But this also will tell you the truth about your requests which might hurt when you migrate. + +What we also have made sure of is that when you want to migrate from an open GraphQL server to trusted documents that this can now be done in a couple of minutes by slapping Banana Cake Pop in. Over a time frame of 30,60 or 90 days the GraphQL server will report operations that were executed and will store them in the operation store. You can manually decide which queries not to take in. After that period you can switch trusted operations on and only operations tracked in the operation store will be allowed form this day forward. + +One other thing we have changed with Hot Chocolate 14 is around introspection. When we detect a production environment in ASP.NET core we will automatically disable introspection and provide a schema file on the route `/graphql?sdl` which is one time computed schema file that will server as a simple file from your server. The misunderstanding with introsection is often that people think its about hiding the schema. This is actually not the case since it quite simple to infer the schema from the request observed in a web application. The problem with introspection is that it is easy to produce very large results in your GraphQL server. When I say large than I mean 200 - 300 MB large. This depends on your schema. Most tools will work fine with a schema file which is much smaller than the introspection result and costs literally no compute and memory. You can override this behavior like the following. + +```csharp +builder + .AddGraphQLServer() + .ModifyRequestOptions(o => o.EnableIntrospection = true); +``` + +Also the schema file can be disabled like the following. + +```csharp +builder + .AddGraphQLServer() + .ModifyRequestOptions(o => o.EnableSchemaFile = false); +``` ## Fusion From 9a4e7bc191254811f3e9a3657e8f54f3c83f0453 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Tue, 27 Aug 2024 09:57:12 +0200 Subject: [PATCH 27/60] more --- .../2024-08-30-hot-chocolate-14.md | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/website/src/blog/2024-08-30-hot-chocolate-14/2024-08-30-hot-chocolate-14.md b/website/src/blog/2024-08-30-hot-chocolate-14/2024-08-30-hot-chocolate-14.md index 78daf8064ac..6aa08a1b393 100644 --- a/website/src/blog/2024-08-30-hot-chocolate-14/2024-08-30-hot-chocolate-14.md +++ b/website/src/blog/2024-08-30-hot-chocolate-14/2024-08-30-hot-chocolate-14.md @@ -609,9 +609,9 @@ We have prefixed the header with `hc-` to signal that this is a Hot Chocolate-sp ## Data -To make it easier to integrate new data sources into Hot Chocolate, we have made our `IExecutable` abstraction easier to implement and integrated it more fully into our resolver pipeline. This allows for easier integration of IQueryable based data drivers, like Entity Framework Core or Cosmos DB without the need of branching the whole data provider in Hot Chocolate. +To make it easier to integrate new data sources into Hot Chocolate, we have made our `IExecutable` abstraction simpler to implement and integrated it more fully into our resolver pipeline. This allows for easier integration of `IQueryable`-based data drivers, like Entity Framework Core or Cosmos DB, without the need to branch the entire data provider in Hot Chocolate. -We have integrated the current Cosmos DB driver with the new `HotChocolate.Data.Cosmos` package and adds the new `AsCosmosExecutable` extension method to the `IQueryable` interface. This allows you to easily convert your Cosmos DB queryable into an `IExecutable` that can be used within the default Filter, Sorting and Projection middleware. +We have integrated the current Cosmos DB driver with the new `HotChocolate.Data.Cosmos` package and added the new `AsCosmosExecutable` extension method to the `IQueryable` interface. This allows you to easily convert your Cosmos DB queryable into an `IExecutable` that can be used within the default Filter, Sorting, and Projection middleware. ```csharp [QueryType] @@ -627,15 +627,15 @@ public static class Query } ``` -However, if you are already trying out EF Core 9 then you should give the new Cosmos driver within EF Core a second look as it was rewritten from the ground up and is now on par with the Cosmos DB SDK driver. +However, if you are already trying out EF Core 9, you should give the new Cosmos driver within EF Core a second look, as it was rewritten from the ground up and is now on par with the Cosmos DB SDK driver. ## Query Conventions -Our mutation conventions were received very well by the community back when we introduced them. They help to implement a complex GraphQL pattern around mutations and errors. With mutation convention we provided consistency and removed the boilerplate from your code. +Our mutation conventions were very well received by the community when we introduced them. They help to implement a complex GraphQL pattern around mutations and errors. With mutation conventions, we provided consistency and removed the boilerplate from your code. -Ever since we have introduced the mutation conventions, we have been asked to provide a similar pattern for queries. While in most cases I actually would not resort to error patterns like for mutations because queries are typically side-effect-free and should be easily queried without care for complex result types. However, there are these cases where you want to return a domain error as part of your query. For these cases we did recognize the need for a consistent pattern. +Ever since we introduced the mutation conventions, we have been asked to provide a similar pattern for queries. While in most cases, I would not recommend resorting to error patterns like those used for mutations—because queries are typically side-effect-free and should be easily queried without concern for complex result types—there are cases where you want to return a domain error as part of your query. For these situations, we recognized the need for a consistent pattern. -However, queries are different from mutations and there is a better pattern here than introducing payloadesque types. With our new query conventions we are embracing a union type as result type where the first entry in the union represents success and the following entries represent errors. +However, queries are different from mutations, and there is a better pattern than introducing payload-esque types. With our new query conventions, we are embracing a union type as the result type, where the first entry in the union represents success, and the following entries represent errors. ```graphql type Query { @@ -693,33 +693,33 @@ public class Query ## Transport -Lets talk about the GraphQL transport layer and what has changed with Hot Chocolate 14. The GraphQL over HTTP spec is now in its final stages and we have been adopting the latest changes. This means that we no longer return status code 500 when the full result has be erased due to a non-null violation. Instead we return status code 200 and a JSON body that contains the error information and data as null. +Let's talk about the GraphQL transport layer and what has changed with Hot Chocolate 14. The GraphQL over HTTP spec is now in its final stages, and we have been adopting the latest changes. This means that we no longer return status code 500 when the full result has been erased due to a non-null violation. Instead, we return status code 200 with a JSON body that contains the error information and `data` as null. -If you are interested into the spec, you can find the current version [here](LINK). +If you are interested in the spec, you can find the current version [here](LINK). -We have also reintroduced the error code for not authenticated errors to make it easier for authentication flows. This was something we originally dropped in Hot Chocolate 13 but because a lot of you have struggled with this we have reintroduced it. +We have also reintroduced the error code for not authenticated errors to make it easier for authentication flows. This was something we originally dropped in Hot Chocolate 13, but because many of you struggled with this, we have reintroduced it. -Apart from these smaller bits an pieces we have completely rewritten our persisted operation aka trusted document pipeline to introduce end-2-end tracability across the whole transport layer. We have done this by implementing a feature we call semantic routes. The idea here is that each operation has a unique uri that is derived from the document has and the operation name. +Apart from these smaller bits and pieces, we have completely rewritten our persisted operation, aka trusted document pipeline, to introduce end-to-end traceability across the entire transport layer. We have done this by implementing a feature we call semantic routes. The idea here is that each operation has a unique URI that is derived from the document hash and the operation name. -This new persisted operation transport pipeline can be mapped separatly like the following. +This new persisted operation transport pipeline can be mapped separately, as shown in the following example: ```csharp app.MapGraphQLPersistedOperations(); ``` -By default we would map the persisted operations to `/graphql/persisted/{documentHash}/{operationName}` but you can change the root for this path. +By default, we would map the persisted operations to `/graphql/persisted/{documentHash}/{operationName}`, but you can change the root for this path. -Now with this only the variables and extensions are posted to the server or when you are using a query you can also use a get request like the following: +Now, with this setup, only the variables and extensions are posted to the server. If you are using a query, you can also use a GET request, like the following: ```csharp GET /graphql/persisted/1234/GetBook?variables={id:1} ``` -This also makes it so much easier to work with CDNs or to reroute certain operation to different servers. +This also makes it much easier to work with CDNs or to reroute certain operations to different servers. -For this release we have also reimplementd our batching transport layer and support now variable batching and request batching. Variable batching is a new batching proposal we have created for the upcoming Composite Schema Specification to transparently use batching in combination with standard GraphQL queries instead of relying on special field like the `_entities` field. +For this release, we have also reimplemented our batching transport layer and now support both variable batching and request batching. Variable batching is a new batching proposal we have created for the upcoming Composite Schema Specification to transparently use batching in combination with standard GraphQL queries, instead of relying on special fields like the `_entities` field. -With variable batching you can batch for the same operation multiple sets of variables. +With variable batching, you can batch multiple sets of variables for the same operation. ```json { @@ -728,7 +728,7 @@ With variable batching you can batch for the same operation multiple sets of var } ``` -Since a variable batch request has the same structure than a standard GraphQL request, except for the variable field which in this case is a list, we can also batch these within a batch request. +Since a variable batch request has the same structure as a standard GraphQL request, except for the `variable` field, which in this case is a list, we can also batch these within a batch request. ```json [ @@ -743,27 +743,27 @@ Since a variable batch request has the same structure than a standard GraphQL re ] ``` -This new batching API with in your backend allows for new use-cases and is a great way to optimize your GraphQL server. +This new batching API within your backend allows for new use cases and is a great way to optimize your GraphQL server. ## Security -We have seen countless GraphQL servers over the last year as part of our consulting engagements and in many cases they were not configured in a secure way. This was not for the lack of functionality in Hot Chocolate but because engineers that were transitioning to GraphQL often did not know good security practices in GraphQL. +We have seen countless GraphQL servers over the last year as part of our consulting engagements, and in many cases, they were not configured in a secure way. This was not due to a lack of functionality in Hot Chocolate but because engineers transitioning to GraphQL often did not know good security practices in GraphQL. -GraphQL as facebook created and used it was built around flexibility at dev time and persisted operations at production. This means the moment facebook deploys to production the GraphQL server becomes in essence a REST server. There is no GraphQL in production. The GraphQL server is only able to execute trusted operations that were exported from the various frontends into an operation store. The way this works is that in the build pipeline operations are stripped from the frontend code and replaced with a unique identifier. The stripped operation documents are stored in an operation store. The frontend on production would send to the GraphQL server the unique identifier instead of a full operation and the GraphQL server would only execute operations that are stored in the operations store. +GraphQL, as Facebook created and used it, was built around flexibility during development and persisted operations in production. This means that when Facebook deploys to production, the GraphQL server essentially becomes a REST server—there is no open GraphQL in production. The GraphQL server is only able to execute trusted operations that were exported from the various frontends into an operation store. In the build pipeline, operations are stripped from the frontend code and replaced with a unique identifier. The stripped operation documents are stored in an operation store. In production, the frontend sends the unique identifier to the GraphQL server instead of a full operation, and the GraphQL server only executes operations stored in the operations store. -This is the best way to do GraphQL and gives you the best story for schema evolvability as used operations are centrally known and can be statically analyzed. It also makes sure that you know the performance characteristics and the impact of operations to your backend, With Banana Cake Pop you can setup a schema registry and an operation store in less than 5 minutes. Have a look [here](LINK) for more information. +This is the best way to do GraphQL and provides the best approach for schema evolvability, as used operations are centrally known and can be statically analyzed. It also ensures that you know the performance characteristics and impact of operations on your backend. With Banana Cake Pop, you can set up a schema registry and an operation store in less than 5 minutes. Have a look [here](LINK) for more information. -However, most new developers are not aware how to do this or do not understand why they should. The other problem is that there is now easy path from an open GraphQL server to a closed system once you have clients working against your API. +However, most new developers are not aware of how to do this or do not understand why they should. Another problem is that there is no easy path from an open GraphQL server to a closed system once you have clients working against your API. -With Hot Chocolate 14 we wanted to make sure that your servers are secure even if you do not configure a single setting, and even if you do not know about persisted operations or you explicitly want an open GraphQL server. Going forward we have built into the core of Hot Chocolate the IBM cost specification to weight the impact of your requests and to restrict expensive operations right from the start. +With Hot Chocolate 14, we wanted to ensure that your servers are secure even if you do not configure a single setting, even if you do not know about persisted operations, or even if you explicitly want an open GraphQL server. Going forward, we have built into the core of Hot Chocolate the IBM cost specification to weigh the impact of your requests and to restrict expensive operations right from the start. -When you export your schema with Hot Chocolate 14 you will see that we have added cost directives to certain fields. We estimate cost automatically so that you do not have to do this manually. You can override where we are wrong. The IBM cost spec has to weights it calculates. The type cost, which estimates the objects being produced, in essence the data cost. Secondly, it estimates the field cost, this basically is the computational cost. +When you export your schema with Hot Chocolate 14, you will see that we have added cost directives to certain fields. We estimate costs automatically so that you do not have to do this manually. You can override these estimates where necessary. The IBM cost spec has two weights it calculates: type cost, which estimates the objects being produced (essentially the data cost), and field cost, which estimates the computational cost. -> With Hot Chocolate 14 we have implemented the static analysis but will add runtime analysis and result analysis as opt-ins with Hot Chocolate 15. +> With Hot Chocolate 14, we have implemented static analysis, but we will add runtime analysis and result analysis as opt-ins with Hot Chocolate 15. -The static analysis will go for maximums, which means if you say you want 50 elements in a list it will estimate 50 elements on not the actual number of elements. This makes sure that you do not overwhelm your server with a single request and have a good estimate what the request could mean to your backend. +The static analysis estimates maximums, meaning if you specify a list of 50 elements, it will estimate 50 elements, not the actual number of elements. This ensures that you do not overwhelm your server with a single request and provides a good estimate of what the request could mean for your backend. -You can combine the cost analysis scores with rate limiting to ensure that a use stays in cost boundaries over time. +You can combine the cost analysis scores with rate limiting to ensure that a user stays within cost boundaries over time. ```csharp .UseRequest(next => @@ -808,15 +808,15 @@ You can combine the cost analysis scores with rate limiting to ensure that a use }) ``` -While you would need it a bit more sophisticated in production, with redis to have a distributed rate limiter, this is a good start to ensure that your server is not overwhelmed. +While you would need a more sophisticated setup in production, such as using Redis to have a distributed rate limiter, this is a good start to ensure that your server is not overwhelmed. -With the cost spec you can also estimate a request impact without doing the actual request by sending in the header `GraphQL-Cost:validate` or if you want the request to be executed but to still the the cost even if the request is valid then you can send in the header `GraphQL-Cost:report`. +With the cost spec, you can also estimate a request's impact without executing the actual request by sending the header `GraphQL-Cost:validate`. If you want the request to be executed but still want to see the cost, even if the request is valid, you can send the header `GraphQL-Cost:report`. -With the IBM cost spec backed into the core its always on and this will make your GraphQL server more secure and more predictable. But this also will tell you the truth about your requests which might hurt when you migrate. +With the IBM cost spec baked into the core, it's always on, making your GraphQL server more secure and predictable. However, it will also reveal the true cost of your requests, which might be challenging when you migrate. -What we also have made sure of is that when you want to migrate from an open GraphQL server to trusted documents that this can now be done in a couple of minutes by slapping Banana Cake Pop in. Over a time frame of 30,60 or 90 days the GraphQL server will report operations that were executed and will store them in the operation store. You can manually decide which queries not to take in. After that period you can switch trusted operations on and only operations tracked in the operation store will be allowed form this day forward. +We have also ensured that migrating from an open GraphQL server to trusted documents can now be done in a few minutes by integrating Banana Cake Pop. Over a period of 30, 60, or 90 days, the GraphQL server will report executed operations and store them in the operation store. You can manually decide which queries to exclude. After that period, you can switch to trusted operations, and only operations tracked in the operation store will be allowed from that day forward. -One other thing we have changed with Hot Chocolate 14 is around introspection. When we detect a production environment in ASP.NET core we will automatically disable introspection and provide a schema file on the route `/graphql?sdl` which is one time computed schema file that will server as a simple file from your server. The misunderstanding with introsection is often that people think its about hiding the schema. This is actually not the case since it quite simple to infer the schema from the request observed in a web application. The problem with introspection is that it is easy to produce very large results in your GraphQL server. When I say large than I mean 200 - 300 MB large. This depends on your schema. Most tools will work fine with a schema file which is much smaller than the introspection result and costs literally no compute and memory. You can override this behavior like the following. +Another change we made with Hot Chocolate 14 is around introspection. When we detect a production environment in ASP.NET Core, we will automatically disable introspection and provide a schema file at the route `/graphql?sdl`, which is a one-time computed schema file that will be served as a simple file from your server. The misunderstanding with introspection is often that people think it's about hiding the schema. This is actually not the case since it's quite simple to infer the schema from requests observed in a web application. The problem with introspection is that it can easily produce very large results from your GraphQL server. When I say large, I mean 200-300 MB, depending on your schema. Most tools will work fine with a schema file, which is much smaller than the introspection result and costs virtually nothing in terms of compute and memory. You can override this behavior as follows: ```csharp builder From 42a5f96043033a62d57476b4eca73833bd63dec8 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Tue, 27 Aug 2024 10:48:38 +0200 Subject: [PATCH 28/60] more --- .../2024-08-30-hot-chocolate-14.md | 99 ++++++++++++++++--- 1 file changed, 86 insertions(+), 13 deletions(-) diff --git a/website/src/blog/2024-08-30-hot-chocolate-14/2024-08-30-hot-chocolate-14.md b/website/src/blog/2024-08-30-hot-chocolate-14/2024-08-30-hot-chocolate-14.md index 6aa08a1b393..362084415b0 100644 --- a/website/src/blog/2024-08-30-hot-chocolate-14/2024-08-30-hot-chocolate-14.md +++ b/website/src/blog/2024-08-30-hot-chocolate-14/2024-08-30-hot-chocolate-14.md @@ -834,31 +834,104 @@ builder ## Fusion -- Source Schema Package -- Composite Schema Specification +OK, with that, let's talk about Fusion, our GraphQL solution for federated APIs. With version 14, we have focused heavily on stability. Based on feedback from the community, we have improved how errors traverse from the source schemas to the composite schema. + +We have also made the configuration process easier by providing a new package that offers attributes for Fusion. This allows you to use C# instead of GraphQL extension files. + +```csharp +public static class Query +{ + [Lookup] + public static async Task GetBrandByProductIdAsync( + [Is("product { id }")] int id, + ISelection selection, + BrandByProductIdDataLoader brandByProductId, + CancellationToken cancellationToken) + => await brandByProductId + .Select(selection) + .LoadAsync(id, cancellationToken); +} +``` + +This is especially nice when we talk about `@require`. + +```csharp +public static int EstimateShippingTime( + [Require("dimension { weight }")] int productWeight) +``` + +We have also worked on experimental support for Aspire, which gives you a much nicer development workflow around distributed GraphQL. + +Apart from these smaller changes, we are currently working on three major areas for Fusion. The first is implementing the composite schema specification, which will align Hot Chocolate Fusion with the open spec proposal. The second effort is achieving AOT compatibility for the gateway. This is a major undertaking, as we are essentially creating a second GraphQL server from scratch, focused solely on the gateway. + +Additionally, recognizing that many people use Apollo Federation and may want to migrate to a pure .NET solution, we are also working on compatibility with the Apollo Federation spec. As the composite schema specification merges Fusion concepts around lookups and the Apollo Federation spec around schema evolution and traffic steering, the step from Fusion to supporting Apollo Federation is not that big. However, we have moved these tasks from Hot Chocolate 14 to Hot Chocolate 15. ## Client -- HotChocolate.Transport +For Hot Chocolate Fusion, we have created a low-level GraphQL client that supports a variety of GraphQL protocols. We have refactored Strawberry Shake to use this basic client for HTTP traffic. For many server-to-server use cases, I recommend using this client as it is geared toward performance and allows you to bring your own models. + +```csharp +var client = new DefaultGraphQLHttpClient(httpClient); + +var query = + """ + query($episode: Episode!) { + hero(episode: $episode) { + name + } + } + """; + +var variables = new Dictionary +{ + ["episode"] = "JEDI", +}; + + +var response = await client.PostAsync(query, variables); + +using var body = await response.ReadAsResultAsync(cts.Token); +var mode = body.Data.Deserialize() +``` ## GraphQL Cockpit -- OpenTelemetry/BCP -- Schema Registry +With Banana Cake Pop, we have further shifted to give you more control over your applications with an end-to-end GraphQL cockpit that provides a schema registry, client registry, operation store, GraphQL telemetry, end-to-end OTEL tracing, logging, metrics, and strong schema evolution workflows that put you in control. + +SCREENSHOT + +With Banana Cake Pop you have the best solution to manage you distributed GraphQL setup. ## Community -- Further optimize filter expressions by @nikolai-mb in https://github.com/ChilliCream/graphql-platform/pull/7311 -- DevContainer -- Azure Data API Builder +In this release, we had a staggering **30** new contributors who helped alongside the team of core contributors. Overall, we had 46 contributors working on Hot Chocolate 14. These contributions ranged from fixing typos to optimizing our filter expressions, like the [pull request](https://github.com/ChilliCream/graphql-platform/pull/7311) from @nikolai-mb. We are very grateful to have such a vibrant community that helps us make Hot Chocolate better every day. + +For this reason, we have now created a GitHub DevContainer template so that you can get started with contributing in about 2 minutes. You can either run the DevContainer directly on GitHub: + +SCREENSHOT + +Or you can run it locally on your own Docker. If you do not know what DevContainers are, you can read up on them [here](https://docs.github.com/en/codespaces/setting-up-your-project-for-codespaces/adding-a-dev-container-configuration/introduction-to-dev-containers). ## Documentation and Courses -- DomeTrain Course +We are still hard at work updating the documentation and are also taking feedback on this version. This post is based on 14.0.0-rc.1. + +If you want to learn all about the new features of Hot Chocolate, I have made a course on DomeTrain that gives you the ultimate introduction to GraphQL and uses Hot Chocolate in its preview builds. + +If you use the code `STAIB`, you will get a 20% discount on the course. + +[https://dometrain.com/course/getting-started-graphql-in-dotnet/](https://dometrain.com/course/getting-started-graphql-in-dotnet/) ## Hot Chocolate 15 -- Focus -- .NET 8 / 9 -- DataLoader -- Projections Engine +Lastly, let's talk about the roadmap ahead. We have already started work on Hot Chocolate 15, which is slated for release in December/January. Hot Chocolate 15 will have a heavy focus on Hot Chocolate Fusion and will introduce a brand new gateway and new composition tooling. As I outlined in the Fusion section, we are working on three areas that will reinvent what Fusion is. + +Other areas we will focus on include `DataLoader`, with a new batch scheduler that uses its own `TaskScheduler` to better track `DataLoader` promises in batching and defer scenarios. We already have a PR up for this but had stability concerns for version 14. With version 15, we will have the time to get this right and provide a much more efficient `DataLoader` implementation. + +Projections is another area where we are all in, working on a brand new projections engine. You can already see bits and pieces in Hot Chocolate 14 with the experimental features we've introduced around `DataLoader` projections. The new projection engine in `HotChocolate.Data` will be built on top of `DataLoader` and will offer a much more efficient way to project your data with proper data requirements. + +With Hot Chocolate 15, we are dropping support for `.NETStandard 2.0`, `.NET 6.0`, and `.NET 7`. Going forward, you will need to run on `.NET 8.0` or `.NET 9.0`. This change will allow us to modernize a lot of code and eliminate many precompile directives. + +Looking beyond Hot Chocolate 15, we will shift our focus back to Strawberry Shake, which will undergo a major overhaul. + +With that, I encourage you to try out Hot Chocolate 14 RC.1 and give us your feedback. We have planned for three more RCs before we go GA. From b8ddcbd7288eb423f2ea2270949c7036b314b941 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Tue, 27 Aug 2024 10:53:17 +0200 Subject: [PATCH 29/60] more --- .../2024-08-30-hot-chocolate-14.md | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/website/src/blog/2024-08-30-hot-chocolate-14/2024-08-30-hot-chocolate-14.md b/website/src/blog/2024-08-30-hot-chocolate-14/2024-08-30-hot-chocolate-14.md index 362084415b0..fb95f20eb34 100644 --- a/website/src/blog/2024-08-30-hot-chocolate-14/2024-08-30-hot-chocolate-14.md +++ b/website/src/blog/2024-08-30-hot-chocolate-14/2024-08-30-hot-chocolate-14.md @@ -36,13 +36,30 @@ For mutations, the situation is different, as mutations inherently cause side ef So, the new default execution behavior is much more opinionated but leads to an easier default experience. However, we recognize that there are reasons to maybe use the request DI scope everywhere and you can, as you can configure the default DI scope behavior with the default schema options. -EXAMPLE. +```csharp +builder + .AddGraphQL() + .AddTypes() + .ModifyOptions(o => + { + o.DefaultQueryDependencyInjectionScope = DependencyInjectionScope.Request; + o.DefaultMutationDependencyInjectionScope = DependencyInjectionScope.Request; + }); +``` Also, you can override the default, whatever your default may be on a per resolver basis. -EXAMPLE. +```csharp +[UseRequestScope] +[UsePaging] +public static async Task> GetBrandsAsync( + PagingArguments pagingArguments, + BrandService brandService, + CancellationToken cancellationToken) + => await brandService.GetBrandsAsync(pagingArguments, cancellationToken).ToConnectionAsync(); +``` -TODO : Mention DataLoader DI handling!!! +> We have applied the same DI handling to source generated DataLoader which by default will now use an explicit DI scope for each DataLoader fetch. ## Query Inspection From dca54fde9c9cd51c4216f346d146e83181c7ef89 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Tue, 27 Aug 2024 11:10:30 +0200 Subject: [PATCH 30/60] more --- .../2024-08-30-hot-chocolate-14.md | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/website/src/blog/2024-08-30-hot-chocolate-14/2024-08-30-hot-chocolate-14.md b/website/src/blog/2024-08-30-hot-chocolate-14/2024-08-30-hot-chocolate-14.md index fb95f20eb34..88d324de500 100644 --- a/website/src/blog/2024-08-30-hot-chocolate-14/2024-08-30-hot-chocolate-14.md +++ b/website/src/blog/2024-08-30-hot-chocolate-14/2024-08-30-hot-chocolate-14.md @@ -63,6 +63,8 @@ public static async Task> GetBrandsAsync( ## Query Inspection +