Skip to content

Commit

Permalink
Update docs to explain layers, drop the beta warning for domain confi…
Browse files Browse the repository at this point in the history
…guration files
  • Loading branch information
emdoyle committed Feb 6, 2025
1 parent d080cc4 commit e7c2869
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 9 deletions.
1 change: 1 addition & 0 deletions docs/mint.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"usage/commands",
"usage/configuration",
"usage/interfaces",
"usage/layers",
"usage/deprecate",
"usage/tach-ignore",
"usage/unchecked-modules",
Expand Down
52 changes: 43 additions & 9 deletions docs/usage/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ This is the project-level configuration file which should be in the root of your

`modules` defines the modules in your project - [see details](#modules).

`interfaces` defines the interfaces of modules in your project - [see details](#interfaces).
`interfaces` defines the interfaces of modules in your project (optional) - [see details](#interfaces).

`layers` defines the layers of modules in your project (optional) - [see details](#layers).

`exclude` accepts a list of directory patterns to exclude from checking. These should be glob paths which match from the beginning of a given file path. For example: `project/*.tests` would match any path beginning with `project/` and ending with `.tests`.

Expand Down Expand Up @@ -48,12 +50,19 @@ exact = true
ignore_type_checking_imports = true
forbid_circular_dependencies = true

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

[[modules]]
path = "tach"
depends_on = []

[[modules]]
path = "tach.__main__"
layer = "ui"

[[modules]]
path = "tach.errors"
Expand All @@ -63,6 +72,7 @@ utility = true
[[modules]]
path = "tach.parsing"
depends_on = ["tach", "tach.filesystem"]
layer = "core"
visibility = ["tach.check"]

[[modules]]
Expand All @@ -72,6 +82,7 @@ depends_on = [
"tach.filesystem",
"tach.parsing",
]
layer = "commands"

[[interfaces]]
expose = ["types.*"]
Expand Down Expand Up @@ -136,6 +147,8 @@ More specifically:
- `expose`: a list of regex patterns which define the public interface
- `from` (optional): a list of regex patterns which define the modules which adopt this interface

[More details here.](../usage/interfaces)

<Note>
If an interface entry does not specify `from`, all modules will adopt the interface.
</Note>
Expand All @@ -144,6 +157,33 @@ If an interface entry does not specify `from`, all modules will adopt the interf
A module can match multiple interface entries - if an import matches _any_ of the entries, it will be considered valid.
</Note>

## Layers

An ordered list of layers can be configured at the top level of `tach.toml`,
and [modules](#modules) can each be assigned to a specific layer.

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

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

[[modules]]
path = "tach.cache"
layer = "core"
```

The configuration above defines three layers, with `ui` being the highest layer, and `core` being the lowest layer.
It also tags `tach.check` as a module in the `commands` layer, and `tach.cache` in `core`.

[More details here.](../usage/layers)


## The Root Module

By default, Tach checks all of the source files beneath all of the configured [source roots](#source_roots), and will ignore dependencies which are not contained by [modules](#modules).
Expand Down Expand Up @@ -221,7 +261,7 @@ To indicate this structure to Tach, set:
source_roots = ["backend"]
```

in your `tach.toml`, or use [`tach mod`](commands#tach-mod) and mark the `backend` folder as the only source root.
in your `tach.toml`, or use [`tach mod`](../usage/commands#tach-mod) and mark the `backend` folder as the only source root.

### Example: Monorepo

Expand Down Expand Up @@ -283,18 +323,12 @@ source_roots = [
]
```

in your `tach.toml`, or use [`tach mod`](commands#tach-mod) and mark the same folders as source root.
in your `tach.toml`, or use [`tach mod`](../usage/commands#tach-mod) and mark the same folders as source root.

In `tach.toml`, each entry in `source_roots` is interpreted as a relative path from the project root.

## `tach.domain.toml`

<Warning>
This feature is in **beta**. The exact behavior and syntax of this configuration is subject to change and should not be considered stable.

If it seems useful, we'd love to hear your feedback! E-mail us at [email protected]
</Warning>

Tach allows splitting your configuration into 'domains', or sub-folders of your project.
You can define modules and interfaces in a `tach.domain.toml` file which lives right next to the module code itself.

Expand Down
66 changes: 66 additions & 0 deletions docs/usage/layers.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
title: Layers
---

An ordered list of layers can be configured at the top level of [`tach.toml`](../usage/configuration#layers),
and [modules](../usage/configuration#modules) can each be assigned to a specific layer.

## How does it work?

[Layered architecture](https://www.oreilly.com/library/view/software-architecture-patterns/9781491971437/ch01.html) 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:

```toml
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.

0 comments on commit e7c2869

Please sign in to comment.