Layers
An ordered list of layers can be configured at the top level of tach.toml
,
and modules can each be assigned to a specific layer.
How does it work?
Layered architecture is often an effective starting point for modularizing an application.
The idea is straightforward: Higher layers may import from lower layers, but lower layers may NOT import from higher layers.
Defining this architecture is more concise and flexible than specifying all module dependencies
with depends_on
, which makes it easier to adopt in an existing project.
Tach allows defining and enforcing a layered architecture with any number of vertically-stacked layers.
When a module is assigned to a layer, this module:
- may freely depend on modules in lower layers, without declaring these dependencies
- must explicitly declare dependencies in its own layer
- may never depend on modules in higher layers, even if they are declared
Closed Layers
By default, modules in higher layers can import from any lower layer. However, you can mark a layer as “closed” to prevent modules in higher layers from importing modules in lower layers.
For example, with layers A, B, and C (high to low), if B is a closed layer, then A cannot import C directly, even though C is lower than A. This is useful for enforcing strict boundaries between architectural tiers.
When a layer is closed, it acts as an intermediary - modules in higher layers must go through the closed layer to access modules in lower layers. This helps enforce architectural boundaries and prevents direct coupling between distant layers.
You can specify a closed layer by using an in-line object in the layers
array:
In this example, if commands
is a closed layer:
ui
modules can import fromcommands
modulescommands
modules can import fromcore
modulesui
modules CANNOT import directly fromcore
modulesui
modules must go throughcommands
modules to accesscore
functionality
Example
We can use the Tach codebase itself as an example of a 3-tier layered architecture:
In the configuration above, three layers are defined.
They are similar to the classic Presentation
- Business Logic
- Data
which are often found in web applications,
but a bit different given that Tach is a CLI program.
In Tach, the highest layer is UI
, which includes code related to the CLI and other entrypoints to start the program.
Just below this, the Commands
layer contains high-level business logic which implements each of the CLI commands.
At the bottom is the Core
layer, which contains utilities, libraries, and broadly relevant data structures.
Given this configuration, tach.check
does not need to declare a dependency on tach.cache
or tach.filesystem
to use it,
because the Commands
layer is higher than the Core
layer.
However, tach.cache
needs to explicitly declare its dependency on tach.filesystem
, because they
are both in the Core
layer.