Now that we can create orders, we’re at the perfect spot to introduce the first basic subscription that will support pushing these orders as they’re created out to subscribed clients. You’ll first need to define a subscription field in your schema, and then you’ll also need a way to actually trigger this subscription when the :place_order mutation runs.
| subscription do |
| field :new_order, :order do |
| |
| config fn _args, _info -> |
| {:ok, topic: "*"} |
| end |
| end |
| end |
For the most part, this is a pretty ordinary-looking field. We’ve got another top-level object, subscription, to house our subscription fields, and then the :new_order, which will return the :order object we’re already familiar with. The fact that it returns a regular :order object is crucial, because this means that all the work we have done to support the output of the mutation can be reused immediately for real-time data.
What’s new, however, is the config macro, and it’s one of a couple macros that are specific to setting up subscriptions. The job of the config macro is to help us determine which clients who have asked for a given subscription field should receive data by configuring a topic. We’ll talk more later about constructing topics, but the main thing to know is that topics are scoped to the field they’re on, and they have to be a string. We’re just going to use "*" to indicate that we care about all orders (but there’s nothing special about "*" itself).
Set Up and Return Error | |
---|---|
The config function can also return {:error, reason}, which prevents the subscription from being created. |
Let’s see if we can subscribe with GraphiQL. First, let’s make sure it’s running again, but this time, inside an IEx session (you’ll see why shortly):
| $ iex -S mix phx.server |
| [info] Running PlateSlateWeb.Endpoint with Cowboy using http://0.0.0.0:4000 |
Then, browse to http://localhost:4000/graphiql and enter the following in the left-side panel (you can close the “Query Variables” panel if you have it open):
| subscription { |
| newOrder { |
| customerNumber |
| items { name quantity} |
| } |
| } |
When you hit “play,” instead of getting a result, you’ll get a message saying, "Your subscription data will appear here after server publication!":
What’s happening here is that although the server has accepted the subscription document, the server is waiting on some kind of event that will trigger execution of the document and distribution of the result. Specifically, it’s waiting for an event that targets the field of our subscription newOrder and the topic associated with this specific document "*".
The most direct way to make this trigger happen is with the Absinthe.Subscription.publish/3 function, which gives us manual control of the publishing mechanism. If you go into the IEx session in your console, you can trigger the subscription you just created in GraphiQL by running:
| iex> order = PlateSlate.Ordering.Order |> PlateSlate.Repo.all |> List.last |
| %PlateSlate.Ordering.Order{} displayed |
| iex> Absinthe.Subscription.publish( |
| PlateSlateWeb.Endpoint, |
| order, |
| new_order: "*" |
| ) |
| :ok |
If you look back to your GraphiQL page, you should see a result as shown in the figure.
The arguments to the publish/3 function are the module you’re using as the pubsub, the value that you’re broadcasting, and the field: topic pairs at which to broadcast the value. Concretely then, the function call you typed in IEx says to broadcast the last %Order{} struct to all clients subscribed to the :new_order field via the "*" topic.
You may have noticed when you set up the subscription field that you didn’t specify a resolver, and this is why. Unlike a root query or root mutation resolver, which generally starts with no root value and has to start from scratch, the root value of a subscription document is the value that is passed to publish/3. You can see this for yourself if you add just an inspect resolver to the subscription field:
| subscription do |
| field :new_order, :order do |
| |
| config fn _args, _info -> |
| {:ok, topic: "*"} |
| end |
| |
» | resolve fn root, _, _ -> |
» | IO.inspect(root) |
» | {:ok, root} |
» | end |
| end |
| end |
If you re-run the Absinthe.Subscription.publish/3 call in your IEx session, you will see printed into console the value you are broadcasting, nested under the :new_order key:
| %Ordering.Order{ |
| Contents |
| } |
With this Absinthe.Subscription.publish/3 function at our disposal, it’s clear then that one possibility for making our live interface is to put it inside the :place_order mutation resolver so that instead of triggering subscriptions from IEx, we’ll trigger subscriptions every time a new order is placed.
| def place_order(_, %{input: place_order_input}, _) do |
| case Ordering.create_order(place_order_input) do |
| {:ok, order} -> |
» | Absinthe.Subscription.publish(PlateSlateWeb.Endpoint, order, |
» | new_order: "*" |
» | ) |
| {:ok, %{order: order}} |
| {:error, changeset} -> |
| {:ok, %{errors: transform_errors(changeset)}} |
| end |
| end |
You could have some fun with this:
Play around with changing what parts of the subscription document you ask for, and play around with the values you put in the mutation document to get a feel for how the two documents relate to one another.
3.129.70.185