public static IRequestExecutorBuilder AddRemoteSchema( this IRequestExecutorBuilder builder, NameString schemaName, Func <IServiceProvider, CancellationToken, ValueTask <RemoteSchemaDefinition> > loadSchema, bool ignoreRootTypes = false) { if (builder is null) { throw new ArgumentNullException(nameof(builder)); } if (loadSchema is null) { throw new ArgumentNullException(nameof(loadSchema)); } schemaName.EnsureNotEmpty(nameof(schemaName)); // first we add a full GraphQL schema and executor that represents the remote schema. // This remote schema will be used by the stitching engine to execute queries against // this schema and also to lookup types in order correctly convert between scalars. builder .AddGraphQL(schemaName) .ConfigureSchemaServices(services => { services.TryAddSingleton( sp => new HttpRequestClient( sp.GetApplicationService <IHttpClientFactory>(), sp.GetRequiredService <IErrorHandler>(), sp.GetRequiredService <IHttpStitchingRequestInterceptor>())); services.TryAddSingleton < IHttpStitchingRequestInterceptor, HttpStitchingRequestInterceptor>(); }) .ConfigureSchemaAsync( async(services, schemaBuilder, cancellationToken) => { // No we need to load the schema document. RemoteSchemaDefinition schemaDef = await loadSchema(services, cancellationToken) .ConfigureAwait(false); DocumentNode document = schemaDef.Document.RemoveBuiltInTypes(); // We store the schema definition on the schema building context // and copy it to the schema once that is built. schemaBuilder .SetContextData(typeof(RemoteSchemaDefinition).FullName !, schemaDef) .TryAddTypeInterceptor <CopySchemaDefinitionTypeInterceptor>(); // The document is used to create a SDL-first schema ... schemaBuilder.AddDocument(document); // ... which will fail if any resolver is actually used ... schemaBuilder.Use(next => context => throw new NotSupportedException()); }) // ... instead we are using a special request pipeline that does everything like // the standard pipeline except the last middleware will not start the execution // algorithms but delegate the request via HTTP to the downstream schema. .UseHttpRequestPipeline(); // Next, we will register a request executor proxy with the stitched schema, // that the stitching runtime will use to send requests to the schema representing // the downstream service. builder .ConfigureSchemaAsync(async(services, schemaBuilder, cancellationToken) => { IInternalRequestExecutorResolver noLockExecutorResolver = services.GetRequiredService <IInternalRequestExecutorResolver>(); IRequestExecutor executor = await noLockExecutorResolver .GetRequestExecutorNoLockAsync(schemaName, cancellationToken) .ConfigureAwait(false); var autoProxy = AutoUpdateRequestExecutorProxy.Create( new RequestExecutorProxy( services.GetRequiredService <IRequestExecutorResolver>(), schemaName), executor); schemaBuilder .AddRemoteExecutor(schemaName, autoProxy) .TryAddSchemaInterceptor <StitchingSchemaInterceptor>() .TryAddTypeInterceptor <StitchingTypeInterceptor>(); var schemaDefinition = (RemoteSchemaDefinition)autoProxy.Schema .ContextData[typeof(RemoteSchemaDefinition).FullName !] !; var extensionsRewriter = new SchemaExtensionsRewriter(); foreach (var extensionDocument in schemaDefinition.ExtensionDocuments) { var doc = (DocumentNode)extensionsRewriter .Rewrite(extensionDocument, schemaName.Value); SchemaExtensionNode?schemaExtension = doc.Definitions.OfType <SchemaExtensionNode>().FirstOrDefault(); if (schemaExtension is not null && schemaExtension.Directives.Count == 0 && schemaExtension.OperationTypes.Count == 0) { var definitions = doc.Definitions.ToList(); definitions.Remove(schemaExtension); doc = doc.WithDefinitions(definitions); } schemaBuilder.AddTypeExtensions(doc); } schemaBuilder.AddTypeRewriter( new RemoveFieldRewriter( new FieldReference( autoProxy.Schema.QueryType.Name, SchemaDefinitionFieldNames.SchemaDefinitionField), schemaName)); schemaBuilder.AddDocumentRewriter( new RemoveTypeRewriter( SchemaDefinitionType.Names.SchemaDefinition, schemaName)); foreach (var schemaAction in extensionsRewriter.SchemaActions) { switch (schemaAction.Name.Value) { case DirectiveNames.RemoveRootTypes: schemaBuilder.AddDocumentRewriter( new RemoveRootTypeRewriter(schemaName)); break; case DirectiveNames.RemoveType: schemaBuilder.AddDocumentRewriter( new RemoveTypeRewriter( GetArgumentValue( schemaAction, DirectiveFieldNames.RemoveType_TypeName), schemaName)); break; case DirectiveNames.RenameType: schemaBuilder.AddTypeRewriter( new RenameTypeRewriter( GetArgumentValue( schemaAction, DirectiveFieldNames.RenameType_TypeName), GetArgumentValue( schemaAction, DirectiveFieldNames.RenameType_NewTypeName), schemaName)); break; case DirectiveNames.RenameField: schemaBuilder.AddTypeRewriter( new RenameFieldRewriter( new FieldReference( GetArgumentValue( schemaAction, DirectiveFieldNames.RenameField_TypeName), GetArgumentValue( schemaAction, DirectiveFieldNames.RenameField_FieldName)), GetArgumentValue( schemaAction, DirectiveFieldNames.RenameField_NewFieldName), schemaName)); break; } } });
public static IRequestExecutorBuilder AddRemoteSchema( this IRequestExecutorBuilder builder, NameString schemaName, Func <IServiceProvider, CancellationToken, ValueTask <DocumentNode> > loadSchema, bool ignoreRootTypes = false) { if (builder is null) { throw new ArgumentNullException(nameof(builder)); } if (loadSchema is null) { throw new ArgumentNullException(nameof(loadSchema)); } schemaName.EnsureNotEmpty(nameof(schemaName)); // first we add a full GraphQL schema and executor that represents the remote schema. // This remote schema will be used by the stitching engine to execute queries against // this schema and also to lookup types in order correctly convert between scalars. builder .AddGraphQL(schemaName) .ConfigureSchemaServices(services => { services.TryAddSingleton( sp => new HttpRequestClient( sp.GetApplicationService <IHttpClientFactory>(), sp.GetRequiredService <IErrorHandler>(), sp.GetRequiredService <IHttpStitchingRequestInterceptor>())); services.TryAddSingleton < IHttpStitchingRequestInterceptor, HttpStitchingRequestInterceptor>(); }) .ConfigureSchemaAsync( async(services, schemaBuilder, cancellationToken) => { // No we need to load the schema document. DocumentNode document = await loadSchema(services, cancellationToken) .ConfigureAwait(false); document = document.RemoveBuiltInTypes(); // The document is used to create a SDL-first schema ... schemaBuilder.AddDocument(document); // ... which will fail if any resolver is actually used ... // todo : how to bind resolvers schemaBuilder.Use(next => context => throw new NotSupportedException()); }) // ... instead we are using a special request pipeline that does everything like // the standard pipeline except the last middleware will not start the execution // algorithms but delegate the request via HTTP to the downstream schema. .UseHttpRequestPipeline(); // Next, we will register a request executor proxy with the stitched schema, // that the stitching runtime will use to send requests to the schema representing // the downstream service. builder .ConfigureSchemaAsync(async(services, schemaBuilder, cancellationToken) => { IInternalRequestExecutorResolver noLockExecutorResolver = services.GetRequiredService <IInternalRequestExecutorResolver>(); IRequestExecutor executor = await noLockExecutorResolver .GetRequestExecutorNoLockAsync(schemaName, cancellationToken) .ConfigureAwait(false); var autoProxy = AutoUpdateRequestExecutorProxy.Create( new RequestExecutorProxy( services.GetRequiredService <IRequestExecutorResolver>(), schemaName), executor); schemaBuilder .AddRemoteExecutor(schemaName, autoProxy) .TryAddSchemaInterceptor <StitchingSchemaInterceptor>() .TryAddTypeInterceptor <StitchingTypeInterceptor>(); }); // Last but not least, we will setup the stitching context which will // provide access to the remote executors which in turn use the just configured // request executor proxies to send requests to the downstream services. builder.Services.TryAddScoped <IStitchingContext, StitchingContext>(); if (ignoreRootTypes) { builder.AddDocumentRewriter(new RemoveRootTypeRewriter(schemaName)); } return(builder); }