Polylith in a Nutshell
Polylith is a software architecture that simplifies our backend services and tools by enabling us to construct them as “modular monoliths” using composable components.
Here we will introduce the basic building blocks of Polylith.
Functions are the smallest building blocks in Polylith from which everything is created. Most communication within a Polylith system is done by using simple function calls as a way to connect the different high-level building blocks that a Polylith system consists of.
The simplicity of functions makes them fantastic building blocks for code:
- 1.Encapsulation: functions hide their implementation and only expose their signature.
- 2.Simplicity: functions have a single responsibility and don't mix nouns with verbs, which makes them fundamentally untangled.
- 3.Stateless: functions are just code; they don't contain state or instance.
These properties make functions (especially pure functions) inherently composable and testable units of code and a perfect foundation for a software architecture like Polylith.
We rely on tooling we already use, that hides the complexity of solving dependencies to other libraries and the caching of files on disk.
Components are high-level building blocks (bricks) which remove the need for layers (horizontal, vertical slice, or onion) in our architecture.
A component can represent a part of our domain (e.g. cart, invoice, order, user, etc.), or be part of our infrastructure (e.g. authentication, database, log, etc.), or be an integration point to a third-party system, (e.g. crm-api, payment-api, sms-api, etc).
A base is a special type of building block (brick) that only exposes its functionality via a public API, e.g. REST, Lambda, GraphQL, gRPC, command-line, etc.
A base exposes a collection of endpoints via its API, and delegates the implementation of each endpoint to an appropriate component.
Brick is the common name for a component or base, which are our building blocks (together with libraries).
A project specifies which libraries and bricks should be included in an artifact, like a service, lambda function, command line tool, or a new library. This allows for optimal code reuse of components across multiple projects.
A development project is the place we use to work with all our libraries, components, and bases. It gives us a “monolithic development experience” with full code navigation, debugging, and refactoring across our entire codebase, and the possibility to work with our entire system in a single REPL.
A workspace is the place in a Polylith codebase where we store all our building blocks and configure our projects.
- Polylith’s single development project gives us a delightful development experience; we get all the benefits of coding with a monolith (code navigation, debugging, refactoring, and a single REPL) but maintain the flexibility of deploying our components into any combination of artifacts.
- The LEGO-like components simplify the design of our tools and services by giving us building blocks at the right level of abstraction, which are understandable, composable, reusable, and replaceable.
- Components are inherently simple and easy to reason about; they are just code, have a clear interface, and hide their implementation.
- Components maximise code reuse to the point of having zero code duplication across our entire codebase.
- Projects maximise the deployment flexibility by allowing us to combine any set of bricks.
Now, let's dig deeper into the Polylith architecture to better understand how it solves these challenges.