-
Notifications
You must be signed in to change notification settings - Fork 0
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
As usual, start the dev tools. You should always have them running. This will be your last reminder.
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}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.
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))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.
The tag for this step is v2.0.4.