Composing commerce with Clojure.
Clojure and Commerce Tools
Commerce Tools Extensions and Events
If you’ve not come across Commerce Tools before, it’s a composable eCommerce platform, designed to be headless, API First, and Cloud Native. Two of the most important ways you can extend CommerceTools when you’re thinking about integrating it with other systems are Extensions and Events.
Extensions allow you to register a webhook-like callback to an endpoint of your choice that allows you to inspect, and modify changes to resources like Carts and Payments as they happen.
Events integrate with your cloud vendor’s queue like platform, be that Pub/Sub on Google Cloud or SNS/SQS on Amazon AWS. You can register a subscription to many events in Commerce Tools so you can receive notifications for things like when Orders are created, products are published or when prices are changed.
Time to market
On one project we were lucky enough to have all of our Clojure experts together on the project so decided it was a good time to use it to build endpoints for CommerceTools extensions & events, as it’s a language ideally suited to the problem of data transformation.
Whilst we could have used ClojureScript to create functions/lambdas we settled on running a Clojure process inside a Docker image hosted in Google Cloud Run as some of the interop for Functions/Lambdas was difficult to manage at the time.
Our final stack consisted of Ring, Reitit & Muuntaja to be able to handle HTTP requests that we could register both as the endpoints for Commerce Tools Extension callbacks and as HTTP Push subscriptions for Google Cloud Pub/Sub. This gave us a single running process that was easy to extend and quickly deployable contributing to rapid development of the first end to end journey. While this doesn’t sound too different to a Kotlin/HTTP4K stack or something based on top of NodeJS, some things about Clojure made this much quicker and easier for us.
Most of our work is transformation
Clojure clicked when I started to think of the world as a series of transformations. For example a HTTP request is nothing more than a series of transformations of a Request object into a Response object. In this case where Extensions and Events are just structured data the need to be transformed and sent onwards clojure really worked for us. For example, taking an Order Created event, and converting that into a structured document to send to an Order Management system became a trivial task to achieve.
Handling Custom Data with Specs
Commerce Tools and a lot of the systems we work with have extendable data structures. Verifying the data you’re getting from the systems you integrate with was made a lot easier by using clojure.spec. This allowed us to create specs of the custom data that we’re getting from the different systems we’re integrating with and validate it before proceeding with the transformation of data. This saved us a few times when some third party system did things we totally didn’t expect in production and were not documented.
Everything ends up very concise
One thing that our wider team was most surprised about when seeing the Clojure codebase for the first time compared to their Kotlin or TS codebases was how concise it was. Each extension or Event is handled by a single clojure file that is relatively easy to read and follow.
Fits nicely into Google Cloud
Being able to push small Docker images of our service to Google Cloud, and having everything performed as a HTTP interaction made this really easy. Making everything uniformly HTTP cut down on the need for interop with SDKs.
Bonus environment to play with data.
We made the choice early on to create wrappers for each of the APIs we interacted with creating a reusable set of functions. This made it quicker to create new integrations but also when those systems had less than adequate documentation it gave us functions in the REPL for experimenting with those APIs. Numerous times this playground allowed us to diagnose issues with data, extract data that wasn’t easily available in the UI, and generally interactively test interactions before committing them to the final code.
This isn’t an article advocating for Clojure in all cases, for us with our team who knew Clojure well it worked really well for us. It’s not the most advanced of used cases but for us showed the everyday power of Clojure when handling data transformation.