Skip to content

Simulating Service Push

Christian S. edited this page Jul 11, 2013 · 8 revisions

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

The application now has a simple counter which can be incremented by some action in the user interface. The plan for this tutorial is to create an application where each user connected to a service will see the counters of all of the other connected users. This means that the application will need 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, this section will show you how to build a simple service simulator. This will allow you to continue work on the client side of the application and delay work on the service until it is actually required.

Pedestal-app makes it easy to work on different parts of an application in isolation. This will allow you 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 you will build the service simulator and create a way to store these "other counter" values. While doing this, you will be introduced the following concepts:

  • How pedestal-app applications communicate with the outside world
  • Simulating services

Starting the development tools

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

Storing other counters

The application's behavior must be updated to support receiving and storing other counter values. To do this, you will add a new transform function. This transform will be named swap-transform and will simply get the value associated with the :value key in the message and return that as the new value in the data model. It can be used any time you would like to replace a value in our data model.

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

Add a new entry in the transform vector of our dataflow definition which will respond to any message with type :swap and any path. As you 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 you will send a message like the one shown below.

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

In this message "abc" represents the other user's id or name.

Simulated service input

Create a new ClojureScript file tutorial-client/app/src/tutorial_client/simulated/services.cljs for the simulated service. This implementation will need to occasionally send a message to the application describing the current state of various counters.

The code for the service 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 defines a service (MockService) which, when started, will send messages to the application (via receive-messages) which report updates to the two counters. One counter is updated every two seconds and the other every five seconds. When MockServices is created it must be passed the application to which it will send messages. 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 is being used.

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. Creating a timeout in this way will work in both Clojure and ClojureScript code. Because this is a ClojureScript file, you could have used JavaScript interop here and called js/setTimeout instead.

Starting the simulated service

The main function in tutorial-client.simulated.start is where the application begins execution when you are viewing the application in the Data UI aspect (this is configured in tutorial-client/config/config.clj). This is where the simulated service should be started.

First, require some additional namespaces

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

and 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

Up to this point, the emitter you have been using is reporting all changes. Replace that emitter with the one shown below.

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

This emitter configuration will only show updates to [:my-counter] and [:other-counters :*] (any of the other counters). By setting up the emitters in this way, it will be easy to turn on and off individual emitters during development.

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])]]})

Back in the browser, if you refresh the Data UI, you will see counters for two other players which will be updated every few seconds.

Next steps

Using a simulator in this way allows you to see how the application will work when it is being used by multiple users. This kind of thing is hard and time consuming to simulate manually. It also allows you to quickly change things on the client when you need to, waiting to change back-end services until client needs have stabilized.

This is only one half of the simulator. In the next section we will simulate publishing the local counter to other users.

The tag for this section is v2.0.4.

Home | Simulating Effects

Clone this wiki locally