Skip to content

Instantly share code, notes, and snippets.

@benmccallum
Last active January 27, 2022 15:19
Show Gist options
  • Save benmccallum/900de160b3044ae54b8b84a0fa1e990e to your computer and use it in GitHub Desktop.
Save benmccallum/900de160b3044ae54b8b84a0fa1e990e to your computer and use it in GitHub Desktop.
Hot Chocolate merge handlers for stitching

Solving name conflicts at the gateway (e.g. types or directives) when you've got local schemas being stitched too. In this example the gateway schema has the dupe type on it and we let the merge handler skip the version/s on the downstream service/s, but if you didn't have the type added at the gateway, you could choose to pick one instance from one of the downstream services and skip the others.

var gatewayBuilder = services
      // Base schema
      .AddGraphQLServer()
          // Add authorization services and types (will require merge handling if a downstream service adds auth too)
          // If you didn't have local schemas being stitched and don't need authorization middleware to run on your gateway, don't do this and then merging is automatic)
          .AddAuthorization()
          // Add Relay Node type (will require merge handling if a downstream service adds relay support too)
          .AddType<NodeType>()
          .AddDirectiveMergeHandler<MergeDirectivesHandler>()
          .AddTypeMergeHandler<MergeTypesHandler>()

Adding custom scalars onto the stitched-in schema on the gateway.

var remoteSchemaName = "Bookings";

// Stitch in remote schema into the gateway schema
gatewayBuilder.AddRemoteSchema(remoteSchemaName, ignoreRootTypes: false);

// Adding the remote schema above will create a local representation in the gateay
// You can get / modify that local representation by grabbing it again by name as below
// Configure additionally (may not be needed in later versions)
gatewayBuilder.Services
    .AddGraphQL(remoteSchemaName)
    // Currently you need to re-add custom scalars here
    // though this may change in v13
    .AddType<EmailAddressType>() // from HotChocolate.Types.Scalars prob
    ;
using System;
using System.Collections.Generic;
using System.Linq;
using HotChocolate.Stitching.Merge;
namespace MyCompany.GatewayService.Stitching
{
/// <summary>
/// <para>This merge handler deals with duplicate directives when schemas are stitched.</para>
/// <para>It currently handles dupes of: authorize.</para>
/// </summary>
public class MergeDirectivesHandler : IDirectiveMergeHandler
{
private readonly MergeDirectiveRuleDelegate _next;
public MergeDirectivesHandler(MergeDirectiveRuleDelegate next) => _next = next;
public void Merge(ISchemaMergeContext context, IReadOnlyList<IDirectiveTypeInfo> directives)
{
// If you're using HC Authorization on multiple downstream servers
if (!directives.First().Definition.Name.Value.Equals("authorize", StringComparison.InvariantCultureIgnoreCase))
{
_next(context, directives);
}
}
}
}
using System.Collections.Generic;
using System.Linq;
using HotChocolate.Stitching.Merge;
namespace MyCompany.GraphQLService.Stitching
{
/// <summary>
/// Handles conflicts when a type exists in mutliple downstream schemas.
/// </summary>
public class MergeTypesHandler : ITypeMergeHandler
{
private readonly MergeTypeRuleDelegate _next;
public MergeTypesHandler(MergeTypeRuleDelegate next) => _next = next;
private readonly string[] _dupeTypes = new[]
{
// If you're using Relay/GlobalIdentification on multiple downstream servers or have added NodeType to the gateway too
"Node",
// If you're using HC Authorization on multiple downstream servers or have added Authorization to the gateway too
"ApplyPolicy"
};
public void Merge(ISchemaMergeContext context, IReadOnlyList<ITypeInfo> types)
{
// I believe this skips all downstream versions of the type,
// leaving only the type added directly onto the gateway schema.
// So you'll need `.AddType<Node>()` and `.AddAuthorization()` on the gateway
// schema builder
if (_dupeTypes.Contains(types.First().Definition.Name.Value))
{
return;
}
// Standard merge behaviour
_next(context, types);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment