-
Notifications
You must be signed in to change notification settings - Fork 0
Making a Counter
In this step, we will create a counter and our application will be able to receive messages which will increment the counter. This simple exercise will introduce us to the following concepts:
- messages
- Transform functions
- Dataflow Definitions
- the input queue
- the Data Renderer
A Pedestal application project is really a set of tools that will help you build a single-page application. These tools do things like compile your code, generate host pages and allow you to view different aspects of your application in isolation. The output of these tools is a set of artifacts which can be deployed.
Before we make any changes to our application, let's start up the tools so that we can see the changes as we make them.
cd tutorial-client
lein repl
Once you have a running REPL, run the following:
(use 'dev)
(run)and then open a browser and navigate to http://localhost:3000. In
the Tools Menu, click on the Data UI link where you will see
:greeting
"Hello World"
You are looking at the Data Renderer. This is a renderer which comes with Pedestal and can be used to display any rendering data which is produced by your application. When working on a new application it is very useful to be able to see and even work with the app before you have done any work on custom rendering. We will see how useful this is as we move forward.
The value being displayed here is a representation of the data model which is currently the value:
{:greeting "Hello World!"}The Data Renderer is displaying this value by making a heading for each top level key and then printing the value associated with each key. Below, we are going to make some changes which will illustrate how to define and change a data model. In a later part of the tutorial we will look a bit closer at why the data is rendered in this way.
Creating a simple counter allows us to solve four problems which are common in all applications:
- define a function which handles a state transition
- trigger a state transition
- define the location where a specific bit of state will be stored
- notice when state changes so that we can do something about it
Before we make any changes to code, we need to understand how to trigger changes in a Pedestal application. A Pedestal application is essentially one big object. It contains state and that state is changed when it receives a message. Messages are data.
Messages have at least a type and a topic. The message which we will send to cause the counter to increment will look like this:
{msg/type :inc msg/topic [:my-counter]}Here the type is :inc and the :topic is [:my-counter]. Both type
and topic are namespaced to the io.pedestal.app.messages
namespace. Message maps can contain any additional data.
The first thing that we will typically change in a new project is the
application's behavior. The code for behavior is located in the file
tutorial-client/app/src/tutorial_client/behavior.clj. It is
important to note that this project layout is not required. As an
application grows, you will not want to have all of your application
logic in this one file. You may organize your code however you like.
To make a counter we will need a function to handle the state
transition for the counter value. This function will simply increment
the value. In the behavior.clj file, delete the existing function
named set-value-transform and replace it with the function shown
below.
(defn inc-transform [old-value _]
((fnil inc 0) old-value))In Pedestal, this is called a transform function. It takes two
arguments: the old value and the input message and it returns a new
value. This function ignores the input message and increments the
old value.
Pedestal applications are written by creating pure functions, like the
one shown above, and linking them together in a dataflow. The dataflow
is described with a map. We call this description the Dataflow
Definition. In a dataflow, transform functions handle all inputs
to the flow. There are many other parts of a dataflow which can be
configured but for now we will stick to transforms.
Update the example-app var in the tutorial-client.behavior
namespace to look like this:
(def example-app
{:version 2
:transform [[:inc [:my-counter] inc-transform]]})Our dataflow definition has two keys: :version and
:transform. :version is used to indicate the version of this
definition format.
:transform contains a vector of vectors which define which transform
function will be called when a message is received. Each vector looks
like this:
[:inc [:my-counter] inc-transform]where the first element is the type, the second element is the topic and the final element is the function to call.
When a message is received, the first matching transform function will be called. When the message
{msg/type :inc msg/topic [:my-counter]}is received it will be routed to the first transform function which matches the type and topic.
Remember that one of the problems above was to "define the location
where a specific bit of state will be stored". That location is
defined in the message as [:my-counter]. We can think of this as a
path to the location in the data model where the function will do its
work. The transform function will receive the old value at this path
as input and the value returned by the function will become the new
value at this path.
What if we wanted to have lots of counters and not just one? We could change the definition above to be:
(def example-app
{:version 2
:transform [[:inc [:*] inc-transform]]})Using a wildcard instead of a keyword. This means that any :inc
message type will be handled by this function and the actual path in
the data model which will be updated is determined by the message. Note
that this will only match a path with one element. To match any path
of any length, we could use:
[:inc [:**] inc-transform]We now have a function, a place to store the counter value and way to make the counter increment. How can we observe changes in the data model and respond to them? For example, how do we know when the counter value has changed so that we can draw it on the screen? In this simple application, the dataflow engine will be handling this for us. This combined with the Data Renderer will allow us to see changes until we have more time to spend on rendering.
In the next section we will see how to customize the notifications which are sent out for rendering.
If we make the changes above and refresh the browser window, we will see nothing. We have defined behavior but now we need to send an appropriate message to actually see it work.
In the file tutorial-client/app/src/tutorial_client/start.cljs you
will find the create-app function which is used to create and start
a new application. Notice that there is a call to p/put-message
which will add a message to the input queue which feeds into the
application.
Change this line to send an :inc message as shown below.
(p/put-message (:input app) {msg/type :inc msg/topic [:my-counter]})Now refresh the browser and you should see that the value under
:my-counter is now 1. In the next section we will add a way to
increment the counter for the user interface.
The tag for this step is v2.0.1.