In the last part, we have discussed how to implement the API, how Duct helps with Dependency injection and we introduced a concept of boundaries. In this part, we will finish the project for production and discover some magic.

Secret magic

You may be wondering how is it possible that a request’s body was parsed to a map. There is one important thing we haven’t described yet. The Duct has got modules with default configurations for common middlewares like Ring defaults. The module is named :duct.module.web/api and it’s initialized in the project’s configuration. If you’re curious you can check the internals and see that it uses Muuntaja for marshalling.

The module puts middleware around :duct.core/handler (the main handler called from the HTTP servlet) when the system starts. This is default behaviour and it can be overridden, but we don’t want to go so far in this article.

When the Muuntaja middleware sees Content-Type in a request’s headers it can try to negotiate and parse the request (in our case to a map). If a request doesn’t have any known Content-Type, the attribute :body-params on the request object will be nil. 

As you can see now technically there is no hidden magic, but a beginner could be surprised or confused.


We have implemented the web service and now we want to run it in a staging or production environment. We need to have a stand-alone file that can be run with JDK. Let’s run the following command: lein uberjar

[lukas@hel:~/dev/flexiana/sms]$ lein uberjar
Compiling sms.boundaries.sms-gateway
Compiling sms.domain.message.impl
Compiling sms.domain.message.sender
Compiling sms.domain.message.service
Compiling sms.handler.api
Compiling sms.handler.api.message
Compiling sms.main
Created /Users/lukas/dev/flexiana/sms/target/sms-0.1.0-SNAPSHOT.jar
Created /Users/lukas/dev/flexiana/sms/target/sms-0.1.0-SNAPSHOT-standalone.jar

Now we can take the sms-0.1.0-SNAPSHOT-standalone.jar and run it everywhere, where JDK is installed.

You can simply test it from a command line. Before running it the Gateway URL must be set via an environment variable. Let’s try it:

[lukas@hel:~/dev/flexiana/sms]$ export SMS_GATEWAY_URL="http://localhost:8080/smsgateway"
[lukas@hel:~/dev/flexiana/sms]$ java -jar target/sms-0.1.0-SNAPSHOT-standalone.jar 
20-05-25 10:28:01 hel REPORT [duct.server.http.jetty:13] - :duct.server.http.jetty/starting-server {:port 3000}

As you can we have set the SMS_GATEWAY_URL variable and run the application. In the command line, we can see that the application has started on port 3000. So if the gateway service was running on port 8080 we would be able to test it by posting a message to http://localhost:3000/messages.

Duct uses by default Timbre for logging. In the development profile, all logs go to logs/dev.log file. But in the production, all logs are sent to stdout.


This article has introduced a Clojure framework, Duct, that helps programmers with building server-side applications. The Duct parts have been briefly described with the necessary details. All the described Duct’s parts have been presented with practical examples and tests.

A reader should be able to create a new server-side application, implement HTTP handlers, and call 3rd party APIs. Also, the reader has been taught how to write tests. Optionally run the tests from the favorite editor and at the end how to build a deployable JAR file and configure it.

This article has not exhausted all of Duct’s possibilities. There are more things that could be explained in future articles like:


[BobCA]: Clean Architecture, Robert C. Martin, Prentice Hall, 2017

[BobCC]: Clean Code, Robert C. Martin, Prentice Hall, 2009

[FowlerPEAA]: Patterns of Enterprise Application Architecture, Martin Fowler, Addison-Wesley, 2003

[XUnit]: XUnit Test Patterns: Refactoring Test Code, Gerard Meszaros, Addison-Wesley, 2007

Lukas Rychtecky is a senior software engineer at Flexiana (primarily working with Clojure), sokol and father who lives in Prague.

Read our next articles in your e-mail

Sign up to our newsletter