Skip to content
Ryan Neufeld edited this page Jul 9, 2013 · 20 revisions

To follow along with this section, start with tag v2.0.0.

In this section, you will create a counter and the application will be able to receive messages which will increment the counter. This simple exercise will introduce you to the following concepts:

  • Messages
  • Transform functions
  • Dataflow definitions
  • The input queue
  • The Data UI

Starting the development tools

Before you make any changes to the application, start up the tools so that you can see the changes as you work.

cd tutorial-client
lein repl

Once you have a running REPL, run the following:

(use 'dev)
(start)

and then open a browser and navigate to http://localhost:3000. In the Tools Menu, click on the Data UI link. You will see the following text rendered in the browser.

:greeting
"Hello World"

This text is being rendered by the Data UI. The Data UI is a renderer which comes with pedestal-app and can be used to display any rendering data produced by the application. When working on a new application it is very useful to be able to see and even work with the application logic before any work has been done on a custom renderer.

The value being displayed here is a representation of the data model which is currently the value:

{:greeting "Hello World!"}

The Data UI is displaying this value by making a heading for each top-level key of the data model and then printing the value associated with each key. Below, you are going to make some changes which will illustrate how to define and change a data model. In a later part of the tutorial you will look a bit closer at why the data is rendered in this way.

Creating the counter

Creating a simple counter allows us to solve four problems common to all applications:

  1. Define a function which handles a state transition
  2. Trigger a state transition
  3. Define the location where a specific bit of state will be stored
  4. Notice when state changes so that we can do something about it

Messages

Before making any changes to code, it is important to understand how to trigger changes in a pedestal-app application. A pedestal-app application is essentially one big object. It contains state, and that state is changed when it receives a message. Messages are data.

At a minimum, each message has a type and a topic. The message's type is a dispatch value often used to determine which state transition function should be called. The topic may also be used for dispatch but, more importantly, it determines the location in the data model where the function will be applied. A message to increment a counter could 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. If :inc were a function we could think of this as meaning:

(update-in data-model [:my-counter] :inc message)

These are the semantics of a transform function.

Transform functions

The first thing that is typically changed in a new project is the application's behavior. In the generated files, the code for behavior is located in tutorial-client/app/src/tutorial_client/behavior.clj. It is important to note that this project layout is not required. As an application grows, it would not be a good idea to put all application logic in a single file. Pedestal-app projects do not enforce a specific source code layout.

Making a counter involves creating a function to handle the state transition for the counter value--this function will simply increment the value. In tutorial-client.behavior, 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-app, this is called a transform function. It takes two arguments (the old value and the input message) and returns a new value. The inc-transform function above ignores the input message and simply increments the old value.

Dataflow

Pedestal-app applications are written by creating pure functions, like the one above, and linking them together in a dataflow. Transform functions handle all inputs to the flow.

In the tutorial-client.behavior namespace, example-app refers to a description of the application's dataflow. Update the value under the :transform key to add inc-transform as a transform function to this dataflow.

(def example-app
  {:version 2
   :transform [[:inc [:my-counter] inc-transform]]})

The :version key is used to indicate the version of this definition format.

:transform contains a vector of vectors that define which transform function will be called when a message is received. Each vector has three elements.

[:inc [:my-counter] inc-transform]

The first element is the type, the second 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 writing a dataflow definition the data structures used convey some important meaning. Because a vector is used to configure transform functions, order is important. When order is not important, sets will be used.

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 of the message.

Defining the data model

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.

Wildcards can be used to dispatch many messages to the same function.

(def example-app
  {:version 2
   :transform [[:inc [:*] inc-transform]]})

This configuration would send any :inc message type to the inc-transform function; the location in the data model to be updated is determined by the path in the message. Note that this will only match a path with one element. To match any path, use :**.

[:inc [:**] inc-transform]

Observing change

The application now has a state transition function, a place to store the counter value and way to make the counter increment. How can this application observe changes in the data model and respond to them? For example, how would it know when the counter value has changed so that it could draw the new value on the screen? In this simple application, the defaults of the dataflow engine can be relied upon to report change. This combined with the Data UI will allow you to see changes until work can be done on rendering.

In the next section, you will see how to customize the notifications that are sent out for rendering.

If you make the changes above and refresh the browser window, you will see nothing. New behavior has been defined, but an initial message must also be sent to cause the counter to increment.

Sending a message

In the file tutorial-client/app/src/tutorial_client/start.cljs you will find the create-app function 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 that 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 section Increment the Counter you will add a way to increment the counter from the user interface.

Next Steps

The next section introduces some simple techniques for testing pedestal-app application behavior. If you are not interested in testing, checkout the v2.0.2 tag and jump straight to Increment the Counter.

The tag for this section is v2.0.1.

Home | Testing | Increment the Counter

Clone this wiki locally