GraphQL Day 004 : Subscriptions

So far we have seen Queries and Mutations in GraphQL. It is now time to look into third and final key component of the triage – Subscriptions.

Subscriptions allow GraphQL servers to send real time updates to subscribed clients when particular events occur. This allows clients to support real-time functionality by having the servers push the updates in real time rather than client polling for possible updates at frequent interval.

GraphQL supports two transport mechanism for providing updates – GraphQL over web sockets and GraphQL over Server Sent Events, also known as SSE.

Web Sockets are considered the de-facto standard for real-time communication for GraphQLand support full duplex communication channel over a single TCP connection. HotChocolate supports two popular protocols over web-sockets – graphql-ws and subscription-transport-ws.

GraphQL over Server Sent Events (SSE) allows servers to send updates to client over HTTP. Unlike Web sockets, SSE is half duplex communication channel allowing server to updates to client, but not vice versa.

Let us now go ahead and create our example subscription. For sake of this example, we will be using web sockets as transport protocol.

So far in this tutorial on GraphQL, we have been working with a GraphQL service, which exposes a database of Projects. We will continue with the example and expand it by allowing the server to send updates to subscribed clients each time a new Project is added. For this we will use the AddProject mutation which we created in the earlier part of this series to raise the event.

The first update we would need make is to ensure the respective event is raised. This is done using the ITopicEventSender. Let us use it our AddProject mutation.

[UseMutationConvention]
[GraphQLDescription("Add Project")]
public async Task<Project> AddProject([Service(ServiceKind.Synchronized)] DemoGraphContext dbContext,
    [Service] ITopicEventSender topicSender,
    [ID]int id, string name, string createdBy)
{

    var project = new Project
    {
        Id = id,
        Name = name,
        CreatedBy = createdBy
    };
    var result = await dbContext.AddAsync(project);
    await dbContext.SaveChangesAsync();
    await topicSender.SendAsync(nameof(AddProject), project);
    return result.Entity;
}

As you can observe in the code above, once we have added and saved the new project, we raise/send a AddProject message. This message now has to be handled by the subscribed clients, for which we would need to create subscribers.

[ExtendObjectType(typeof(Subscription))]
publicclassProjectSubscription
{
    [Subscribe]
    [Topic(nameof(ProjectMutation.AddProject))]
    public Project AddedProject([EventMessage] Project project) => project;
}

We have created a new class ProjectSubscription with a single method called AddedProject. The method itself doesn’t do anything fancy, it just returns the parameter it is passed to. However, the important aspect to observe here is the two attributes added to the method – SubscribeAttribute and TopicAttribute.

The SubscribeAttribute marks the method as a subscription, while the TopicAttribute specifies the particular message/topic the subscription listens to. In this case, we are listening to the “AddedProject” topic.

The next step is to register our subscriptions just like we did our queries and mutations.

builder.Services.AddGraphQLServer()
                .AddDefaultTransactionScopeHandler()
                .AddMutationConventions(applyToAllMutations: false)
                .AddQueryType<Query>()
                .AddTypeExtension<ProjectQueryResolver>()
                .AddTypeExtension<TimeLogQueryResolver>()
                .AddMutationType<Mutation>()
                .AddTypeExtension<ProjectMutation>()
                .AddSubscriptionType<Subscription>()
                .AddTypeExtension<ProjectSubscription>();
                

We have one more step to do. A Subscription represents a producer consumer implementation, which is used to handle the events. HotChocolate provides 3 main subscription provides, In-Memory provider, Redis provider, and Postgres Provider.

In this example, we will use In Memory Provider. To add support for In-Memory Subscription provider, we can use the AddInMemorySubscriptions() in method.

builder.Services.AddGraphQLServer()
                .AddDefaultTransactionScopeHandler()
                .AddMutationConventions(applyToAllMutations: false)
                .AddQueryType<Query>()
                .AddTypeExtension<ProjectQueryResolver>()
                .AddTypeExtension<TimeLogQueryResolver>()
                .AddMutationType<Mutation>()
                .AddTypeExtension<ProjectMutation>()
                .AddSubscriptionType<Subscription>()
                .AddTypeExtension<ProjectSubscription>()
                .AddInMemorySubscriptions();

That’s all we require to run our subscriptions. To test your Banana Pop client, run two instances of your client, and in one subscribe for a particular subscription, and in other create a mutation which triggers the subscription message.

Leave a comment