The Polylith team likes simplicity and that's why we have used Clojure when implementing the Polylith tool and the RealWorld example app. The Clojure syntax may look like a bunch of parentheses, added for no apparent reason but the parentheses are actually there to solve actual problems. In the same way the Polylith way of structure code may also look alien to people.
It is no coincidence that a component lives in its own src directory, that it’s just “plain code”, that it has an interface, that it’s only allowed to depend on interfaces (and libraries) and that it lives in a monorepo. All those things are there to decouple our system(s) into small Lego-like bricks in a way that they can be shared and put together in a useful way.
Components are very similar to functions in that way, but operate on a higher level. A function is composable, easy to reason about, has a well defined interface and is fundamentally simple. A component is also composable, easy to reason about, has a well defined interface and is fundamentally simple.
Teasing things apart so that they can be composed together is often a sign of good design. It has been as hard to convince people that Polylith is a good idea as it has been to convince them that Clojure is. Our experience though, is when they start using either of them, they will soon discover how simple they are, how fun they are to work with and how productive you become, and that’s why we love Clojure and Polylith.
To get an idea of the principles Polylith is based on, please read this article where I try to explain where complexity comes from.
This is how Polylith fights complexity:
We use a monorepo that helps us keep the codebase in sync so that we can make coordinated commits across projects.
We have only one of each component and interface, which removes code duplication and maximises code reuse.
A component is "just code" that exposes a set of functions. We avoid the complexity that comes with mutable state, by not introducing mutable objects.
A component lives in its own place, a separate src directory. The default way of structuring code is normally to put different functionalities together in one or several places/projects. The result is that each piece of functionality can't be shared across projects. Polylith solves that problem by letting each functionality live in its own separate place.
Components have interfaces and only know of other interfaces. This decouples them from each other and make them composable, reusable and replaceable.
The single development project helps us keep the contracts between the components in sync and stops us from introducing bidirectional or circular dependencies. In other words, it guarantees that all dependencies point in the same direction, even across projects.
A project specifies what building blocks it contains. There are two things that make this possible. First, all dependencies point in the same direction. Second, not only frozen code (libraries) can be reused across projects but also living code in the form of components and bases. This allows us to pick and choose what to include in each project as if they were Lego bricks. The result is that we can decouple development concerns from production concerns which helps us reduce the complexity of our system(s).
The introduction of new high-level concepts and the standardized structure makes it easier to reason about the code and reduces the cognitive load, which results in reduced complexity.
As you can see, Polylith is not only about productivity but very much about simplicity.