Skip to content

Commit db6ceb5

Browse files
docs: improve documentation for dependency rules
1 parent 2cf1563 commit db6ceb5

File tree

1 file changed

+159
-34
lines changed

1 file changed

+159
-34
lines changed

docs/docs/dependency-rules.md

Lines changed: 159 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,141 @@ title: Dependency Rules
44
displayed_sidebar: tutorialSidebar
55
---
66

7-
Sheriff provides access rules.
7+
## Introduction
88

9-
To define access rules, run `npx sheriff init` in your project's root folder. This creates a _sheriff.config.ts_ file, where you can define the tags and dependency rules.
9+
Dependency rules determine which modules can access other modules. Since managing dependencies on a per-module basis
10+
doesn't scale well, Sheriff utilizes tags to group modules together. Dependency rules are then defined based on these
11+
tags.
1012

11-
The initial `sheriff.config.ts` doesn't have any restrictions in terms of dependency rules.
13+
Each tag specifies a list of other tags it can access. To maintain clarity, it’s best practice to categorize tags into
14+
two groups: one for defining the module's domain/scope and another for defining the module's type.
15+
16+
For instance, if an application includes a customer domain and a holiday domain, these would define the domain tags.
17+
18+
Within a domain, you might have different modules distinguished by type tags. For example, one module might contain
19+
smart components, another might have dumb components, and another might handle logic.
20+
21+
Domain tags could be `domain:customer` and `domain:holiday`. Type tags could be `type:feature` (for smart components),
22+
`type:ui` (for dumb components), or `type:data` (for logic).
23+
24+
Each module should have both a domain tag and a type tag. For example, a module containing smart components for
25+
customers would be tagged with `domain:customer` and `type:feature`. A module in the same domain but containing UI
26+
components would be tagged with `domain:customer` and `type:ui`.
27+
28+
Dependency rules specify that a module tagged with `domain:customer` can only access modules with the same domain tag.
29+
Additionally, a module tagged with `type:feature` can access modules tagged with `type:ui` and `type:data`.
30+
31+
When a module containing smart components needs to access a dumb component, Sheriff retrieves the tags for both modules.
32+
It then checks each tag of the smart component module to determine if it is permitted to access the corresponding tags
33+
of the dumb component module.
34+
35+
```mermaid
36+
stateDiagram-v2
37+
[*] --> RequestAccessToModule
38+
RequestAccessToModule --> GetFromAndToTags
39+
GetFromAndToTags --> IterateOverFromTags
40+
IterateOverFromTags --> HasFromTagAccessToAnyToTag
41+
HasFromTagAccessToAnyToTag --> AccessDenied: No
42+
HasFromTagAccessToAnyToTag --> MoreFromTagsAvailable: Yes
43+
MoreFromTagsAvailable --> AccessGranted: No
44+
MoreFromTagsAvailable --> IterateOverFromTags: Yes
45+
AccessGranted --> [*]
46+
AccessDenied --> [*]
47+
48+
```
49+
50+
Since both modules share the same domain, access is allowed based on the domain tag. Additionally, because "type:
51+
feature" is allowed to access "type:ui", all tags are cleared, and access is granted.
52+
53+
```mermaid
54+
flowchart LR
55+
subgraph "smart components"
56+
direction TB
57+
dc1[domain:customer]
58+
sc1[type:feature]
59+
end
60+
61+
subgraph "dumb components"
62+
direction TB
63+
dc2[domain:customer]
64+
sc2[type:ui]
65+
end
66+
67+
sc1 --> sc2
68+
dc1 --> dc2
69+
linkStyle 0 stroke-width: 2px, fill: green, stroke: green;
70+
linkStyle 1 stroke-width: 2px, fill: green, stroke: green;
71+
```
72+
73+
Conversely, if a dumb component tries to access a smart component, access would be denied based on the type tag.
74+
75+
```mermaid
76+
flowchart LR
77+
subgraph "dumb components"
78+
direction TB
79+
dc1[domain:customer]
80+
sc1[type:ui]
81+
end
82+
83+
subgraph "smart components"
84+
direction TB
85+
dc2[domain:customer]
86+
sc2[type:feature]
87+
end
88+
89+
sc1 --> sc2
90+
dc1 --> dc2
91+
linkStyle 0 stroke-width: 2px, fill: green, stroke: red;
92+
linkStyle 1 stroke-width: 2px, fill: green, stroke: green;
93+
```
94+
95+
If a smart component from the customer domain tries to access a dumb component from another domain, access would be
96+
denied due to the domain tag.
97+
98+
```mermaid
99+
flowchart LR
100+
subgraph "Customers"
101+
subgraph "smart components"
102+
direction TB
103+
dc1[domain:customer]
104+
sc1[type:feature]
105+
end
106+
end
107+
108+
subgraph Holidays
109+
subgraph "dumb components"
110+
direction TB
111+
dc2[domain:holiday]
112+
sc2[type:ui]
113+
end
114+
end
115+
116+
sc1 --> sc2
117+
dc1 --> dc2
118+
linkStyle 0 stroke-width: 2px, fill: green, stroke: green;
119+
linkStyle 1 stroke-width: 2px, fill: green, stroke: red;
120+
```
12121

13122
## Automatic Tagging
14123

15-
By default, an untagged module has the tag "noTag". All files which are not part of a module are
16-
assigned to the "root" module and therefore have the tag "root".
124+
Initially, you don't need to assign tags to modules manually.
125+
126+
Any module that remains untagged is automatically assigned the `noTag` tag.
127+
128+
All files that are not part of a specific module are assigned to the `root` module and therefore receive the `root` tag.
129+
130+
However, it is essential to set up the dependency rules. Specifically, you must allow the [`root` tag](#the-root-tag) (
131+
i.e., the root module) to access all other untagged modules.
17132

18-
Dependency rules operate on those tags.
133+
This configuration is done by setting the `depRules` in the _sheriff.config.ts_ file. The `depRules` is an object
134+
literal where each key represents the from tag and its value specifies the tags it can access.
19135

20-
Here's an example of a _sheriff.config.ts_ file with auto-tagged modules:
136+
Initially, all modules can access each other. As a result, every `noTag` module can access other `noTag` modules as well
137+
as those tagged with `root`.
138+
139+
If you use the [CLI](./cli) to initialize Sheriff for the first time, this configuration will be set up automatically.
140+
141+
Here's an example configuration in _sheriff.config.ts_:
21142

22143
```typescript
23144
import { SheriffConfig } from '@softarc/sheriff-core';
@@ -30,12 +151,11 @@ export const sheriffConfig: SheriffConfig = {
30151
};
31152
```
32153

33-
The configuration allows every module with tag "noTag" to access any other module with tag "noTag"
34-
and ["root"](#the-root-tag).
154+
This approach is recommended for existing projects, as it allows for the incremental introduction of Sheriff.
35155

36-
This is the recommendation for existing projects and allows an incremental introduction of Sheriff.
156+
If you are starting a new project, you can skip this step and proceed directly to [manual tagging](#manual-tagging).
37157

38-
If you start from scratch, you should go with [manual tagging](#manual-tagging).
158+
---
39159

40160
To disable automatic tagging, set `autoTagging` to `false`:
41161

@@ -52,7 +172,7 @@ export const sheriffConfig: SheriffConfig = {
52172

53173
## The `root` Tag
54174

55-
Let's say we have the following directory structure:
175+
Consider the following directory structure:
56176

57177
<pre>
58178
src/app
@@ -72,9 +192,10 @@ src/app
72192
│ ├── footer.component.ts
73193
</pre>
74194

75-
_src/app/holidays/data_ and _src/app/holidays/feature_ are modules. All other files are part of the root module which
76-
is tagged with "root". Sheriff assigns the tag "root" automatically. You cannot change it and "root" doesn't have an
77-
_index.ts_. [By default](./integration), it is not possible to import from the root module.
195+
The directories _src/app/holidays/data_ and _src/app/holidays/feature_ are considered modules. All other files are part
196+
of the root module, which is automatically tagged with `root` by Sheriff. This tag cannot be changed, and the root
197+
module does not include an _index.ts_ file. [By default](./integration), importing from the root module is not
198+
permitted.
78199

79200
```mermaid
80201
flowchart LR
@@ -105,7 +226,10 @@ flowchart LR
105226

106227
## Manual Tagging
107228

108-
The following snippet shows a configuration where four directories are assigned to a domain and to a module type:
229+
To assign tags manually, you need to provide a `tagging` object in the _sheriff.config.ts_ file. The keys in this object
230+
represent the module directories, and the corresponding values are the tags assigned to those modules.
231+
232+
The following snippet demonstrates a configuration where four directories are assigned both a domain and a module type:
109233

110234
```typescript
111235
import { SheriffConfig } from '@softarc/sheriff-core';
@@ -121,11 +245,12 @@ export const sheriffConfig: SheriffConfig = {
121245
};
122246
```
123247

124-
With "domain:_" and "type:_", we have two dimensions which allows us to define the following rules:
248+
By using `domain:_` and `type:_`, we establish two dimensions that allow us to define the following rules:
125249

126-
1. A module can only depend on modules of the same domain
127-
2. A module of "type:feature" can depend on "type:data" but not the other way around
128-
3. "root" can depend on a module of "type:feature" and both domains.
250+
1. A module can only depend on other modules within the same domain.
251+
2. A module tagged as `type:feature` can depend on `type:data`, but the reverse is not allowed.
252+
3. The `root` module can depend on modules tagged as `type:feature`. Since the root module only has the `root` tag,
253+
there is no need to include domain tags.
129254

130255
```typescript
131256
import { SheriffConfig } from '@softarc/sheriff-core';
@@ -142,18 +267,19 @@ export const sheriffConfig: SheriffConfig = {
142267
'domain:holidays': ['domain:holidays'], // Rule 1
143268
'domain:customers': ['domain:customers'], // Rule 1
144269
'type:feature': 'type:data', // Rule 2
145-
root: ['type:feature', 'domain:holidays', 'domain:customers'], // Rule 3
270+
root: 'type:feature', // Rule 3
146271
},
147272
};
148273
```
149274

150-
If those roles are violated, a linting error is thrown:
275+
If these rules are violated, a linting error will be triggered:
151276

152277
<img width="1512" alt="Screenshot 2023-06-13 at 17 50 41" src="/img/dependency-rules-1.png"></img>
153278

154-
For existing projects, you want to tag modules and define dependency rules incrementally.
279+
For existing projects, it is recommended to tag modules and define dependency rules incrementally.
155280

156-
If you only want to tag modules from "holidays" and leave the rest auto-tagged, you can do so:
281+
If you prefer to only tag modules within the "holidays" directory and leave the rest of the modules auto-tagged, you can
282+
do so:
157283

158284
```typescript
159285
import { SheriffConfig } from '@softarc/sheriff-core';
@@ -166,14 +292,14 @@ export const sheriffConfig: SheriffConfig = {
166292
depRules: {
167293
'domain:holidays': ['domain:holidays', 'noTag'],
168294
'type:feature': ['type:data', 'noTag'],
169-
root: ['type:feature', 'domain:holidays', 'noTag'],
295+
root: ['type:feature', 'noTag'],
170296
noTag: ['noTag', 'root'],
171297
},
172298
};
173299
```
174300

175-
All modules in the directory "customers" have the tag "noTag". Be aware, that every module from "domain:holidays" can
176-
now depend on any module from directory "customers" but not vice versa.
301+
All modules in the "customers" directory are assigned the `noTag` tag. Be aware that this setup allows any module from
302+
`domain:holidays` to depend on modules within the "customers" directory, but the reverse is not permitted.
177303

178304
## Nested Paths
179305

@@ -199,7 +325,7 @@ export const sheriffConfig: SheriffConfig = {
199325
'domain:holidays': ['domain:holidays'],
200326
'domain:customers': ['domain:customers'],
201327
'type:feature': 'type:data',
202-
root: ['type:feature', 'domain:holidays', 'domain:customers'],
328+
root: ['type:feature'],
203329
},
204330
};
205331
```
@@ -226,7 +352,7 @@ export const sheriffConfig: SheriffConfig = {
226352
'domain:holidays': ['domain:holidays'],
227353
'domain:customers': ['domain:customers'],
228354
'type:feature': 'type:data',
229-
root: ['type:feature', 'domain:holidays', 'domain:customers'],
355+
root: ['type:feature'],
230356
},
231357
};
232358
```
@@ -237,15 +363,14 @@ We can use placeholders on all levels. Our configuration is now more concise.
237363
import { SheriffConfig } from '@softarc/sheriff-core';
238364

239365
export const sheriffConfig: SheriffConfig = {
240-
version: 1,
241366
tagging: {
242367
'src/app/<domain>/<type>': ['domain:<domain>', 'type:<type>'],
243368
},
244369
depRules: {
245370
'domain:holidays': ['domain:holidays'],
246371
'domain:customers': ['domain:customers'],
247372
'type:feature': 'type:data',
248-
root: ['type:feature', 'domain:holidays', 'domain:customers'],
373+
root: ['type:feature'],
249374
},
250375
};
251376
```
@@ -265,12 +390,12 @@ export const sheriffConfig: SheriffConfig = {
265390
depRules: {
266391
'domain:*': ({ from, to }) => from === to,
267392
'type:feature': 'type:data',
268-
root: ['type:feature', ({ to }) => to.startsWith('domain:')],
393+
root: ['type:feature'],
269394
},
270395
};
271396
```
272397

273-
or
398+
or use `sameTag`, which is a pre-defined function.
274399

275400
```typescript
276401
import { sameTag, SheriffConfig } from '@softarc/sheriff-core';
@@ -283,7 +408,7 @@ export const sheriffConfig: SheriffConfig = {
283408
depRules: {
284409
'domain:*': [sameTag, 'shared'],
285410
'type:feature': 'type:data',
286-
root: ['type:feature', ({ to }) => to.startsWith('domain:')],
411+
root: ['type:feature'],
287412
},
288413
};
289414
```

0 commit comments

Comments
 (0)