You can find code example for this chapter here.
In the previous chapters we've seen how subscriptions work, where the fields available for them are defined, and how to implement event filtering. Now you probably have the impression that it's relatively easy to implement real-time notifications that push data to clients whenever they need and keep them up-to-date with all changes. This is also the point where the AWS documentation and most tutorials stop.
Unfortunately, subscriptions implemented like this are hardly usable for any realistic scenario as they miss a lot of practical requirements.
First, filtering can be done only on top-level fields of the return type. In the previous examples, we used filters on the Todo items, but that's usually not what you want. When a user logs in to the app, it is interested in the new Todo items that is assigned to them. For this, we would need to move from the Todo to the User and filter by the User.id
field. And that is not possible with AppSync.
Second, subscriptions don't handle the scenario when a change needs to send multiple notifications. In a ticketing system, moving a ticket between projects needs to send 2 events: one for the deletion from the original project and a second one for a creation in the new project, otherwise there will be some information disclosure. Or when a user is deleted, its tickets should become unassigned, and that can generate several events.
And third, the subscription event contains only the fields defined by the mutation, which is usually client-controlled. This can easily break real-time functionality for a seemingly unrelated change.
To solve all these problems, we need to think about subscriptions a bit differently and break the mutation -> subscription tie. In this chapter, we'll discuss a pattern that makes subscriptions practically useful.
First, add a dedicated mutation and event for the subscription:
type TodoEvent {
userId: ID!
groupId: ID!
todoId: ID!
severity: Severity!
todo: Todo!
}
type Mutation {
notifyTodo(userId: ID!, groupId: ID!, severity: Severity!, id: ID!): TodoEvent!
}
type Subscription {
todo(userId: ID, groupId: ID, severity: Severity): TodoEvent
@aws_subscribe(mutations: ["notifyTodo"])
}
The TodoEvent
type has all the fields on the top level that are needed for filtering. In our case, that will be the todoId
, severity
, the userId
, and a second level of indirection, groupId
. This means the clients can subscribe to updates to individual Todo items, items belonging to a user, and items under a group.
Then the notifyTodo
mutation has all the same arguments as the fields of the TodoEvent
. This is to make sure all these filters can be used even when a Todo item is removed. For example, without getting the userId
as an argument, the resolver for the notifyTodo
wouldn't know which user the deleted Todo item belonged to.