This post is going to continue my exploration of GraphQL using the .NET Boxed template as a jumping off point. The code I am starting with can be found here. Check out GraphQL using .NET Boxed: Mutations from last week for an exploration of mutations.
Subscriptions are GraphQL’s way of allowing a client to request notification of changes to the data.
Starting Point
As discovered a few weeks ago, MainSchema
is the central point to finding how GraphQL is set up in this template. For reference here is the full class.
public class MainSchema : Schema { public MainSchema( QueryObject query, MutationObject mutation, SubscriptionObject subscription, IDependencyResolver resolver) : base(resolver) { this.Query = resolver.Resolve<QueryObject>(); this.Mutation = mutation; this.Subscription = subscription; } }
Today we are interested in the Subscription
property which is being assigned a SubscriptionObject
.
Subscription Object
The following is the full SubscriptionObject
for reference. I will point out a few details after the code.
public class SubscriptionObject : ObjectGraphType<object> { public SubscriptionObject(IHumanRepository humanRepository) { this.Name = "Subscription"; this.Description = "The subscription type, represents all updates can be pushed to the client in real time over web sockets."; this.AddField( new EventStreamFieldType() { Name = "humanCreated", Description = "Subscribe to human created events.", Arguments = new QueryArguments( new QueryArgument<ListGraphType<StringGraphType>>() { Name = "homePlanets", }), Type = typeof(HumanCreatedEvent), Resolver = new FuncFieldResolver<Human>(context => context.Source as Human), Subscriber = new EventStreamResolver<Human>(context => { var homePlanets = context.GetArgument<List<string>>("homePlanets"); return humanRepository .WhenHumanCreated .Where(x => homePlanets == null || homePlanets.Contains(x.HomePlanet)); }), }); } }
A lot of this is going to look very similar to the other types we have reviewed for queries and mutations. For example, the Type
is HumanCreatedEvent
which derives from HumanObject
which is ObjectGraphType
around the Human
class.
Type = typeof(HumanCreatedEvent)
One of the hardest things exploring GraphQL is getting a good handle on the object graph. I highly recommend you spend some time in these classes getting the connection solid in your mind.
As another example that should look pretty similar to things we coved in the other post is the Resolver
which is dealing with the base Human
type.
Resolver = new FuncFieldResolver<Human>(context => context.Source as Human)
This next bit is new and deals with the actual notification of GraphQL when the HumanRepository
creates a new human. The following code has had the home planet related stuff removed for clarity.
Subscriber = new EventStreamResolver<Human>(context => { return humanRepository .WhenHumanCreated; })
What is WhenHumanCreated
? Looks like it is an observable provided by theHumanRepository
.
public IObservable<Human> WhenHumanCreated => this.whenHumanCreated.AsObservable();
Looking at the AddHuman
function you will see that this observable is provided a new value everytime a human is created which in turn provides notification to our GraphQL setup to notify any clients that are subscribed that a new human was added.
public Task<Human> AddHuman(Human human, CancellationToken cancellationToken) { human.Id = Guid.NewGuid(); Database.Humans.Add(human); this.whenHumanCreated.OnNext(human); return Task.FromResult(human); }
Wrapping Up
That covers my exploration of subscriptions. For me, this was the coolest part of the things I have seen in GraphQL.
I know this was a bit of a strange series as we just looked at the code generated by a template. I hope you found it useful. I know it helped me get a better grip on the idea behind GraphQL and how it can be handled in .NET Core.
The associated sample code can be found here.
Also published on Medium.
Hi, I also follow the similar pattern.
How do you adjust your code when you want to retrieve data from multiple streams..
especially one source is from external (assume via message queue) and other is from your code (some enumerable to observable).