Monorepos and Clojure CLI/deps.edn

For anyone out there wondering how to set up the Clojure CLI / deps.edn with a monorepo – or even wondering what a monorepo is – I wrote a blog post about the things we tried that didn’t work so well and what we’ve arrived at now, that seems to be working really well:

An Architect’s View: deps.edn and monorepos (corfield.org)

13 Likes

Thanks for a great write up on monorepos with Clojure.

I know this is comparing apples to oranges, but this looks like a much improved version of how we use lerna for a monorepo typescript project at the client I am currently working with. Especially your use of dependency overrides to control versions at the top level is something I have wished for in that project.

This is not meant as bashing lerna or typescript, though I do wish we could have used Clojure.

I‘ve had some success using Nx for npm monorepo projects recently, putting React and Angular projects under one hood. Not sure how its scope overlaps with Lerna.

I’ve written an update to this, that talks a bit about changes we’ve made recently, and touches on our initial experiences with Polylith: An Architect’s View: deps.edn and monorepos II (corfield.org)

2 Likes

Have you integrated Polylith somehow with component? It seems like going back a bit with regards to state management and replacing components

I think of Component as a way to manage dependencies between things that have start/stop lifecycles, which your application needs in order to run – so I see no conflict between Polylith and Component. You can see how my usermanager example app – which uses Component for several things – looks with Polylith in this branch: seancorfield/usermanager-example at polylith (github.com)

1 Like

Is Polylith a way to encapsulate micro-services as libraries in different folders as independent projects with an API layer, but instead of using as your protocol HTTP, it simply uses standard Clojure lib-spec requires and function calls?

So basically, where one person might break out their app in different micro-services, each an independent project with its own src, tests, APIs, which is worked on independently and can be released at its own cadence with its own versions, and shared by multiple clients. But would typically also need to deploy them to their own fleet, hook it up to its own http web server to serve the API over HTTP, forcing all messages to be data like JSON in the process. Polylith makes those same micro-services usable as a simple Clojure namespace. Where multiple clients can depend on them with different versions, etc. ?

Is that the premise? So you get strong boundaries between the components at development time, they look very isolated and separate and could have different teams working on them only pulling down from git their component to work on. But when bundled for deploy, you still end up with a monolith all running self-contained within the same process?

I think the idea is that during development time you can use a single repl, but you can package the polylith to multiple separately deployed services.

https://polylith.gitbook.io/polylith/conclusion/advantages-of-polylith#production

Hum, the part I’m missing is you cannot transparently deploy functions over HTTP, so how would a Polylith component deploy to its own instance? What would expose the APIs? Also, if you’ve designed your interface functions to take as input/output non-data, like an actual object instance, that’s going to get tricky to serialize/deserialize.

Edit: Oh I see, that’s where Base comes in. It seems pretty similar to the Facade pattern in that way. Ok I’ll read more about it before asking more questions :relaxed:.

We were already doing that with our monorepo. Polylith is about more than just that.

Ok, I read the doc and have yet to try it out. I definitely appreciate its formalization, that’s a good way to help newcomers and have people ramp up to a code base or set of them.

The tool also seem a really good addition. I’ve found one advantage of micro-services is the strong boundary they create, a dev cannot accidentally create a coupling between them, because it’s physically impossible (unless they took a dependency from one to the other). Where as components within a code base are easy for people to get lazy and start coupling them strongly, breaking their interface and blurring their bounds. It’s nice that the tool will enforce this by failing if a component or base depends on anything else but an interface.

While I like exposing my interfaces as just what is public versus private, there are advantages of a separate namespace, and I think accidentally forgetting to make something private is one of those, which back to my point above, can easily lead to accidental leaking of internals.

The base versus component separation is good too, I always use a Facade pattern, decouple your logic from the concerns of exposing an interface to its users. I like its explicitness and formalism, will help a lot juniors working on a project to understand the separation.

It’s also nice the tool supports keeping two interface in sync, and scaffolds all these things.

The monorepo part I guess are just monorepo vs multi-repo pros/cons. Though I have to say it makes creating a new component a very lightweight and quick process which is good. In a multi-repo, creating a new repo for your component is often more work, and I think that can push devs to be lazy and end up with a monolith.

That said, it leaves the hardest bit unsolved, but I don’t think anything can automate that, which is how to break appart your logic into components, how big should each be, how should their interface behave, how is state managed and where, etc.

I’m also curious where Spec live? Are they part of the interface? I’d guess so, at least the specs related to the interface. Or Malli if you used that. And then specs for things unrelated to the interface probably just go somewhere else in the component.

I wonder if the whole thing could be decoupled from files though. Like could Vars be annotated with meta instead, so in a single namespace you could have base, component-impl and component-interface.

I guess I’m asking for something less formal and explicit haha. But personally I think that process often allows the separation to appear. Though I guess that would prevent them from being immediately sharable with other projects. Always starting with a database component, that can quickly devolve in overengineering I feel, but I guess this is where I should give it a try and probably will.

One of the things I’ve found about Polylith’s structure and conventions is that at least it focuses your mind when thinking about “components” in a general sense: the deliberate approach to defining and organizing components (and bases to a lesser extent) makes you spend a bit more time thinking about “how to break apart your logic”, even if it doesn’t “solve” or “automate” it for you.

Somewhere in the docs it talks about this: you can have “sub-interfaces” and that’s a good place for Specs to live: top.ns.my-cool-component.interface.specs in components/my-cool-component/src/top/ns/my_cool_component/interface/specs.clj

See polyfy/polylith at issue-66 (github.com):

This can be handy if we want to group the functions and not put everyone into one place. A common usage is to place clojure specs in its own spec sub namespace, which we have an example of in the RealWorld example app, where the article component also has an interface.spec sub interface.

2 Likes