In/Clojure 2024: Developer Tooling For Speed And Productivity In 2024!
These are slides from my talk at In/Clojure 2024. These slides may not have all the context, they'll make better sense after you watch the talk.
If you have any problems using the tools I've mentioned here, or if you want help improving your Clojure programming workflow, drop me an email! (Or better still, book a session with me). I love talking to other Clojure programmers, exchanging tips and tricks, and learning from them.
- Why this talk?
- I want to write a new library or application!
- I use deps-new!
- What deps-new gives me
- You can even write your own deps-new templates!
- What can we as clojure devs do?
- References to learn deps-new
- I want to add more aliases to my repository!
- I use neil!
- Examples of using neil!
- Neil can do more than just add aliases!
- Neil can do more than just add aliases!
- What can we as clojure devs do?
- References for learning more about neil
- Okay, I am now ready to spin up a REPL and connect to it!
- I'm happy I never have to figure this out again!
- Templates for the win
- I use structured logging everywhere!
- I want beautiful Documentation
- I use Clerk: write beautiful notebooks and beautiful code-bases!
- Clerk: write beautiful notebooks and beautiful code-bases!
- I use tagref: clear separation of code and documentation
- Documentation for the team: tagref
- References for improving documentation
- I want to write clean, maintainable code!
- clj-kondo: My recommendation
- clj-kondo: My recommendation
- cljfmt: My recommendation
- What can we as clojure devs do?
- References for clj-kondo, cljfmt and zprint
- Can we improve the REPL experience?
- Debugging like I've never experienced before: Flowstorm!
- Debugging with Flowstorm
- References for learning more about Flowstorm
- Why Micro-services?
- Why Micro-services?
- Why Monorepos?
- So what goes wrong with Monorepos?
- I use Polylith from day one
- Let's create our Polylith monorepo!
- The terminology of Polylith: project
- The terminology of Polylith: base
- The terminology of Polylith: component
- How does all this tie together?
- How Polylith promotes Modular architecture
- Polylith: getting the best of micro-services and mono-repos
- What does the poly tool give me?
- References for learning more about Polylith
- There is so much more …
- Come say hi!
Why this talk?
So you'll show me your cool workflows! (help me get better!)
I want to write a new library or application!
I need to figure out …
- How to run my application
- How to run all the tests in my project
- How to build the artifacts of my app/lib
- How to deploy these artifacts so others can use them
Speaker Notes
The Javascript ecosystem, for all the abuse we hurl at them, has done an incredible job. The templating that people have access to out of the box is something we should all aspire to!
I use deps-new!
https://github.com/seancorfield/deps-new
clojure -Tnew lib :name me.vedang/depsnew_lib
tree depsnew_lib
RESULTS:
depsnew_lib
├── CHANGELOG.md
├── LICENSE
├── README.md
├── build.clj
├── deps.edn
├── doc
│ └── intro.md
├── src
│ └── me
│ └── vedang
│ ├── depsnew_lib.clj
└── test
└── me
└── vedang
└── depsnew_lib_test.clj
What deps-new gives me
- All the boilerplate!
- Clear Instructions in the README
- Aliases:
clojure -X:run-x
,clojure -X:run-m
Run your appclojure -T:build test
Run testsclojure -T:build ci
Build artifactsclojure -T:build deploy
Deploy artifacts
You can even write your own deps-new templates!
AND YOU SHOULD!
(looking at you, framework authors!)
What can we as clojure devs do?
- Does your favourite framework have a deps-new template? Add it!
- Do you find yourself copying the same code everywhere? Try and extract it into a template!
References to learn deps-new
- What is deps-new? github/deps-new/README
- Templates provided by the Community deps-new/README#templates
- How to write your own template? practical.li/create-deps-new-template-for-clojure-cli-projects
I want to add more aliases to my repository!
- How do I connect to a REPL?
- How do I setup logging?
- …
I use neil!
https://github.com/babashka/neil
- neil can add aliases for you!
- neil can do so much more!
Examples of using neil!
neil add cider
:cider ;; added by neil
{:extra-deps {cider/cider-nrepl {:mvn/version "0.47.0"}
djblue/portal {:mvn/version "0.52.2"}
mx.cider/tools.deps.enrich-classpath {:mvn/version "1.19.0"}
nrepl/nrepl {:mvn/version "1.1.1"}
refactor-nrepl/refactor-nrepl {:mvn/version "3.10.0"}}
:main-opts ["-m" "nrepl.cmdline"
"--middleware" "[cider.nrepl/cider-middleware,refactor-nrepl.middleware/wrap-refactor,portal.nrepl/wrap-portal]"]}
Neil can do more than just add aliases!
neil dep search next.jdbc
RESULTS:
:lib pro.juxt.clojars-mirrors.com.github.seancorfield/next.jdbc :version "1.2.674" :description "pro.juxt.clojars-mirrors.com.github.seancorfield/next.jdbc on Maven"
:lib net.cloudopt.next/cloudopt-next-jdbc :version "3.1.3.0-RELEASE" :description "net.cloudopt.next/cloudopt-next-jdbc on Maven"
:lib com.github.seancorfield/next.jdbc :version "1.3.999-SNAPSHOT" :description "The next generation of clojure.java.jdbc: a new low-level Clojure wrapper for JDBC-based access to databases."
:lib seancorfield/next.jdbc :version "1.2.659" :description "The next generation of clojure.java.jdbc: a new low-level Clojure wrapper for JDBC-based access to databases."
:lib com.layerware/hugsql-adapter-next-jdbc :version "0.5.3" :description "next.jdbc hugsql adapter"
:lib dev.weavejester/ragtime.next-jdbc :version "0.9.4" :description "Ragtime migrations for next.jdbc"
:lib hugsql-next-jdbc/hugsql-next-jdbc :version "0.1.3" :description "next.jdbc adapter for HugSQL"
:lib de.active-group/active-jdbc :version "0.2.1" :description "Functions to work with JDBC building upon next.jdbc."
:lib daaku/pgmig :version "2.0.1" :description "Database migrations for PostgreSQL using next.jdbc."
:lib taskmaster/taskmaster :version "0.0.4-SNAPSHOT-1" :description "Background publisher/consumer on top of Postgres, next.jdbc and hikari-cp"
Neil can do more than just add aliases!
neil dep add :lib com.github.seancorfield/next.jdbc
neil dep add :lib nextjournal/clerk :alias :cider
What can we as clojure devs do?
- Make it easy to add aliases to neil!
- I would like to add to existing aliases instead of creating new ones!
References for learning more about neil
- Using neil michielborkent/new-clojure-project-quickstart.html
- All the options github/babashka/neil/README.md
- My neil script, with aliases I use github/vedang/neil
Okay, I am now ready to spin up a REPL and connect to it!
clojure -M:test:logs-dev:cider
Speaker Notes
Wait, what's the logging alias?
Logging is hard!
So once I figured it out, I made a template!
I'm happy I never have to figure this out again!
neil add logs-dev
:logs-dev ;; added by neil
{:extra-deps {me.vedang/logger {:local/root "logger"}}
:jvm-opts
["-Dclojure.tools.logging.factory=clojure.tools.logging.impl/log4j2-factory"
"-Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.Slf4jLog"
"-Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector"
"-Dlog4j2.configurationFile=logger/log4j2-dev.xml"
;; Change logging.level to one of TRACE, DEBUG, INFO, WARN, ERROR
;; depending on requirement during development
"-Dlogging.level=DEBUG"]}
Templates for the win
clojure -Sdeps '{:deps {io.github.vedang/clj-logging {:git/sha "e009d366c827705f513ef9018ffd920a49ce19da"}}}' -Tnew create :template me.vedang/logger :name me.vedang/logger
tree logger
RESULTS:
logger
├── README.org
├── deps.edn
├── project.clj
├── resources
│ └── logger
│ ├── log4j2-dev.xml
│ └── log4j2-prod.xml
└── src
└── me
└── vedang
└── logger
└── interface.clj
7 directories, 6 files
I use structured logging everywhere!
http://pedestal.io/pedestal/0.7/reference/logging.html
(in-ns 'me.vedang.depsnew-lib)
(require '[me.vedang.logger.interface :as log])
(log/info :function :welcome
:action :starting
:args {:greeting "Hello World"})
{
"instant": {
"epochSecond": 1711161317,
"nanoOfSecond": 787564000
},
"thread": "nREPL-session-d6c4a5db-e57c-4efe-906b-7e5f1d0ad6e6",
"level": "INFO",
"loggerName": "me.vedang.depsnew-lib",
"message": "{\"function\":\"welcome\",\"action\":\"starting\",\"args\":{\"greeting\":\"Hello World\"},\"line\":3}",
"endOfBatch": true,
"loggerFqcn": "org.apache.logging.slf4j.Log4jLogger",
"contextMap": {},
"threadId": 41,
"threadPriority": 5
}
Prefer emitting logs as JSON events. This lets us push them to Observability tools easily in production.
I want beautiful Documentation
Clojure is REPL-first! Where are my notebooks?
Speaker Notes
We've been writing notebooks and refactoring them into usable code-bases all our lives. So why do we not have beautiful looking notebooks like the Python people?
I use Clerk: write beautiful notebooks and beautiful code-bases!
https://github.com/nextjournal/clerk
Write your comments like you write Markdown, done!
Clerk: write beautiful notebooks and beautiful code-bases!
;;; # 📈 Sunday Morning Fun With Our Group Meditation Data
;; First, we need to parse the data from the Whatsapp Group. Whatsapp
;; provides a `export-chat` feature that we can use, but there are
;; some things we need to fix first:
;; ## Whatsapp uses a ridiculous format for the date when the message was sent
^{:nextjournal.clerk/visibility {:code :show :result :hide}}
(def whatsapp-export-date-format
"Frankly, this is a ridiculous format that no one should use."
"MM/dd/yy, hh:mm a")
;; The problem isn't so much the format, as the fact that they make it
;; hard to parse. For example, they won't export the date as
;; `"05/01/23, 03:31 pm"`. They will export it as `"5/1/23, 3:31 PM"`.
;; Every part of this is meant to fail your parser. 🤷🏽♂️
What Clerk notebooks look like:
- https://recife.pfeodrippe.com/notebooks/recife/notebook/slow_start.html
- https://book.clerk.vision/#vega-lite
Speaker Notes
- Talk about Separedit!
I use tagref: clear separation of code and documentation
https://github.com/stepchowfun/tagref
(require 'clojure.math)
;; [tag:polynomial_nonzero] This function never returns zero.
(defn polynomial [x]
(+ (clojure.math/pow x 2) 1))
;;; in some other file
(defn inverse-polynomial [x]
;; This is safe due to [ref:polynomial_nonzero].
(/ 1 (polynomial x)))
Documentation for the team: tagref
tagref list-tags | awk '{print $1}'
RESULTS:
[tag:gifs_need_to_be_converted_to_video_format]
[tag:rules_for_deciding_when_we_should_update_a_concept_record]
[tag:an_important_thing_to_understand_about_repetition_records]
[tag:rules_for_deciding_when_we_should_update_a_chapter_record]
[tag:rules_to_mark_a_chapter_as_done_for_now]
[tag:edge_case_empty_concepts_when_updating_chapter_or_concept_records]
[tag:the_order_in_which_parsers/checkers_run_is_important]
[tag:next-question_is_the_heart_of_our_logic]
[tag:rules_for_picking_the_next_concept_for_this_student]
[tag:rules_for_picking_the_next_chapter_for_this_student]
[tag:field_name_change_needs_to_be_the_last_parser]
[tag:no_need_to_optimise_insertion_db_calls]
[tag:clojure.string/replace_using_replacement_function]
[tag:what_can_session_filters_contain?]
[tag:rules_to_mark_a_concept_as_done_for_now]
[tag:extend_protocols_for_automatic_conversion_of_data_to_and_from_pg]
[tag:meaningful_ring_responses]
[tag:what_to_keep_in_mind_when_updating_routes]
[tag:on_magic_links]
References for improving documentation
- Using Clerk book.clerk.vision
- Using Separedit github/twlz0ne/separedit.el
- Using Tagref github/stepchowfun/tagref
- Jack Rusher on the power of interactive notebooks (among other things): Stop Writing Dead Programs
I want to write clean, maintainable code!
https://github.com/clj-kondo/clj-kondo https://github.com/weavejester/cljfmt https://github.com/kkinnear/zprint
I do automatic Linting and Formatting on the code base!
clj-kondo: My recommendation
Export clj-kondo config for libraries you use! (create .clj-kondo
directory at root first)
clj-kondo --lint "$(clojure -A:dev:test:cider:build -Spath)" --copy-configs --skip-lint
(clojure-lsp
will do this automatically)
tree .clj-kondo
RESULTS:
.clj-kondo
├── babashka
│ └── fs
│ └── config.edn
├── http-kit
│ └── http-kit
│ ├── config.edn
│ └── httpkit
│ └── with_channel.clj
├── nextjournal
│ └── clerk
│ ├── config.edn
│ └── nextjournal
│ └── clerk
│ └── viewer.clj_kondo
└── rewrite-clj
└── rewrite-clj
└── config.edn
12 directories, 6 files
clj-kondo: My recommendation
Commit your .clj-kondo
folder!
cljfmt: My recommendation
(For Emacsen) Use Apheleia!
Runs code-formatter on the buffer "at the right time" with minimal disturbance.
What can we as clojure devs do?
Add clj-kondo
configuration for your favourite libraries!
References for clj-kondo, cljfmt and zprint
- clj-kondo Documentation. github/clj-kondo/clj-kondo
- cljfmt Documentation. github/weavejester/cljfmt
- The zprint Reference. github/kkinnear/zprint/doc/reference.md
- Apheleia for Emacs. github/radian-software/apheleia
Can we improve the REPL experience?
After all, this is where we are all day!
Speaker Notes
IF we can make the experience even 1% better, isn't that something amazing?
Debugging like I've never experienced before: Flowstorm!
neil add cider-storm
:cider-storm ;; added by neil
{:classpath-overrides
;; we need to disable the official compiler and use ClojureStorm
{org.clojure/clojure nil}
:extra-deps {cider/cider-nrepl {:mvn/version "0.47.0"}
com.github.flow-storm/clojure {:mvn/version "1.11.2"}
com.github.flow-storm/flow-storm-dbg {:mvn/version "3.13.1"}
djblue/portal {:mvn/version "0.52.2"}
mx.cider/tools.deps.enrich-classpath {:mvn/version "1.19.0"}
nrepl/nrepl {:mvn/version "1.1.1"}
org.openjfx/javafx-controls {:mvn/version "23-ea+3"}
org.openjfx/javafx-base {:mvn/version "23-ea+3"}
org.openjfx/javafx-graphics {:mvn/version "23-ea+3"}
org.openjfx/javafx-swing {:mvn/version "23-ea+3"}
refactor-nrepl/refactor-nrepl {:mvn/version "3.10.0"}}
:main-opts ["-m" "nrepl.cmdline"
"--middleware" "[flow-storm.nrepl.middleware/wrap-flow-storm,cider.nrepl/cider-middleware,refactor-nrepl.middleware/wrap-refactor,portal.nrepl/wrap-portal]"]
:jvm-opts ["-Dclojure.storm.instrumentEnable=true"
"-Dclojure.storm.instrumentOnlyPrefixes=me.vedang."]}
Debugging with Flowstorm
(in-ns 'me.vedang.depsnew-lib)
(defn foo [n]
(->> (range n)
(filter odd?)
(partition-all 2)
(map second)
(drop 10)
(reduce +)))
References for learning more about Flowstorm
- Flowstorm website
- @jpmonetta showing a full walk-through Reifying execution, the interactive programming missing piece
Why Micro-services?
Smaller teams, clear ownership!
Why Micro-services?
- Easier to test
- Clear contracts (API)
- Independent deployments
Why Monorepos?
- No version hell
- Easy refactoring across multiple services (in theory)
- Easy collaboration and on-boarding (in theory)
So what goes wrong with Monorepos?
- Contracts are unwritten
- Testing becomes messy (specifically, test-suite time)
- Refactoring, Collaboration, On-boarding Hell
Speaker Notes
Eventually, teams start building conventions and tooling and start refactoring.
The problem is, it's too late by now.
I use Polylith from day one
https://github.com/polyfy/polylith
Polylith is a set of conventions and tooling that has thought through all these problems!
Let's create our Polylith monorepo!
poly create workspace name:polylith top-ns:me.vedang :commit
tree -L 3
RESULTS:
.
├── bases
├── components
├── deps.edn
├── development
│ └── src
├── logo.png
├── projects
├── readme.md
└── workspace.edn
6 directories, 4 files
Speaker Notes
Addendum: you can also create a workspace in an existing repo: poly create workspace top-ns:me.vedang
The terminology of Polylith: project
projects/
Let's think through an example:
- Static Site : projects/weblogstatic (Artifact: Public Folder, Deployment: Github Pages)
- Dynamic Site : projects/weblogdynamic (Artifact: Uberjar, Deployment: Dockerfile, fly.toml)
The terminology of Polylith: base
bases/
Continuing with our example:
- Static Site: bases/serverstatic
- Dynamic Site: bases/serverdynamic (server! routes)
Speaker Notes
This folder contains code that interacts with the external world
Think: CLI tool, Server, Lambda function, Worker
The terminology of Polylith: component
components/
Continuing with our example:
- components/content: The markdown files I want to serve as posts
- components/render : Code to convert MD files to HTML
- components/readers: Logged-in visitors on my dynamic website
Speaker Notes
This folder contains re-usable, modular code that implements specific functionality.
How does all this tie together?
projects/weblog_static -> [bases/server_static
components/content
components/render]
projects/weblog_dynamic -> [bases/server_dynamic
components/content
components/render
components/readers]
How Polylith promotes Modular architecture
Code outside a component can only refer to functions in the component's interface
namespace.
Speaker Notes
During development, you have access to the entire code-base! But there are rules around how you can access any code outside your own brick.
(so I don't need to know how you implement the code, I'm only concerned with the interface of your component)
Interface functions pass-through the arguments to appropriate internal functions.
Polylith: getting the best of micro-services and mono-repos
- Where does the Dockerfile go?
projects/weblog_dynamic
- Where do I put end-to-end tests?
projects/weblog_dynamic/test
- Where do I define the routes and the main class?
bases/server_dynamic/core.clj
- Which functions do I use to render HTML pages?
components/render/interface.clj
- Which functions do I write contract tests for?
components/render/interface.clj
- Which functions do I document thoroughly?
components/render/interface.clj
- Where do I hook observability?
components/render/interface.clj
,bases/server_dynamic/core.clj
- Where does the actual rendering functionality live?
render/posts.clj
,render/tags.clj
,render/index.clj
…
What does the poly tool give me?
- Enables running the right tests
- Checks to ensure component code is used properly everywhere
- Upgrades libraries across all your bricks
… and much more
References for learning more about Polylith
- Learn Polylith Conventions polylith.gitbook.io
- Use
poly
to enforce conventions: cljdoc.org/polylith/clj-poly - Sean Corfield's series on monorepos: corfield.org/deps-edn-monorepo
There is so much more …
- CI/CD! (Use
babashka
!) - Observability (
pedestal
! oriapetos
+clj-otel
) - Web Development (
martian
makes talking to APIs a breeze!)
Come say hi!
- @vedang on 🐘fosstodon, 🖥️github, 🐦twitter
- https://unravel.tech
- We can help with your problems!
- (design, product, engineering)
THANK YOU! QUESTIONS?
Published On: Wed, 27 Mar 2024. Last Updated On: Sun, 15 Sept 2024.