Integrating Cizen with Phoenix

Yuki Kodama
Cizen
Published in
5 min readNov 11, 2018

Do you know Cizen? It’s an Elixir library that helps you to develop applications that consist of automata who communicate by passing events to each other. The author of Cizen has published an article titled “Asynchronous Request and Connection Pooling in Elixir with Cizen”.

But, wait. Is Cizen a library for specific purposes? Absolutely NOT! Just like a meaning of Cizen (“自然” in Japanese, “Nature” in English), it can be utilized every scene in application development. In this article, I’d like to introduce an integration of Cizen and Phoenix Framework though building a tiny web application “Hello World” (github repo). 🚀

I assume you have already installed requirements below.

  • Elixir 1.7.3 (Erlang/OTP 21)
  • Phoenix 1.4.0

If not, please install Elixir and Phoenix before started. To upgrade Phoenix from 1.3.x to 1.4.0, don’t forget to uninstall project generator before install.

Hello World 🌐

Before diving into the Cizen world, I’d like to explain what I want to achieve with this example. “Hello World” is a simple web application that accepts HTTP GET request and returns an HTML response including a text “Hello World”. Uh…where is Cizen? Don’t worry, Cizen has a responsibility of a core application; this means that a content of the HTTP response is provided from a Cizen’s automaton.

Overview of Hello World application

Let’s create “Hello World” application with Phoenix’s project generator. In this application, we don’t use database backends (ecto) and JavaScript assets (webpack since 1.4.0, formerly brunch). Therefore we drop them on scaffolding.

mix phx.new hello_world --no-webpack --no-ecto

Run dev server and open in your browser to check it works fine.

cd hello_world
mix phx.server

Congrats! 🎉

To add Cizen to this project, open mix.exs file with your favorite editor and add {:cizen, "~> 0.12.5"} to dependencies section.

defp deps do
[
{:cizen, "~> 0.12.5"},
{:phoenix, "~> 1.4.0"},
{:phoenix_pubsub, "~> 1.1"},
{:phoenix_html, "~> 2.11"},
{:phoenix_live_reload, "~> 1.2", only: :dev},
{:gettext, "~> 0.11"},
{:jason, "~> 1.0"},
{:plug_cowboy, "~> 2.0"}
]
end

Save the file, run mix deps.get to install new dependencies, and restart Phoenix dev server. To simplify the HTML template, remove unnecessary part of layout/app.html.eex and page/index.html.eex.

app.html.eex

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>HelloWorld</title>
</head>
<body>
<main role="main" class="container">
<%= render @view_module, @view_template, assigns %>
</main>
<script type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
</body>
</html>

index.html.eex

<h1>Hello World</h1>

Let’s dive into Cizen part. An automaton is a primitive entity of the world. In a Cizen way of design, we think what kind of automata is needed and how the automata behave through receiving and dispatching events.

Here is a specification for our automaton.

  • The name is GreetingAutomaton
  • Receives Greeting event
  • Dispatches Reply event with a text message

That’s all! It’s time to write code. 😁 First, we define the Greeting event.

defmodule HelloWorld.Greeting do
defstruct []
end

Cizen’s events are not special, it’s just an Elixir struct. Because of the Greeting event doesn’t have a payload, defstruct declaration leaves blank.

In contrast with the Greetingevent, the definition of Reply event is a bit different. To represent communications such as a pair of request and response, Cizen provides a feature named “Requestive Events”. Use Cizen.Reuqest behavior and define the Reply response event using defresponse macro. Notice that message field is also defined in it.

defmodule HelloWorld.Greeting do
defstruct []
use Cizen.Request
defresponse Reply, :greeting_id do
defstruct [:greeting_id, :message]
end
end

message field is used for containing a text message, but umm…what is greeting_id field? Can we remove it? No. It’s important for the feature because Cizen needs to pick a response event up from tons of unrelated events in the world.

Define GreetingAutomaton that responds the Greeting event and returns the Reply response event with a message.

alias Cizen.Effects.{Receive, Subscribe, Dispatch}
alias Cizen.{Event, Filter}
defmodule HelloWorld.GreetingAutomaton do
use Cizen.Automaton
defstruct [] @impl true
def spawn(id, _) do
# Subscribe "Greeting" event
perform id, %Subscribe{
event_filter: Filter.new(
fn %Event{body: %HelloWorld.Greeting{}} -> true end
)
}
# No state in this automaton
:loop
end
@impl true
def yield(id, :loop) do
# Wait for "Greeting" event
event = perform id, %Receive{}
# Respond to "Greeting" event
perform id, %Dispatch{
body: %HelloWorld.Greeting.Reply{
greeting_id: event.id,
message: "Hello Cizen"
}
}
:loop
end
end

Oops…lots of code. Don’t be afraid, let’s take a look at the detail.

To define Cizen’s automata, use Cizen.Automaton behavior and implement some lifecycle callbacks; spawn/2 and yield/2. spawn/2 is called on start. It’s useful for an initialization such as subscribing events and determining an initial state of an automaton. By the way, defstruct of an automaton is used for passing parameters to automaton on launch.

Cizen’s automata need to interact with others via performing “Effects”. To subscribe Greeting events, we perform Subscribe effect with a filter that passes only Greeting events. yield/2 is called after returning an initial state from spawn/2 callback. Thanks to Elixir’s awesome pattern matching, we can also define multiple yield/2 based on the state (second argument of yield/2).

yield/2 behaves like a general event loop; waits until arriving an event, then does something, yields new state, and loops infinitely. GreetingAutomaton receives Greeting event by performing Receive effect, and then dispatches Greeting.Reply response event that contains greeting_id (comes from Greeting event) and a message “Hello Cizen” by performing Dispatch effect.

Place above code in lib/hello_world/hello.ex (source code). Don’t forget adding a bootstrap code to lib/hello_world/application.ex (source code) in order to start the GreetingAutomaton as part of a supervision tree.

defmodule HelloWorld.Application do
@moduledoc false
use Application def start(_type, _args) do
children = [
HelloWorldWeb.Endpoint,
%{
id: HelloWorld.GreetingAutomaton,
start: {Cizen.Saga, :start_link,
[%HelloWorld.GreetingAutomaton{}]}
}
]
opts = [strategy: :one_for_one, name: HelloWorld.Supervisor]
Supervisor.start_link(children, opts)
end
[...]
end

By the way, where Greeting event comes from? It’s Phoenix world. Let’s connect the both worlds. Open controllers/page_controller.ex file (source code).

alias Cizen.Effects.Requestdefmodule HelloWorldWeb.PageController do
use HelloWorldWeb, :controller
use Cizen.Effectful
def index(conn, _params) do
%{body: %{message: message}} = handle fn id ->
perform id, %Request{body: %HelloWorld.Greeting{}}
end
render(conn, "index.html", message: message)
end
end

Cizen.Effectful is useful to create a block which performs effects. In the block of handle, perform Request effect with Greeting event and block until receiving Reply response event from GreetingAutomaton. This process runs every time Phoenix gets a request. To render a greeting message, update templates/page/index.html.eex file.

<h1><%= @message %></h1>

Finally, we get a greeting from GreetingAutomaton in the Cizen world.

As a conclusion, summarize what I wanted to present in this article.

  • Automata drive only with Events
  • Automata interact with others through performing Effects
  • Use Cizen.Effectful to interact with the Cizen world

Thanks for reading. Enjoy Cizen! 🎉

Acknowledgment: Thank you for revising this article, Ryo33.

--

--

Yuki Kodama
Cizen
Writer for

Web Developer. JavaScript / Ruby on Rails / AWS. Using Vim and loving coffee. ex-TortoiseHg maintainer.