Edge is a complete working website written in the Clojure programming language, integrating a set of software components and libraries.

Broken windows!

This documentation currently contains broken links as we work to fully modularize Edge. Please bear with us while we fix things.

1. Introduction

1.1. Why Edge?

Clojure is a wonderful language, boasting features that provide massive benefits to developers. It’s impossible to explain the numerous reasons Clojure is a much better choice than mainstream alternatives such as JavaScript. In order to appreciate the benefits, you have to try it for yourself.

Unfortunately, it can take a lot of time and effort to put together a development environment for a Clojure project that really shows off what a Lisp can do. That’s why we’ve created Edge.

1.2. Who is this for?

Edge is intended for both serious and educational use, including by students learning Clojure as a programming language, school teachers, college lecturers, as well as professional Clojure programmers in small and large organisations.

It is also designed as a spring-board for your own projects, whether serious or for fun.

1.3. Open Source

The complete system, including the Clojure language, the Java Virtual Machine on which it runs, the Clojure code within Edge and the numerous Clojure and Java libraries that are employed, are licensed with open-source licenses.

1.4. Performant

Edge’s design goals have included performance and scaleability, so is ideal for websites that need to cope with a reasonably high volume of web requests.

Edge uses yada, which is built on aleph, which is built on Netty. Netty is used a large companies such as Google, Apple and Facebook for their most demanding workloads. Therefore, with the correct tuning, the system will scale. However, there are many factors that affect performance and you should measure the performance of your overall system to ensure it meets your demands.

1.5. Modular

Edge is modular. New functionality can be added to Edge as discreet modules. Existing functionality (including examples and documentation) can be cleanly removed.

Examples

Directories below lists the sub-directories contained in the Edge repository and their purpose.

Table 1. Directories
Directory Purpose

bin

Shell scripts

phonebook-api

An example API implemented in Clojure with yada. This API is covered in detail in Phonebook API.

phonebook-app

A single page application for the phonebook-api, written in ClojureScript using Figwheel.

phonebook-graphql

A GraphQL version of the phonebook-api example, with support for subscriptions.

graphql-ws

An example of an internal library, incubating in Edge.

doc

Documentation (such as what you’re reading).

main

Declares Edge’s dependencies (deps.edn) and configuration (config.edn). Includes common components such as the web server, templating engine and inter-component messaging.

2. Phonebook API

Edge contains a phonebook API which provides access to names and telephone numbers in a simple database. It demonstrates how to build an API with yada with content negotiation.

The HTML 'representation' of the API is also somewhat useable, demonstrating a quick approach to creating, updating and deleting data via web forms.

  • bidi:uri:edge.phonebook.routes/phonebook-index[Phonebook]

Edge also bundles a third-party utility called Swagger that allows you to access and test this API.

  • bidi:uri:edge.web-listener/swagger[Swagger UI,path-info="/",query-params={"url" "/phonebook/api/swagger.json"}]

3. Phonebook App

To demonstate a client that calls out to the phonebook API, a ClojureScript 'single page application' is provided.

The application is compiled with Figwheel. In development mode, Figwheel injects some code into the application to provide a HUD (Head Up Display), rapid feedback.

  • bidi:uri:edge.phonebook-app.routes/phonebook-app[Phonebook (ClojureScript)]

Under the hood

4. Configuration with Aero

Everything in Edge is driven from configuration data, so let’s start there.

Our configuration is defined as a map, in main/resources/config.edn.

This is a standard Clojure EDN file that contains a few custom tag literals that combine to make the EDN format more suitable for configuration. These tag literals are defined in one of our most popular libraries, Aero.

There’s a function in edge.system that reads the config, let’s examine it:

link:src:edge.system/config[]
  1. The configuration file is loaded, as a resource (so this will work even if Edge is running from a Java JAR).

  2. Aero’s read-config function is called, with the value of the profile parameter.

One extremely useful tag literal contributed by Aero is #profile. This allows us to switch between different flavors of configuration. We use this feature when we need to specify different settings in different environments. For example, it’s common to need different environments (development, test, producation, etc.) to be setup slightly differently.

With our #profile tag, we can specify each environment in place (rather than maintain a separate configuration file for each environment).

One of the entries in the configuration map is :edge/web-listener. Let’s see what the value is when we read it with the profile argument above set to :dev:

link:config:dev:edge/web-listener[]

And now with :prod:

link:config:prod:edge/web-listener[]

With Aero’s #profile tag, we can specify (and document the intention of) difference, explicitly, where it occurs, without duplicating commonality. These are significant benefits as we scale up.

A key part of the configuration map is used to define the componentry (or, rather, component tree) that makes up the system. This configuration is nested under the :ig/system key (how it works is covered in the next chapter). Each component is given its own configuration.

What’s the difference between #ref and #ig/ref?

Configuration values can be literal values (strings, numbers, nested maps, etc.). However, in order to avoid duplication, often it’s useful to refer to configuration that’s already been specified. This can be achieved using Aero’s #ref tag literal, followed by a path to the configuration that should be 'copied in'.

For example, we can copy in the port of our web-listener like this:

#ref [:edge/web-listener :edge.web-listener/port]

Integrant components may also refer to each other, as dependencies. This is a rather different kind of reference, indicated by a different tag literal, #ig/ref. To declare a dependency from one component to another, we use the #ig/ref tag literal in the component’s configuration.

For example, if our :edge/web-listener component is to depend on :edge/event-bus, we declare the relationship like this:

:edge/web-listener {:edge/event-bus #ig/ref :edge/event-bus}

5. System initialisation with Integrant

Edge uses [integrant] to build a system of components from the configuration. The actual integrant configuration is found in the :ig/system entry. We call this the system configuration.

The application is modularized into runnable components. The system configration is a normal Clojure map, with an entry declaring each component, along with any configuration the component might require.

Regardless of whether the application is running in development or production, the system configuration is produced in the same way. To see how, we need to look at main/src/edge/system.clj:

edge.system
link:src:edge.system/system-config[]
  1. The config function (defined above) is called with the given profile (this might be :dev, :prod or indeed any other profile).

  2. The configuration contains integrant declarations, which we’re interested in here, so we pull out the integrant section from the configuration.

  3. This trick will ensure that the namespace of any component we declare is automatically required. This is necessary because integrant relies on Clojure’s multimethods to work, and we need the corresponding defmethods declared.

  4. Finally we return the Integrant system configuration.

The system configuration is now passed to Integrant's integrant.core/init function.

See Components for a description of each of the components declared in the system configuration.

6. Serving web requests with bidi and yada

Edge has a full web stack built-in, using JUXT’s bidi and yada libraries.

Bidi is a data-driven router. Further information can be found on bidi’s GitHub project page.

Let’s see how Edge implements the classic 'Hello World!' example. srcloc:edge.hello/hello-routes[titlecase] contains a function which returns a bidi route, which is always vector containing a couple of elements [1].

The first element is a pattern, in this case, "/hello".

Example 1. Hello World! with yada

7. Swagger

Rather than start with a Swagger specification and generate boilerplate code, Edge encourages a fast-turnaround approach where the API is declared in Clojure code/data, which generates Swagger documentation and handles API requests at the same time. This ensures the documentation is always aligned to the code.

Edge shows 2 different ways of publishing Swagger descriptions with yada. These descriptions have multiple uses. For example, you can use a Swagger description to generate an API on Amazon Web Services (AWS) using their API Gateway Tool, or of course to drive the Swagger UI which is built in to Edge.

The first method is to use yada/swaggered to wrap a bidi structure containing yada resources. The disadvantage of this approach is that the Swagger description is in the same URI hierarchy as your API.

The second is more flexible, and allows you to publish your Swagger descriptions separately from your API. This is the approach demonstrated by the Phonebook example.

8. GraphQL

Edge also demonstrates GraphQL support powered by Lacinia.

subscription {
  personupdates {
    id
  }}

9. ClojureScript development

9.1. Browser REPL

You can upgrade the REPL to a browser REPL

(cljs-repl)

Get back to the server repl with :cljs/quit.

Alternatively you can connect to the REPL over port 5600

10. Style

10.1. Sass CSS

To change the style, edit the .scss files in the sass directory. Changes will be compiled automatically. If you are building a ClojureScript app, these changes will be automatically reloaded into the browser for immediate feedback.

Security

11. Authentication

There is a blog on JUXT’s website about yada authentication here. The examples linked below correspond to the examples in the article.

11.1. Basic Authentication

Click here to test in your browser–you’ll need to use the user alice

Or use the following command test the resource:

curl -i {edge-url-root}/authn-examples/basic -u alice:password
Exercises
  1. What is the result if you change the username alice for another username?

11.2. Custom Authentication

curl -i {edge-url-root}/authn-examples/custom-trusted-header -H X-WhoAmI:alice

11.3. Form Authentication

Deployment

When you’ve modified Edge to your own requirements, the day will come when you’ll want to show off your new application to the world (or your client, or customers).

Edge can be deployed in a number of different ways, ranging from fully packed to fully unpacked.

12. Packed deployments

We use the term 'packed deployment' to mean that everything required at runtime is built (if necessary) and packed into an archive, ready for deployment and execution in the target operating environment.

It is common to create 'uberjars' containing pre-compiled Clojure code, togther with dependent pre-compiled library code from Clojure and other JVM languages.

Advantages of this approach include:

  • Start up time is fast, ideal for AWS Lambdas and for coping with sudden spikes in traffic, for example, using AWS auto-scaling groups.

  • Immutable

Disadvantages include:

  • Uberjars can be large, meaning they can be costly to build, store and transfer across network links.

  • Lossy uberjar build process, possible licensing issues and ambiguities.

  • Require a full redeploy on every change

  • Distance between dev and prod

12.1. Creating an uberjar

From the top-level directory, run the following:

edge$ bin/uberjar main

This creates an uberjar named main.jar.

Note
By default, uberjars are built using the capsule strategy in JUXT’s pack tool.

12.2. Running the uberjar

edge$ java -jar main.jar

13. Unpacked deployments

The repository is cloned in the target operating environment.

Advantages:

  • Fast to

13.1. Execution

For unpacked deployments, use the bin/run script to start the system.

bin/run script
link:resource:run[]
# Systemd

[Unit]
Description=Edge (1)

[Service]
Type=simple
Environment="DISPLAY=:0"
ExecReload=kill -HUP $MAINPID
Restart=always
User=app (2)
WorkingDirectory=/home/app/edge/main (3)
ExecStart=/home/app/edge/main/bin/run %i (4)

[Install]
WantedBy=multi-user.target
  1. This is the name of the application in systemd’s journal, go ahead and change this.

  2. The application 'user'. Consider creating a separate non-root user for running the application.

  3. For unpacked deployments, set the working directory to where main resides.

  4. This is the full location of the bin/run script.

Building on Edge

14. How to build your own project upon Edge

Edge is designed to be built upon. You are free to make whatever changes you like (additions and deletions), in accordance with the license.

14.1. Licence

The MIT License (MIT)

Copyright © 2016-2018 JUXT LTD.

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

14.2. Code

We recommend adding new Clojure namespaces under a new directory under src. Suppose your organisation is called ACME INC., then you might want to create src/acme or src/com/acme, or similar.

It’s also a good idea to keep the code in src/edge around for as long as possible—it’s full of useful knowledge and you never know when you might need to refer to it. Once you’re ready for production, you can dispense with the parts you don’t need.

14.3. Documentation

We think it’s a good idea to continue to document your project as we’ve done with Edge. You might want to update the CSS stylesheet, source highlighting and other Asiidoctor attributes. They can be found in doc/resources/doc/asciidoctor/attributes.edn.

link:resource:doc/asciidoctor/attributes.edn[]

If the footer has a copyright declaration from JUXT, you can update this by updating doc/resources/doc/docinfo/docinfo-footer.html. This file name must not be changed as it is looked for by Asciidoctorj when generating the HTML output.

link:resource:doc/docinfo/docinfo-footer.html[]

Appendices

Appendix A: Components

Component Description

:edge/web-listener

An HTTP server demonstrating JUXT’s bidi and yada libraries.

:edge.phonebook/db

A 'stub' database containing phonebook entries

:edge/executor

An executor to schedule futures and other deferred tasks

:edge/event-bus

An manifold bus to propagate events through the system

:edge.graphql/schema

A component to read a Lacinia GraphQL schema

:edge/asciidoctor

A parser engine for AsciiDoc documents (running in the JVM thanks to JRuby)

:edge/selmer

Our preferred templating engine

A.1. edge/web-listener

This component starts the web listener, which is built in Aleph (which, in turn, is built on Netty).

The listener is started with a data-description of the routes that it will server. The router is called bidi. You can find more about bidi in Serving web requests with bidi and yada.

A.2. edge.phonebook/db

Note
TBD

A.3. edge/event-bus

Note
TBD

A.4. edge.graphql/schema

Note
TBD

A.5. edge/asciidoctor

Note
TBD

A.6. edge/selmer

Note
TBD

Bibliography


1. The route returned forms part of a later data structure, but this does not concern us here