November 5, 2022

deps.edn and monorepos IX (Polylith)

This is part of an ongoing series of blog posts about our ever-evolving use of the Clojure CLI, deps.edn, and Polylith, with our monorepo at World Singles Networks.

The Monorepo/Polylith Series

This blog post is part of an ongoing series following our experiences with our Clojure monorepo and our migration to Polylith:

  1. deps.edn and monorepos
  2. deps.edn and monorepos II
  3. deps.edn and monorepos III (Polylith)
  4. deps.edn and monorepos IV
  5. deps.edn and monorepos V (Polylith)
  6. deps.edn and monorepos VI (Polylith)
  7. deps.edn and monorepos VII (Polylith)
  8. deps.edn and monorepos VIII (Polylith)
  9. deps.edn and monorepos IX (Polylith) (this post)
  10. deps.edn and monorepos X (Polylith)
  11. deps.edn and monorepos XI (Polylith)

Part IX

Hard to believe but it has been almost a year since my last post in this series!

It has been a busy, heads-down year at work, getting a lot of new features launched and ploughing ahead with our migration to Polylith.

At this point, we have 112 components (with 110 implementations), 17 bases, and 20 projects, totalling 100,984 lines of code, out of a total of 132,765 lines -- so we are 76% through our migration!

New Relic

A year ago, I was pleased that New Relic released 7.4.0 and gave us compatibility with JDK 17. They recently released 7.11.0 and JDK 19 support. For years we've stayed with LTS versions of the JDK, even though we're not using the Oracle binaries. With the CVEs found against JDK 17, we moved to JDK 18 in production and we're on JDK 19 in dev/CI right now, so we're pleased to see the update from New Relic, so we can move forward with JDK 19 and, via --enable-preview, we can start to take advantage of virtual threads!

Automating Builds

We've continued to move our ad hoc scripts into build.clj and we discussed whether Babashka would make sense for us. It's an amazing project, and I've adopted it in some of my OSS projects, but we ultimately decided that we're already reliant on build.clj and the JVM so as long as we can continue to leverage that, we have no need for additional tools.


We've continued our migration to Polylith. We're very close now to getting rid of one of our "big" legacy subprojects (our "kitchen sink" worldsingles subproject -- just two namespaces left to refactor). And we have just three legacy projects to move to bases and refactor the common code to components. We're very happy with the incremental testing that this migration has enabled: our build times have generally been cut dramatically, unless we touch a "brick" that is widely used (which still happens while we are refactoring but we expect will happen a lot less once we're "done").

We continue to contribute to Polylith, with recent PRs to address classloader and memory usage issues, as well as support for :as-alias, and the maintainers continue to be responsive so we're still happy to recommend this as an architectural approach.

We now have two swappable component implementations. I've talked before about our HTTP client component (using either Hato on modern JDKs or http-kit on legacy JDKs) but we also have two i18n implementations now. Our traditional i18n component uses a database-backed implementation as part of our content management system that supports over a dozen languages, with dedicated translators using our Admin app to work with out content, that can then be promoted to production automatically. In order to support our UI/UX folks, we've added an i18n implementation that uses a local JSON file, so that they can work on our (Selmer) HTML templates and not need all of the machinery that our production system requies. This swappable implementation allows them to work with a local JSON file to specify all the keys in multiple languages, while keeping the HTML template format the same as our main system. We have a project that builds a small "preview" JAR that our UI/UX folks can run locally (in a Docker instance -- their choice) and they can work with a JSON file. Then we take those HTML templates, and incorporate the JSON content into our database, and everything "just works" in our production system. Another "win" for Polylith and swappable component implementations!


Portal has become increasingly important to my day-to-day workflow. I have recently adopted the nREPL middleware so that I can completely ignore the nREPL window that Calva provides and rely entirely on the Portal window displaying results.

I've enhanced my VS Code/Calva/Portal setup as well as my dot-clojure dev/repl startup. Feel free to DM me on Slack with any questions about my setup. I hope to find some time to do some screencasts, showcasing my VS Code/Calva/Portal workflow.

CVE Checking

In part 8, I talked about nvd-clojure and using it from the CLI. We've since switched to clj-watson because we prefer the output it produces, highlighting the dependency path to the vulnerabilities and the recommended updates, if known.

We've recently migrated from Jetty 9 to Jetty 11, via Ning Sun's excellent Jetty 10/11 adapter which provides very slick WebSocket socket (which we'd had to do manually with Jetty 9). When we first moved to 0.17.9, clj-watson warned about several CVEs against Jetty 10 and its dependencies, so it was nice to upgrade to 0.18.1 and see all those CVEs go away!

Tags: clojure polylith monorepo new relic