Dealing with cyclic dependencies in a monolith: Removing and managing dependencies to improve dependency graph

This message was imported from the Ruby/Rails Modularity Slack server. Find more info in the import thread.

Message originally sent by slack user U717FXJ8HWK

How have you approached cyclic dependencies? For our monolith, we’re currently at around 150 packages, and for a while we’ve been ignoring circular dependencies. Now we’re at a point where the dependency graph is very messy because for the most part we’ve been accepting dependencies to resolve existing dependency violations. So I’m thinking of removing some subset of dependencies that cause cycles, accepting those violations as TODO (into package_todo.yml), and then no longer allowing dependency cycles in CI. Anyone with similar or relevant experience here?

Message originally sent by slack user U70I61FD0VD

We do what you’re proposing here, and it works ok. It’s better than ignoring them altogether, but hopefully it drives the conversation about if the dependency is the correct one or not. We don’t allow declaring circular dependencies, but do allow them via Todo, and use packwerk check / packwerk validate in CI to verify it.

In practice, I think it can become a task of “fixing the build” instead of thinking about the proper dependency tree. Some devs, especially more junior become accustomed to just regenerating Todo to fix the build - I’ve even noticed it instead of declaring a 1-way dependency. So, I’d recommend adding some guardrails/training about the overall goal/philosophy of packs.

Message originally sent by slack user U71V6G2PDJS

We approached it by removing the cyclic dependencies. We also only have ~15 packs, so maybe our system is just a lot simpler.

Message originally sent by slack user U717FXJ8HWK

How big are your packs? Perhaps they’re bigger than ours - we went for lots of relatively small packs, and we’re currently at 500 000 LOC total, so it’s around 3 300 LOC/pack. With this number of packs, the dependency cycles are very long and it’s hard to untangle them.

Message originally sent by slack user U71V6G2PDJS

We’re only at 50KLOC - so our packs are probably roughly the same size on average.

Our monolith has over 80 KLOC and right now we have ~40 packs. But we just started moving files into packs a couple months ago and it’s an effort we’ve been having to do in small bursts of efforts.

For now, we’re focusing on architecture violations. We defined a few layers, defined a few packs for each and started moving files to them.

We’re measuring progress with (this depends on my PR here to be merged so it supports architecture):

bin/packs get_info -t architecture | head -3

And I visualize the architecture todos with:

bin/visualize_packs --no-legend --no-dependencies --no-privacy --only-todo-types=architecture > packs.dot && dot packs.dot -Tpng -o packs.png && open packs.png