Skip to content

Connecting to the Service

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

Connecting to the Service

Let's take a quick inventory of our project. We have a working client and service. In the client project there is a simulated service which receives and sends messages to the client.

In this section we will connect the projects and have the client talking to the service. This will involve writing a real service implementation in the client.

The following concepts will be introduced in this section:

  • Communicating with a service
  • Integrating application and service projects
  • Development while running against the real service

Implementing the connection to the service

In the tutorial-client project, we have already implemented a service named MockServices which simulates the real service. Now that we have a service, we will implement the real version.

Add a new source file named src/tutorial_client/services.cljs for the namespace tutorial-client.services.

In the mock version of the service we had created a services object and a function named services-fn to handle outgoing messages. In this version will do the same thing except that we will create an EventSource for receiving Server Sent Events and an XMLHttpRequest for sending outgoing messages.

The namespace declaration will require the following namespaces

(ns tutorial-client.services
  (:require [io.pedestal.app.protocols :as p]
            [cljs.reader :as reader]))

Our Services implementation will create an EventSource at the endpoint /msgs and will respond to msg events by reading the message string to Clojure data and placing the message on the input queue.

(defn receive-ss-event [app e]
  (let [message (reader/read-string (.-data e))]
    (p/put-message (:input app) message)))

(defrecord Services [app]
  p/Activity
  (start [this]
    (let [source (js/EventSource. "/msgs")]
      (.addEventListener source
                         "msg"
                         (fn [e] (receive-ss-event app e))
                         false)))
  (stop [this]))

The services-fn implementation will send messages as a POST to /msgs.

(defn services-fn [message input-queue]
  (let [body (pr-str message)]
    (let [http (js/XMLHttpRequest.)]
      (.open http "POST" "/msgs" true)
      (.setRequestHeader http "Content-type" "application/edn")
      (.send http body))))

The body of the messages is the messages data printed to a string using (pr-str message). The content type is set to application/edn.

That is all. This will allow us to send and receive messages.

Wire up service code in the main function

We now have to wire up this service to our application. The starting point for the development and production version of our application is the main function located in the tutorial-client.start namespace.

To use the new service, we need to require the namespace

[tutorial-client.services :as services]

and then update the main function as shown below.

(defn ^:export main []
  (let [app (create-app (rendering/render-config))
        services (services/->Services (:app app))]
    (app/consume-effects (:app app) services/services-fn)
    (p/start services)
    app))

We have added three lines to the main function. We create the service, providing it with our application.

(services/->Services (:app app))

We then arrange for the services-fn to consume all messages on the effects queue

(app/consume-effects (:app app) services/services-fn)

Finally, we start the service

(p/start services)

So now, when we run the development or production aspects of our application (these to aspects are configured to run the main function above), it will attempt to connect to the service.

Single origin policy

If we were to run this as it is, it would not work. This is because the service that we have created will attempt to connect to the server from which the JavaScript source was served. While running the client project that server is the application development server, not the service server.

What we would like to do is serve our application from the service server.

All of the compiled output of our application project is compiled to out/public. We can create a symlink to this directory in the service project to server files from there.

Symlink out/public in the service project

In the service project, do the following:

mkdir resources
cd resources
ln -s ../../tutorial-client/out/public

The resources directory is already on the classpath. For this to work, the out/public directory must exist.

With this in place we can access anything compiled into out/public from the service server.

Configure API Server

Before we try the application, let's make one configuration change to the development application which will make our workflow a little better.

In the config file config/config.clj, under the :application section, add the following key and value

:api-server {:host "localhost" :port 8080 :log-fn nil}

This tells the development server that there is a service running on localhost port 8080.

For any aspects which we would like to redirect to the service, add the following key and value

:use-api-server? true

Do this for the :development and :production aspects.

We are now ready to start everything up and try it out.

Running everything

If you are running the development server, stop it and start it in the usual way. We need to stop it because we made a change to the config file.

To start the server project, enter that directory and run:

lein repl

then evaluate the following in the REPL.

(use 'dev)
(start)

If we open the development project

http://localhost:3000

And then click on either the Development or Production links in the tools menu, the applications will be compiled and then we will be redirected to the service running on http://localhost:8080.

The application should now work. Start up multiple browsers and connect to the same URL to try it out.

Working on the application and using the service

You may have noticed that we first went to the development server clicked on the links to take us over the service. We did this instead of just going directly to the service. The reason for this is that clicking on the links in the development server will actually cause compilation to happen before redirecting to the service.

If while running from the service, we change our application code and then refresh the page, we will not see the change. This is because the service doesn't know anything about ClojureScript or compilation. It only knows that is serving static files from the out/public directory.

To trigger compilation, we have to go back to the development server and again click on the Development or Production links.

This if fine if only need to do it once but if we find that we would like to work on the application, making a lot of changes, from the service then there is a better way.

Watch

The watch function is located in the dev namespace and can be used to watch files for changes and trigger compilation. This will trigger the same compilation process so we can change scss, html templates or ClojureScript files and compilation will be initiated.

To compile the development aspect when files change run

(watch :development)

from the development project REPL.

To stop watching run

(unwatch)

The argument to watch is the aspect that you would like to compile. It is best to use the :development aspect so that compilation times are faster.

Next steps

This concludes the first part one of the tutorial where we cover all of the basics of Pedestal applications. This should give you are good grounding in Pedestal and allow you to get started working on your own projects.

This is not, however, the end of the tutorial. In part two, we will get some new requirements which, as we implement them will introduce us to the following important Pedestal concepts:

  • Rendering without the DOM
  • Messages with parameters
  • Parallel processing with Web Workers
  • Multi-screen applications and Focus

The tag for this step is v2.0.14.

Home | Changing Requirements

Clone this wiki locally