Skip to content

Commit

Permalink
feat(gql): add federation
Browse files Browse the repository at this point in the history
  • Loading branch information
cyberhck committed Jun 5, 2021
0 parents commit f4c5639
Show file tree
Hide file tree
Showing 15 changed files with 373 additions and 0 deletions.
30 changes: 30 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Build and Test

on: [push]

jobs:
build:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.203
- name: restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore
publish:
runs-on: ubuntu-18.04
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Semantic Release
id: semantic
uses: cycjimmy/semantic-release-action@v2
with:
extra_plugins: |
@semantic-release/[email protected]
@semantic-release/[email protected]
@semantic-release/[email protected]
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.idea
Micro.GraphQL.Federation/bin
Micro.GraphQL.Federation/obj
16 changes: 16 additions & 0 deletions Micro.GraphQL.Federation.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Micro.GraphQL.Federation", "Micro.GraphQL.Federation\Micro.GraphQL.Federation.csproj", "{962DFC2C-79C8-48F5-859C-493088E245D5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{962DFC2C-79C8-48F5-859C-493088E245D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{962DFC2C-79C8-48F5-859C-493088E245D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{962DFC2C-79C8-48F5-859C-493088E245D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{962DFC2C-79C8-48F5-859C-493088E245D5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
14 changes: 14 additions & 0 deletions Micro.GraphQL.Federation/Directives/ExtendsDirective.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using GraphQL.Types;

namespace Micro.GraphQL.Federation.Directives
{
public class ExtendsDirective : DirectiveGraphType
{
public const string DirectiveName = "extends";
public override bool? Introspectable => true;

public ExtendsDirective() : base(DirectiveName, DirectiveLocation.Object, DirectiveLocation.Interface)
{
}
}
}
15 changes: 15 additions & 0 deletions Micro.GraphQL.Federation/Directives/ExternalDirective.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using GraphQL.Types;

namespace Micro.GraphQL.Federation.Directives
{
public class ExternalDirective : DirectiveGraphType
{

public const string DirectiveName = "external";
public override bool? Introspectable => true;

public ExternalDirective() : base(DirectiveName, DirectiveLocation.FieldDefinition)
{
}
}
}
20 changes: 20 additions & 0 deletions Micro.GraphQL.Federation/Directives/KeyDirective.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using GraphQL.Types;

namespace Micro.GraphQL.Federation.Directives
{
public class KeyDirective : DirectiveGraphType
{
public const string DirectiveName = "key";
public override bool? Introspectable => true;

public KeyDirective() : base(DirectiveName, DirectiveLocation.Object, DirectiveLocation.Interface)
{
Repeatable = true;
Arguments = new QueryArguments(new QueryArgument<NonNullGraphType<StringGraphType>>
{
Name = "fields",
Description = "_FieldSet"
});
}
}
}
19 changes: 19 additions & 0 deletions Micro.GraphQL.Federation/Directives/ProvidesDirective.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using GraphQL.Types;

namespace Micro.GraphQL.Federation.Directives
{
public class ProvidesDirective : DirectiveGraphType
{
public const string DirectiveName = "provides";
public override bool? Introspectable => true;

public ProvidesDirective() : base(DirectiveName, DirectiveLocation.FieldDefinition)
{
Arguments = new QueryArguments(new QueryArgument<NonNullGraphType<StringGraphType>>
{
Name = "fields",
Description = "_FieldSet"
});
}
}
}
19 changes: 19 additions & 0 deletions Micro.GraphQL.Federation/Directives/RequiresDirective.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using GraphQL.Types;

namespace Micro.GraphQL.Federation.Directives
{
public class RequiresDirective : DirectiveGraphType
{
public const string DirectiveName = "requires";
public override bool? Introspectable => true;

public RequiresDirective() : base(DirectiveName, DirectiveLocation.FieldDefinition)
{
Arguments = new QueryArguments(new QueryArgument<NonNullGraphType<StringGraphType>>
{
Name = "fields",
Description = "_FieldSet"
});
}
}
}
66 changes: 66 additions & 0 deletions Micro.GraphQL.Federation/Extensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System.Collections.Generic;
using GraphQL.Builders;
using GraphQL.Types;
using GraphQL.Utilities.Federation;
using GraphQLParser.AST;
using Micro.GraphQL.Federation.Types;
using Microsoft.Extensions.DependencyInjection;

namespace Micro.GraphQL.Federation
{
public static class Extensions
{
public static void EnableFederation<TEntityType>(this IServiceCollection services) where TEntityType : EntityType
{
services.AddTransient<AnyScalarGraphType>();
services.AddTransient<ServiceGraphType>();
services.AddTransient<TEntityType>();
}
private static FieldBuilder<TSourceType, TReturnType> BuildAstMeta<TSourceType, TReturnType>(FieldBuilder<TSourceType, TReturnType> fieldBuilder, string name, string value = null)
{
fieldBuilder.FieldType.BuildAstMeta(name, value);
return fieldBuilder;
}
public static FieldBuilder<TSourceType, TReturnType> Requires<TSourceType, TReturnType>(this FieldBuilder<TSourceType, TReturnType> fieldBuilder, string fields) => BuildAstMeta(fieldBuilder, "requires", fields);
public static FieldBuilder<TSourceType, TReturnType> Provides<TSourceType, TReturnType>(this FieldBuilder<TSourceType, TReturnType> fieldBuilder, string fields) => BuildAstMeta(fieldBuilder, "provides", fields);
public static FieldBuilder<TSourceType, TReturnType> External<TSourceType, TReturnType>(this FieldBuilder<TSourceType, TReturnType> fieldBuilder) => BuildAstMeta(fieldBuilder, "external");
public static void BuildAstMeta(this IProvideMetadata type, string name, string value = null)
{
var definition = (GraphQLObjectTypeDefinition)type.GetMetadata<ASTNode>("__AST_MetaField__", () => BuildGraphQLObjectTypeDefinition());
var directive = BuildGraphQLDirective(name, value);
AddDirective(definition, directive);
//type.SetAstType(definition);
type.Metadata["__AST_MetaField__"] = definition;
}
private static void AddDirective(GraphQLObjectTypeDefinition definition, GraphQLDirective directive) => ((List<GraphQLDirective>)definition.Directives).Add(directive);
private static GraphQLObjectTypeDefinition BuildGraphQLObjectTypeDefinition() => new GraphQLObjectTypeDefinition
{
Directives = new List<GraphQLDirective>(),
Location = new GraphQLLocation(),
Fields = new List<GraphQLFieldDefinition>()
};
private static GraphQLDirective BuildGraphQLDirective(string name, string value = null, ASTNodeKind kind = ASTNodeKind.StringValue) => new GraphQLDirective
{
Name = new GraphQLName
{
Value = name,
Location = new GraphQLLocation()
},
Arguments = string.IsNullOrEmpty(value) ? new List<GraphQLArgument>() : new List<GraphQLArgument>() {
new GraphQLArgument
{
Name = new GraphQLName {
Value = "fields",
Location = new GraphQLLocation()
},
Value = new GraphQLScalarValue(kind) {
Value = value,
Location = new GraphQLLocation()
},
Location = new GraphQLLocation()
}
},
Location = new GraphQLLocation()
};
}
}
12 changes: 12 additions & 0 deletions Micro.GraphQL.Federation/Micro.GraphQL.Federation.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="GraphQL" Version="4.5.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="5.0.0" />
</ItemGroup>

</Project>
26 changes: 26 additions & 0 deletions Micro.GraphQL.Federation/ObjectGraphType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Threading.Tasks;
using GraphQL.Types;
using GraphQL.Utilities.Federation;

namespace Micro.GraphQL.Federation
{
public class ObjectGraphType<TSource> : global::GraphQL.Types.ObjectGraphType<TSource>
{
protected void ResolveReferenceAsync(Func<FederatedResolveContext, Task<TSource>> resolver)
{
ResolveReferenceAsync(new FuncFederatedResolver<TSource>(resolver));
}

protected void Key(string fields)
{
this.BuildAstMeta("key", fields);
}

private void ResolveReferenceAsync(IFederatedResolver resolver)
{
// Metadata[FederatedSchemaBuilder.RESOLVER_METADATA_FIELD] = resolver;
Metadata["__FedResolver__"] = resolver;
}
}
}
59 changes: 59 additions & 0 deletions Micro.GraphQL.Federation/Query.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using GraphQL;
using GraphQL.Types;
using GraphQL.Utilities.Federation;
using Micro.GraphQL.Federation.Types;

namespace Micro.GraphQL.Federation
{
public abstract class Query<TEntityType> : ObjectGraphType where TEntityType : EntityType
{
protected Query()
{
Field<NonNullGraphType<ServiceGraphType>>().Name("_service").Resolve(x => new { });
Field<NonNullGraphType<ListGraphType<TEntityType>>>().Name("_entities")
.Argument<NonNullGraphType<ListGraphType<NonNullGraphType<AnyScalarGraphType>>>>("representations")
.ResolveAsync(context =>
{
var representations = context.GetArgument<List<Dictionary<string, object>>>("representations");
var results = new List<Task<object>>();

foreach (var representation in representations)
{
var typeName = representation["__typename"].ToString();
var type = context.Schema.AllTypes[typeName];

if (type != null)
{
// execute resolver
var resolver = type.GetMetadata<IFederatedResolver>("__FedResolver__");
if (resolver != null)
{
var resolveContext = new FederatedResolveContext
{
Arguments = representation,
ParentFieldContext = context
};
var result = resolver.Resolve(resolveContext);
results.Add(result);
}
else
{
results.Add(Task.FromResult((object)representation));
}
}
else
{
// otherwise return the representation
results.Add(Task.FromResult((object)representation));
}
}

var tasks = Task.WhenAll(results).ContinueWith(results => (object)results.Result);
tasks.ConfigureAwait(false);
return tasks;
});
}
}
}
21 changes: 21 additions & 0 deletions Micro.GraphQL.Federation/Schema.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using GraphQL.Utilities.Federation;
using Micro.GraphQL.Federation.Directives;
using Micro.GraphQL.Federation.Types;

namespace Micro.GraphQL.Federation
{
public class Schema<TEntityType> : global::GraphQL.Types.Schema where TEntityType : EntityType
{
protected Schema(IServiceProvider services) : base(services)
{
RegisterType(typeof(AnyScalarGraphType));
RegisterType(typeof(ServiceGraphType));
RegisterType(typeof(TEntityType));
Directives.Register(new ExternalDirective());
Directives.Register(new RequiresDirective());
Directives.Register(new ProvidesDirective());
Directives.Register(new KeyDirective());
}
}
}
12 changes: 12 additions & 0 deletions Micro.GraphQL.Federation/Types/EntityType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using GraphQL.Types;

namespace Micro.GraphQL.Federation.Types
{
public abstract class EntityType : UnionGraphType
{
public EntityType()
{
Name = "_Entity";
}
}
}
41 changes: 41 additions & 0 deletions release.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
class SemanticReleaseError extends Error {
constructor(message, code, details) {
super(message);
Error.captureStackTrace(this, this.constructor);
this.name = 'SemanticReleaseError';
this.details = details;
this.code = code;
this.semanticRelease = true;
}
}

module.exports = {
verifyConditions: [
() => {
if (!process.env.NUGET_TOKEN) {
throw new SemanticReleaseError(
"No NUGET_TOKEN specified",
"ENONUGET_TOKEN",
"Please make sure to nuget token in `NUGET_TOKEN` environment variable on your CI environment. The token must be able to push to nuget");
}
},
"@semantic-release/github"
],
"prepare": [
{
"path": "@semantic-release/exec",
"cmd": "dotnet build --configuration Release -p:Version=${nextRelease.version}"
},
{
"path": "@semantic-release/exec",
"cmd": "dotnet pack --include-symbols -c Release --output ./artifacts -p:PackageVersion=${nextRelease.version}"
}
],
"publish": [
"@semantic-release/github",
{
"path": "@semantic-release/exec",
"cmd": "dotnet nuget push ./artifacts/*.nupkg --api-key ${ process.env.NUGET_TOKEN } --source https://api.nuget.org/v3/index.json"
}
]
}

0 comments on commit f4c5639

Please sign in to comment.