Real-Time Data - AWS AppSync

Real-Time Data

GraphQL Schema Subscription Directives

Subscriptions in AWS AppSync are invoked as a response to a mutation. This means that you can make any data source in AWS AppSync real time by specifying a GraphQL schema directive on a mutation. The AWS AppSync client SDK automatically handles subscription connection management. The SDK uses either pure WebSockets or MQTT over WebSockets as the network protocol between the client and service. The protocol depends on the version of the client you’re using.

Note: To control authorization at connection time to a subscription, you can leverage controls such as IAM, Amazon Cognito identity pools, or Amazon Cognito user pools for field-level authorization. For fine-grained access controls on subscriptions, you can attach resolvers to your subscription fields and perform logic using the identity of the caller and AWS AppSync data sources. For more information, see Security.

Subscriptions are triggered from mutations and the mutation selection set is sent to subscribers.

The following example shows how to work with GraphQL subscriptions. It doesn’t specify a data source because the data source could be AWS Lambda, Amazon DynamoDB, or Amazon Elasticsearch Service.

To get started to with subscriptions, you must add a subscription entry point to your schema as follows:

schema { query: Query mutation: Mutation subscription: Subscription }

Suppose you have a blog post site, and you want to subscribe to new blogs and changes to existing blogs. To do this, add the following Subscription definition to your schema:

type Subscription { addedPost: Post updatedPost: Post deletedPost: Post }

Suppose further that you have the following mutations:

type Mutation { addPost(id: ID! author: String! title: String content: String url: String): Post! updatePost(id: ID! author: String! title: String content: String url: String ups: Int! downs: Int! expectedVersion: Int!): Post! deletePost(id: ID!): Post! }

You can make these fields real time by adding an @aws_subscribe(mutations: ["mutation_field_1", "mutation_field_2"]) directive for each of the subscriptions you want to receive notifications for, as follows:

type Subscription { addedPost: Post @aws_subscribe(mutations: ["addPost"]) updatedPost: Post @aws_subscribe(mutations: ["updatePost"]) deletedPost: Post @aws_subscribe(mutations: ["deletePost"]) }

Because the @aws_subscribe(mutations: ["",..,""]) takes an array of mutation inputs, you can specify multiple mutations, which trigger a subscription. If you’re subscribing from a client, your GraphQL query might look like the following:

subscription NewPostSub { addedPost { __typename version title content author url } }

The subscription query above is needed for client connections and tooling. However, if you’re using MQTT over WebSockets, the client triggering the mutation specifies the selection set that subscribers receive. To demonstrate this, suppose that a mutation was made from another mobile client or a server (for example, mutation addPost(...){id author title }). In this case, the content, version, and URL are not published to subscribers. Instead, the ID, author, and title are published.

If you use the pure WebSockets client, selection set filtering is done per client, as each client can define its own selection set. In this case, the subscription selection set must be a subset of the mutation selection set. For example, a subscription addedPost{author title} linked to the mutation addPost(...){id author title url version} receives only the author and title of the post. It does not receive the other fields. However, if the mutation lacked the author in its selection set, the subscriber would get a null value for the author field (or an error in case the author field is defined as required/not-null in the schema).

Furthermore, if you are using MQTT over WebSockets in your application, there are some changes you need to be aware of. If you didn’t configure the associated subscription selection set with the required fields and relied on the mutation fields to push data to subscribed client, the behavior will change when you move to pure WebSockets. In the example above, a subscription without the “author” field defined in its selection set would still return the author name with MQTT over WebSockets as the field is defined in the mutation, the same behavior won’t apply for pure WebSockets. The subscription selection set is essential when using pure WebSockets: if a field is not explicitly defined in the subscription it won’t be returned by AWS AppSync.

In the previous example, the subscriptions didn’t have arguments. Suppose your schema looks like the following:

type Subscription { updatedPost(id:ID! author:String): Post @aws_subscribe(mutations: ["updatePost"]) }

In this case, your client defines a subscription as follows:

subscription UpdatedPostSub { updatedPost(id:"XYZ", author:"ABC") { title content } }

The return type of a subscription field in your schema must match the return type of the corresponding mutation field. In the previous example, this was shown as both addPost and addedPost returned as a type of Post.

To set up subscriptions on the client, see Building a Client App.

Using Subscription Arguments

An important part of using GraphQL subscriptions is understanding when and how to use arguments because subtle changes enable you to modify how and when clients are notified about mutations that have occurred. To do this, see the sample schema from the Quickstart section Launch a Sample Schema, which creates “Events” and “Comments”. For this sample schema, the following mutation occurs:

type Mutation { createEvent( name: String!, when: String!, where: String!, description: String! ): Event deleteEvent(id: ID!): Event commentOnEvent(eventId: ID!, content: String!, createdAt: String!): Comment }

In the default sample, clients can subscribe to comments when a specific eventId argument is passed through:

type Subscription { subscribeToEventComments(eventId: String!): Comment @aws_subscribe(mutations: ["commentOnEvent"]) }

However, if you want to enable clients to subscribe to a single event OR all events, you can make this argument optional by removing the exclamation point (!) from the subscription prototype:

subscribeToEventComments(eventId: String): Comment

With this change, clients that omitted this argument get comments for all events. Additionally, if you want clients to explicitly subscribe to all comments for all events, you should remove the argument as follows:

subscribeToEventComments: Comment

Use these for comments on one or more events. If you want to know about all events that are created, you could do the following:

type Subscription { subscribeToNewEvents: Event @aws_subscribe(mutations: ["createEvent"]) }

Multiple arguments can also be passed. For example, if you want to get notified of new events at a specific place and time, you could do the following:

type Subscription { subscribePlaceDate(where: String! when: String!): Event @aws_subscribe(mutations: ["createEvent"]) }

As a result, the client application can now do the following:

subscription myplaces { subscribePlaceDate(where: "Seattle" when: "Saturday"){ id name description } }

Argument null value has meaning

When making a subscription query in AWS AppSync, a null argument value will filter the results differently than omitting the argument entirely.

Let’s explain with an example. Let’s go back to the events app sample where we could create events and post comments on events. See the sample schema from the Quickstart section Launch a Sample Schema.

Let’s modify our schema to include a new location field on the Comment field, that describes where the comment was sent from. The value could be a set of coordinates or a place. See below the schema, note that we stripped it down for the sake of brevity:

type Comment { # The id of the comment's parent event. eventId: ID! # A unique identifier for the comment. commentId: String! # The comment's content. content: String # Location where the comment was made location: String } type Event { id: ID! name: String where: String when: String description: String } type Mutation { commentOnEvent(eventId: ID!, location: String, content: String): Comment } type Subscription { subscribeToEventComments(eventId: String!, location: String, content: String): Comment @aws_subscribe(mutations: ["commentOnEvent"]) }

Note the new optional field Comment.location.

Now say that we want to get notified of all the comments as they are posted for a particular event, we would write the following subscription:

subscribeToEventComments(eventId: "1") { eventId commentId location content }

Now if we were to instead add the field argument location: null to the subscription above,

subscribeToEventComments(eventId: "1" location: null) { eventId commentId location content }

we would now be asking a different question. This subscription now registers the client to get notified of all the comments that have not provided a location for a particular event.