Skip to content

Connecting to the Service

Ryan Neufeld edited this page Jul 9, 2013 · 10 revisions

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

In this section you will connect the client and service projects. This will involve writing a real service implementation in the client.

The following pedestal-app 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, you have already implemented a service named MockServices which simulates the real service. Now that a real back-end service exists, you can write the code which connects to it.

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

The mock version of the service has two parts: the services object which implements the Activity protocol and the function named services-fn to handle outgoing messages. The new version will do the same thing except that it 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]))

The 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.

All input goes through 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 request 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 the client to send and receive messages.

Wire up service code in the main function

The starting point for the development and production version of the application is the main function located in the tutorial-client.start namespace.

To use the new service, require the namespace

[tutorial-client.services :as services]

and then update the main function to create the service, start it and use the services/services-fn function to consume the effects queue.

(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))

Three new lines have been added to the main function. First create the service, providing it with the application.

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

Then arrange for the services-fn to consume all messages on the effects queue.

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

Finally, start the service.

(p/start services)

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

Single origin policy

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

In order for this application to work, it must be served from the service.

All of the compiled output of the application project is compiled to out/public. To get the service to serve this content, we simply need to create a symlink to this directory in the service project.

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 you can access anything compiled into out/public from the service server.

Configure API Server

Before you try the application, make one configuration change to the development application which will make the workflow a little better.

In the config file tutorial-client/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 you 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.

You 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. You need to stop it because of the change which was made to the config file.

To start the server project, enter that directory and run the, now familiar, commands for starting the server.

lein repl
(use 'dev)
(start)

Next, navigate to 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 you 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 you first went to the development server and clicked on the links to take you over to the service. You 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, you change the application code and then refresh the page, you will not see the change. This is because the service doesn't know anything about ClojureScript or compilation. It only knows that it is serving static files from the resources/public directory.

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

This is fine if you only need to do it once but if you find that you would like to work on the application, making a lot of changes while serving it 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 that is triggered when you reload a page in the development application. So you 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 but you may use any of the available aspects.

Next steps

This concludes Part 1 of the tutorial which covers all of the basics of pedestal-app applications. This should give you good grounding in pedestal-app and allow you to get started working on your own projects.

This is not, however, the end of the tutorial. In Part 2, you will get some new requirements which, as you implement them will introduce you to the following important pedestal-app concepts:

  • External JavaScript and externs files
  • Rendering without the DOM
  • Messages with parameters
  • Multi-screen applications and Focus
  • Continue functions
  • Parallel processing with Web Workers

The tag for this section is v2.0.14.

Home | Changing Requirements