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

Example

We can use the Tach codebase itself as an example of a 3-tier layered architecture:

layers = [
  "ui",
  "commands",
  "core"
]

[[modules]]
path = "tach.check"
layer = "commands"

[[modules]]
path = "tach.cache"
depends_on = ["tach.filesystem"]
layer = "core"

[[modules]]
path = "tach.filesystem"
depends_on = []
layer = "core"

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.