Skip to content

Simulating Service Push

Brenton Ashworth edited this page Jul 2, 2013 · 8 revisions

Simulating Service Push

We now have a simple counter which will be incremented when clicked. The plan for this tutorial is to create an application where each person connected to the service will see all of the other connected counters. This means that the application will need to be able to send its counter value to a service and receive other counter values.

Instead of building the service and working on actually transmitting messages over a network, we are going to build a simple service simulator. This will allow us to continue work on the client side of our application and delay work on the service until it is actually required.

Pedestal makes it easy to work on different parts of an application in isolation. This will allow us to clearly define the data which must be provided to the client in order to support the functions of the user interface.

In this section we will build the service simulator and create a way to store these "other counter" values. While doing this, we will be introduced the following concepts:

  • How Pedestal applications communicate with the outside world
  • simulating services

Starting the dev tools

As usual, start the dev tools. You should always have them running. This will be your last reminder.

Storing other counters

First we will provide support for receiving other counter values. To do this we will add a new transform function. This transform will simply get the value associated with the :value key in the message and return that as the new value. This transform will be called swap-transform. It can be used any time that we would like to replace a value in our data model.

(defn swap-transform [_ message]
  (:value message))

We add a new entry in the transform list of our dataflow definition which will respond to any message with type :swap and any path. As we continue building this application, :swap will be used often to updated values.

(def example-app
  {:version 2
   :transform [[:inc  [:my-counter] inc-transform]
               [:swap [:**]         swap-transform]]
   :emit [{:init init-main}
          [#{[:*]} (app/default-emitter [:main])]]})

To add other counter values we will send a message like the one shown below.

{msg/type :swap msg/topic [:my-counter "abc"] :value 42}

Simulated service input

Create a new ClojureScript file app/src/tutorial_client/simulated/services.cljs for the simulated service. This code will need to occasionally send message to the app which describe the current state of various counters.

The code is shown below.

(ns tutorial-client.simulated.services
  (:require [io.pedestal.app.protocols :as p]
            [io.pedestal.app.messages :as msg]
            [io.pedestal.app.util.platform :as platform]))

(def counters (atom {"abc" 0 "xyz" 0}))

(defn increment-counter [key t input-queue]
  (p/put-message input-queue {msg/type :swap
                              msg/topic [:other-counters key]
                              :value (get (swap! counters update-in [key] inc) key)})
  (platform/create-timeout t #(increment-counter key t input-queue)))

(defn receive-messages [input-queue]
  (increment-counter "abc" 2000 input-queue)
  (increment-counter "xyz" 5000 input-queue))

(defrecord MockServices [app]
  p/Activity
  (start [this]
    (receive-messages (:input app)))
  (stop [this]))

This code creates a service object which, when started, will send messages to the application which report updates to two counters. One counter is updated every two seconds and the other every three seconds. When MockServices is created it must be passed the application so that it can send messages to it.

The code above should be fairly clear. There are two interesting new things here: the io.pedestal.app.util.platform namespace and the Activity protocol.

The Activity protocol is used for things which can start and stop. This mock service and the real service will implement the same protocol so downstream code does not have to know which implementation it is dealing with.

The io.pedestal.app.util.platform namespace contains functions which require platform specific implementations. It is used in the example above to create a timeout. We are writing Clojure code here which we can easily test as Clojure code. The target platform is JavaScript. This means that to schedule something to run in the future we need to use timeouts. When running on the Java platform, JavaScript timeout behavior is simulated with a ScheduledExecutor.

Starting the simulated service

The main function in tutorial-client.simulated.start is were our application begins execution when we are viewing the application in the Data UI aspect. This is where the simulated service should be started.

First we require some additional namespaces,

[io.pedestal.app.protocols :as p]
[tutorial-client.simulated.services :as services]

then we create and start MockServices.

(defn ^:export main []
  (let [app (start/create-app d/data-renderer-config)
        services (services/->MockServices (:app app))]
    (p/start services)
    app))

Reporting change

We will need to add an input to the default emitter so that it will report changes to the other counters.

:emit [{:init init-main}
       [#{[:my-counter] [:other-counters :*]} (app/default-emitter [:main])]]

After all of the changes above, the dataflow definition should now look like the one shown below.

(def example-app
  {:version 2
   :transform [[:inc  [:my-counter] inc-transform]
               [:swap [:**]         swap-transform]]
   :emit [{:init init-main}
          [#{[:my-counter] [:other-counters :*]} (app/default-emitter [:main])]]})

After a refresh, the Data UI should now display two other counters.

Next steps

The tag for this step is v2.0.4.

Home | Simulating Effects

Clone this wiki locally