Integrating OTP Services with Channels

Now that we have a complete information system, let’s integrate it with our web frontend. Our goal is to call into our information system any time a user adds an annotation to a video, to see if we have relevant results to add to that user’s conversation.

First let’s make sure rumbl_web depends on info_sys. Open up apps/rumbl_web/mix.exs and add this entry under deps:

 {:info_sys, in_umbrella: true},

Note how we were able to build and test info_sys in complete isolation and now we can introduce it as a dependency to any of the sibling applications that need it. If an application doesn’t need info_sys, then it doesn’t have to depend on it either.

Next let’s integrate InfoSys with the VideoChannel. Whenever we receive a new annotation in handle_in, we want to invoke the compute function. Since the compute function is a blocking call, we want to make it asynchronous in our channel so our user gets the annotation broadcast right away. Let’s first use a task to spawn a function call for our InfoSys computation by making the following changes to your lib/rumbl_web/channels/video_channel.ex:

1: def​ handle_in(​"​​new_annotation"​, params, user, socket) ​do
case​ Multimedia.annotate_video(user, socket.assigns.video_id, params) ​do
{​:ok​, annotation} ->
broadcast_annotation(socket, user, annotation)
5:  Task.start(​fn​ -> compute_additional_info(annotation, socket) ​end​)
{​:reply​, ​:ok​, socket}
{​:error​, changeset} ->
{​:reply​, {​:error​, %{​errors:​ changeset}}, socket}
10: end
end
defp​ broadcast_annotation(socket, user, annotation) ​do
broadcast!(socket, ​"​​new_annotation"​, %{
15: id:​ annotation.id,
user:​ RumblWeb.UserView.render(​"​​user.json"​, %{​user:​ user}),
body:​ annotation.body,
at:​ annotation.at
})
20: end

On line 4, we extract our broadcast to a shared broadcast_annotation function so our information system can make use of it when it has relevant results to share. Next, we spawn a task on line 5 to asynchronously call a new compute_additional_info function, which we’ll write in a moment. We use Task.start because we don’t care about the task result nor if it fails. It’s important that we use a task here so we don’t block on any particular messages arriving to the channel.

Now, let’s write compute_additional_info to ask our InfoSys for relevant results:

 defp​ compute_additional_info(annotation, socket) ​do
  for result <-
  InfoSys.compute(annotation.body, ​limit:​ 1, ​timeout:​ 10_000) ​do
 
  backend_user = Accounts.get_user_by(​username:​ result.backend.name())
  attrs = %{​body:​ result.text, ​at:​ annotation.at}
 
 case​ Multimedia.annotate_video(
  backend_user, annotation.video_id, attrs) ​do
 
  {​:ok​, info_ann} ->
  broadcast_annotation(socket, backend_user, info_ann)
  {​:error​, _changeset} -> ​:ignore
 end
 end
 end

First, we call into our information system, asking for only one result. Our service returns the best information it has, given our query. We tell it we are willing to wait ten seconds for an answer. Next, we use a comprehension to grab the backend user from our Accounts context, get the relevant attributes and annotate our video with that information. Finally we call broadcast_annotation on line 13 to report the new annotation to all subscribers on this topic. The integration is tight and smooth, and it’s done. Our code is extremely efficient with our caching layer. Imagine an active chat of users watching a sports game or chanting the same message. Our service won’t waste any cycles recomputing values.

We need to seed our database with a wolfram user to post annotations along with our real user conversations. Create a priv/repo/backend_seeds.exs, like this:

 {​:ok​, _} = Rumbl.Accounts.create_user(%{​name:​ ​"​​Wolfram"​, ​username:​ ​"​​wolfram"​})

Now, you can run these seeds with mix run, like this:

 $ ​​cd​​ ​​apps/rumbl
 $ ​​mix​​ ​​run​​ ​​priv/repo/backend_seeds.exs
 [debug] QUERY OK db=0.8ms
 begin []
 [debug] QUERY OK db=80.9ms
 INSERT INTO "users" ("name","username","inserted_at", ...
 [debug] QUERY OK db=7.5ms
 commit []

Note we’re using our internal create_user function instead of the user-facing register_user function. The context function perfectly fits this scenario and allows us to not mix up end-user code paths with the path for internal users. Let’s try it out on the frontend:

images/src/otp/erlang-the-movie.png

It works!

At this point, you can use this template to add services to our information system. Bing has an API that you might use to retrieve search results for linking. You could also build your own service. The important thing is that you have a framework to add services to.

We’re at a convenient breaking point. It’s time to wrap up.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.158.36