An attempt to implement something similar to Spring Boot in Clojure. The idea is to provide sane defaults and a reasonable template to start a new web application project in a professional context.
Since there is already a project named Clojure Boot this experiment is clj-weba. Supposedly this doesn't mean anything.
The code was presented in the awesome ClojuTre 2014 event.
By professional I mean that certain concerns should be addressed which are not relevant in all hobby projects. I do not think the Clojure needs heavy opinionated frameworks and this is not aimed to become such.
- Sane logging (UTF-8, Logback based through clj-log)
- Reasonable HTTP access log, Lolog
- Embedded light-weight server (HTTP-kit)
- Support REPL workflow (start/stop/restart hooks in user.clj)
- Heartbeat URL (status page)
- External configuration outside the JAR (with type checking to prevent mistakes)
- Web application and Ring separated (to allow functional unit testing without HTTP protocol and server)
- Localization support
- JSON serialization support
- Retry macro
- Sane exception handling
- Client-side Javascript error logger (Why? Thoughtworks explains
- Interesting source/properties tests (see below)
- Performance test, using clj-gatling
- Relational DB support
- API support (Swagger)
- Context sensitive authorization
These are recurring problems but much more context sensitive. More difficult to provide a "default solution" which would make sense.
Obviously certain libraries have been selected such as Compojure. Beoynd the obvious there are a few worth mentioning:
- The software provides route to the status page. In development mode the status page contains settings. In production mode it's just OK for ping and monitoring.
- build-id.txt file is used to determine the build version if it exists. Generate during the CI build to automagically access the version.
- install-history.txt is used to show installation history. Generate in your deployment pipeline to automagically access the installation history.
- Localization support is for fi/sv at the moment and JS-based. Change this if you need it, it's just an example.
- Logback configuration automatically loads from logback.xml.
lein uberjar
java -jar target/weba-standalone.jar
Then you can try the front page: http://localhost:8081 The status page should respond at: http://localhost:8081/fi/status
A sample session's output might look like this
2014-11-25 08:56:29.709 INFO weba.server: Starting server, version dev
2014-11-25 08:56:29.714 INFO weba.settings: logback configuration reset: /Users/anttivi/work/projects/weba/resources/logback.xml
2014-11-25 08:56:29.740 INFO weba.server: Server started at port 8081
2014-11-25 08:56:33.397 INFO lolog.core: Request 2 start. remote-addr: 0:0:0:0:0:0:0:1 ,method: GET ,uri: /fi/ ,query-string: ,user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36 ,referer:
2014-11-25 08:56:33.416 INFO lolog.core: Request 2 end. Duration: 20 ms. uri: /fi/
2014-11-25 08:56:33.517 INFO lolog.core: Request 3 start. remote-addr: 0:0:0:0:0:0:0:1 ,method: GET ,uri: /favicon.ico ,query-string: ,user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36 ,referer:
2014-11-25 08:56:33.518 INFO lolog.core: Request 3 end. Duration: 1 ms. uri: /favicon.ico
Unit testing a web application is quite possible. A sample is included and the test run looks like this:
lein test
lein test rest.statuspage-test
2014-11-25 10:55:29.461 INFO lolog.core: Request 2 start. remote-addr: localhost ,method: GET ,uri: /fi/status ,query-string: ,user-agent: ,referer:
2014-11-25 10:55:29.544 INFO lolog.core: Request 2 end. Duration: 280 ms. uri: /fi/status
Ran 1 tests containing 1 assertions.
0 failures, 0 errors.
The interesting point is that the test is almost purely functional. The logging is a side-effect, but it would be a bit difficult to trace possible errors without it in CI builds. To make unit testing possible, the application logic (Ring wrapper stack) is separated from the web server. The test uses the Ring stack but doesn't start up web server.
This has two obvious benefits: the tests run faster and they can be executed simultaneously.
First, start the application in REPL or from the uberjar.
Performance test runs with this command
lein test :performance
The test is very simple and straightforward. The perf tests are excluded from the normal lein test runs using test-selector in project.clj. Test selectors are nice.
Look into clj-gatling for more information about the perf test library.
Go to http://localhost:8081/api/i18n/fi and the software returns a JSON containing the localization keys and values. The idea is to use these keys in your UI. This assumes your UI is Javascript-based rather than HTML rendered in the backend, but I suppose this is a valid assumption these days.
You should get something like this back from the URL:
{"ultimate":{"pwner":"Conan the Cimmerian"}}
There are three "levels" in my opinion:
- simply provide a ping URL which returns "OK" if everything is fine
- provide easy access to see the settings
- provide access to system information
Options 1 and 2 are here. I have done option 3 but modern monitoring systems seem to make it obsolete.
Here's what the option 2 might look like:
There are things which are relatively easy to check automatically and a source test is provided which checks the following:
- Javascript source for console.log/debug calls which require developer tools to work. Not a good idea in production.
- localization properties files have matching keys
- pre/post conditions are defined properly. Clojure compiler doesn't warn if they are incorrectly defined.
Using the same pattern it's possible to check more interesting things from Clojure source code using the Reader and building on the homoiconocity of the language. To my understanding, despite being very awesome, Haskel and all that Hindley-Milner stuff will not do this :)
While much of the code has been written by me, much of it has been borrowed. I can't recall everything but the code is heavily based on Aituhaku and Aitu projects written for Finnish National Board of Education. The code for that project was written partly in Finnish because that's how we agreed on the project. I translated the code to english and modified it to suit the purpose of serving as a more general example.
The code is without unit tests. The tests actually exist, but I have translated and modified the code and didn't bother to translate the tests to match this modified version. If this project evolves towards something more mature it will come with proper test suite.
Nothing like this seems to exist.
Copyright © 2014 Antti Virtanen
Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.