@open-domain-specification/skill 0.1.11 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundle.generated.d.ts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +10 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +10 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/skill/SKILL.md +27 -1
- package/skill/examples/petstore.md +56 -4
- package/skill/references/dsl-api.md +14 -6
- package/skill/references/interview-playbook.md +31 -0
- package/skill/references/model-reference.md +52 -0
- package/skill/references/reconciliation.md +184 -0
- package/skill/references/strategic-relationships.md +98 -0
- package/skill/references/translation-table.md +3 -3
- package/skill/references/validation-rules.md +8 -0
- package/src/bundle.generated.ts +10 -8
- package/src/bundle.test.ts +175 -7
|
@@ -17,11 +17,11 @@ independently of the name.
|
|
|
17
17
|
| `Subdomain` | `addBoundedcontext(name, { description, bigBallOfMud?, team? })` | a context serving this subdomain |
|
|
18
18
|
| `BoundedContext` | `serves(subdomain)` | adds a served subdomain |
|
|
19
19
|
| `BoundedContext` | `ownedBy(team)` | sets the owning team |
|
|
20
|
-
| `BoundedContext` | `upstreamOf(other, { type?, upstreamRoles?, downstreamRoles?, description? })` | directed relationship, this side upstream; `type` defaults to `"upstream-downstream"`, or `"customer-supplier"` |
|
|
20
|
+
| `BoundedContext` | `upstreamOf(other, { type?, upstreamRoles?, downstreamRoles?, description?, comments?, disposition? })` | directed relationship, this side upstream; `type` defaults to `"upstream-downstream"`, or `"customer-supplier"` |
|
|
21
21
|
| `BoundedContext` | `downstreamOf(other, options)` | the same, this side downstream |
|
|
22
|
-
| `BoundedContext` | `partnerOf(other, description?)` | partnership |
|
|
23
|
-
| `BoundedContext` | `sharesKernelWith(other,
|
|
24
|
-
| `BoundedContext` | `separateWaysFrom(other,
|
|
22
|
+
| `BoundedContext` | `partnerOf(other, { description?, comments?, disposition? })` | partnership |
|
|
23
|
+
| `BoundedContext` | `sharesKernelWith(other, options)` | shared kernel |
|
|
24
|
+
| `BoundedContext` | `separateWaysFrom(other, options)` | separate ways |
|
|
25
25
|
| `BoundedContext` | `addAggregate(name, { description })` | an aggregate |
|
|
26
26
|
| `BoundedContext` | `addService(name, { type, description })` | a service; `type` is `"application" \| "domain"` |
|
|
27
27
|
| `BoundedContext` | `addPolicy(name, { description })` | a policy; chain `.on(...events).then(...operations)` |
|
|
@@ -31,8 +31,8 @@ independently of the name.
|
|
|
31
31
|
| `Aggregate` | `addEntity(name, { description, root? })` | an entity |
|
|
32
32
|
| `Aggregate` | `addValueObject(name, { description })` | a value object |
|
|
33
33
|
| `Aggregate` | `addInvariant(name, { description })` | an invariant; chain `.constrains(...entities, valueObjects or attributes)` |
|
|
34
|
-
| `Aggregate`, `Service` | `provides(name, { type, description, pattern?, internal?, schema? })` | a consumable; `type` is `"event" \| "operation"`, `pattern` is `"open-host-service" \| "published-language"` |
|
|
35
|
-
| `Aggregate`, `Service` | `consumes(consumable, { pattern? })` | a consumption; `pattern` is `"conformist" \| "anti-corruption-layer"` |
|
|
34
|
+
| `Aggregate`, `Service` | `provides(name, { type, description, pattern?, internal?, schema?, comments?, disposition? })` | a consumable; `type` is `"event" \| "operation"`, `pattern` is `"open-host-service" \| "published-language"` |
|
|
35
|
+
| `Aggregate`, `Service` | `consumes(consumable, { pattern?, comments?, disposition? })` | a consumption; `pattern` is `"conformist" \| "anti-corruption-layer"` |
|
|
36
36
|
| `Consumable` | `raises(...events)` | the events an operation raises |
|
|
37
37
|
| `Entity`, `ValueObject`, `DataSchema` | `addAttribute(name, { type, description?, identity?, valueobject? })` | an attribute; `type` is free text |
|
|
38
38
|
| `Entity`, `ValueObject` | `uses(target, label, cardinality?)` | a `uses` relation |
|
|
@@ -41,5 +41,13 @@ independently of the name.
|
|
|
41
41
|
| `Entity`, `ValueObject` | `addRelation(target, { relation, label?, cardinality? })` | any relation explicitly |
|
|
42
42
|
| `Entity` | `.attributes.get("name")` | look an attribute up, e.g. to constrain it |
|
|
43
43
|
|
|
44
|
+
Every relationship, consumable and consumption also takes the evidence pair from
|
|
45
|
+
RFC-002: `comments` is a list of `{ text, link? }`, where a link is
|
|
46
|
+
`{ kind, url, label? }` and `kind` is
|
|
47
|
+
`"code" | "contract" | "adr" | "runbook" | "dashboard"`; `disposition` is
|
|
48
|
+
`"by-design" | "tolerated" | "refactor"` and defaults to `by-design`, which is never
|
|
49
|
+
written to JSON. Read one back with `dispositionOf(element)`, and list the intents
|
|
50
|
+
nobody has documented with `intentsWithoutComments(workspace)`.
|
|
51
|
+
|
|
44
52
|
`cardinality` is `"1" | "0..1" | "*" | "1..*"`. Chainable methods (`raises`, `on`, `then`,
|
|
45
53
|
`constrains`, `embody`, `serves`, `ownedBy`) return their receiver.
|
|
@@ -61,6 +61,7 @@ job is to get the model out of their head without making them learn the vocabula
|
|
|
61
61
|
- "Does the upstream side publish a documented API, or a documented message format?" →
|
|
62
62
|
`open-host-service` / `published-language`. Goes on `upstreamRoles` and on each exposed
|
|
63
63
|
consumable's `pattern`.
|
|
64
|
+
- Then the two evidence questions (see below), once for the relationship you just recorded.
|
|
64
65
|
|
|
65
66
|
## Phase E: inside one context (produces Aggregates, Entities, Value Objects, Invariants, Glossary)
|
|
66
67
|
|
|
@@ -100,9 +101,39 @@ Repeat for each context the user wants detailed. Ask which one to start with.
|
|
|
100
101
|
with a downstream `pattern`.
|
|
101
102
|
- Close: "Which of the words we used should I define, and does each map to one of the things
|
|
102
103
|
we modelled?" → glossary terms with `embodiedBy`.
|
|
104
|
+
- Ask the two evidence questions (see below) for each consumable or consumption that came out
|
|
105
|
+
of this phase with a `pattern` on it.
|
|
103
106
|
|
|
104
107
|
## Phase G: validate and reflect
|
|
105
108
|
|
|
106
109
|
Run validation. Explain each diagnostic in one plain sentence, propose the fix, and ask before
|
|
107
110
|
applying fixes for warnings. Then summarise what changed, in the user's words, and ask what to
|
|
108
111
|
model next.
|
|
112
|
+
|
|
113
|
+
## The two evidence questions
|
|
114
|
+
|
|
115
|
+
Every strategic intent — a relationship, a consumable that leaves its context, a consumption —
|
|
116
|
+
gets exactly these two, and only when it is new:
|
|
117
|
+
|
|
118
|
+
- "Is that how you want it, or is it something you are living with?" → `disposition`. "How we
|
|
119
|
+
want it" is `by-design`, which is the default and is never written down. "Living with it, and
|
|
120
|
+
nobody is going to change it" is `tolerated`. "It should not stay like that" is `refactor`;
|
|
121
|
+
follow up with "what should it become?" and put the answer in the comment.
|
|
122
|
+
- "Where does that live — a file, a repo, an API doc, a decision record?" → the first `comment`,
|
|
123
|
+
with its `link`. Take a path or a URL, whichever they give; `kind` is `code`, `contract`,
|
|
124
|
+
`adr`, `runbook` or `dashboard`. If they have nothing to point at, still record what they
|
|
125
|
+
said as a comment with no link.
|
|
126
|
+
|
|
127
|
+
Rules for asking them:
|
|
128
|
+
|
|
129
|
+
- Once per intent, never per role. A relationship with an `open-host-service` upstream role and
|
|
130
|
+
an `anti-corruption-layer` downstream role is still one relationship and gets one pair of
|
|
131
|
+
questions, not two.
|
|
132
|
+
- Never for an internal consumable. It does not cross a boundary, so there is no strategic
|
|
133
|
+
claim to back up.
|
|
134
|
+
- Never for an intent that already carries comments. Read first, ask second, as everywhere else.
|
|
135
|
+
- Do not ask them before the intent itself is settled; they are the follow-up to "so I'd note a
|
|
136
|
+
... right?", not a replacement for it.
|
|
137
|
+
|
|
138
|
+
If the codebase is at hand, offer to answer the second question yourself instead of asking:
|
|
139
|
+
that is reconciliation, and it is in `reconciliation.md`.
|
|
@@ -19,6 +19,7 @@ Represents a workspace in the Open Domain Specification (ODS).
|
|
|
19
19
|
| `logoUrl` | string | no | |
|
|
20
20
|
| `name` | string | yes | |
|
|
21
21
|
| `odsVersion` | string | yes | |
|
|
22
|
+
| `options` | [WorkspaceOptions](#workspaceoptions) | no | Switches for behaviour that is not part of the model, such as opt-in rules. |
|
|
22
23
|
| `primaryColor` | string | no | |
|
|
23
24
|
| `relationships` | array of [ContextRelationship](#contextrelationship) | yes | |
|
|
24
25
|
| `teams` | map of id to [Team](#team) | yes | |
|
|
@@ -75,13 +76,38 @@ Represents a bounded context in the Open Domain Specification (ODS).
|
|
|
75
76
|
|
|
76
77
|
No other fields are allowed.
|
|
77
78
|
|
|
79
|
+
## Comment
|
|
80
|
+
|
|
81
|
+
A short grounded statement about the real system behind a strategic intent, optionally backed by one link.
|
|
82
|
+
|
|
83
|
+
| Field | Type | Required | Notes |
|
|
84
|
+
|---|---|---|---|
|
|
85
|
+
| `link` | [CommentLink](#commentlink) | no | |
|
|
86
|
+
| `text` | string | yes | |
|
|
87
|
+
|
|
88
|
+
No other fields are allowed.
|
|
89
|
+
|
|
90
|
+
## CommentLink
|
|
91
|
+
|
|
92
|
+
Where the evidence for a comment lives: the code, the contract, the decision record, the runbook or the dashboard.
|
|
93
|
+
|
|
94
|
+
| Field | Type | Required | Notes |
|
|
95
|
+
|---|---|---|---|
|
|
96
|
+
| `kind` | "adr" | "code" | "contract" | "dashboard" | "runbook" | yes | |
|
|
97
|
+
| `label` | string | no | What to show instead of the raw URL. |
|
|
98
|
+
| `url` | string | yes | |
|
|
99
|
+
|
|
100
|
+
No other fields are allowed.
|
|
101
|
+
|
|
78
102
|
## Consumable
|
|
79
103
|
|
|
80
104
|
Represents a consumable in the Open Domain Specification (ODS).
|
|
81
105
|
|
|
82
106
|
| Field | Type | Required | Notes |
|
|
83
107
|
|---|---|---|---|
|
|
108
|
+
| `comments` | array of [Comment](#comment) | no | Grounded statements about the real system behind this consumable. |
|
|
84
109
|
| `description` | string | yes | |
|
|
110
|
+
| `disposition` | "by-design" | "refactor" | "tolerated" | no | What the architecture thinks of this consumable. Absent means `by-design`. |
|
|
85
111
|
| `internal` | boolean | no | True when the consumable stays inside its context: an event only local policies react to, or an operation only local callers issue. Internal consumables may not be consumed from another context. |
|
|
86
112
|
| `name` | string | yes | |
|
|
87
113
|
| `pattern` | "open-host-service" | "published-language" | no | The upstream role this consumable is offered under. Absent on internal consumables. |
|
|
@@ -97,7 +123,9 @@ Represents a consumption in the Open Domain Specification (ODS).
|
|
|
97
123
|
|
|
98
124
|
| Field | Type | Required | Notes |
|
|
99
125
|
|---|---|---|---|
|
|
126
|
+
| `comments` | array of [Comment](#comment) | no | Grounded statements about the real system behind this consumption. |
|
|
100
127
|
| `consumable` | `{ "$ref": string }` | yes | |
|
|
128
|
+
| `disposition` | "by-design" | "refactor" | "tolerated" | no | What the architecture thinks of this consumption. Absent means `by-design`. |
|
|
101
129
|
| `pattern` | "anti-corruption-layer" | "conformist" | no | The downstream role the consumer adopts for this consumable. |
|
|
102
130
|
|
|
103
131
|
No other fields are allowed.
|
|
@@ -126,7 +154,9 @@ An upstream/downstream relationship between two bounded contexts.
|
|
|
126
154
|
|
|
127
155
|
| Field | Type | Required | Notes |
|
|
128
156
|
|---|---|---|---|
|
|
157
|
+
| `comments` | array of [Comment](#comment) | no | Grounded statements about the real system behind this relationship. |
|
|
129
158
|
| `description` | string | no | |
|
|
159
|
+
| `disposition` | "by-design" | "refactor" | "tolerated" | no | What the architecture thinks of this relationship. Absent means `by-design`. |
|
|
130
160
|
| `downstream` | `{ "$ref": string }` | yes | |
|
|
131
161
|
| `downstreamRoles` | array of "anti-corruption-layer" | "conformist" | yes | |
|
|
132
162
|
| `type` | "customer-supplier" | "upstream-downstream" | yes | |
|
|
@@ -210,6 +240,16 @@ A reaction: when these events happen, issue these commands.
|
|
|
210
240
|
|
|
211
241
|
No other fields are allowed.
|
|
212
242
|
|
|
243
|
+
## RuleOptions
|
|
244
|
+
|
|
245
|
+
Opt-in validation rules. A rule listed here is off unless the workspace turns it on.
|
|
246
|
+
|
|
247
|
+
| Field | Type | Required | Notes |
|
|
248
|
+
|---|---|---|---|
|
|
249
|
+
| `commentsRequired` | boolean | no | Warn on every context relationship that carries no comments. Off by default. |
|
|
250
|
+
|
|
251
|
+
No other fields are allowed.
|
|
252
|
+
|
|
213
253
|
## Service
|
|
214
254
|
|
|
215
255
|
Represents a service in the Open Domain Specification (ODS).
|
|
@@ -242,7 +282,9 @@ A relationship between two bounded contexts with no upstream or downstream side.
|
|
|
242
282
|
|
|
243
283
|
| Field | Type | Required | Notes |
|
|
244
284
|
|---|---|---|---|
|
|
285
|
+
| `comments` | array of [Comment](#comment) | no | Grounded statements about the real system behind this relationship. |
|
|
245
286
|
| `description` | string | no | |
|
|
287
|
+
| `disposition` | "by-design" | "refactor" | "tolerated" | no | What the architecture thinks of this relationship. Absent means `by-design`. |
|
|
246
288
|
| `participants` | array of unknown | yes | |
|
|
247
289
|
| `type` | "partnership" | "separate-ways" | "shared-kernel" | yes | |
|
|
248
290
|
|
|
@@ -273,6 +315,16 @@ Represents a value object in the Open Domain Specification (ODS).
|
|
|
273
315
|
|
|
274
316
|
No other fields are allowed.
|
|
275
317
|
|
|
318
|
+
## WorkspaceOptions
|
|
319
|
+
|
|
320
|
+
Per-workspace switches for behaviour that is not part of the model itself.
|
|
321
|
+
|
|
322
|
+
| Field | Type | Required | Notes |
|
|
323
|
+
|---|---|---|---|
|
|
324
|
+
| `rules` | [RuleOptions](#ruleoptions) | no | |
|
|
325
|
+
|
|
326
|
+
No other fields are allowed.
|
|
327
|
+
|
|
276
328
|
## Ref grammar
|
|
277
329
|
|
|
278
330
|
Every cross-link is an object `{ "$ref": "<path>" }`. Paths are JSON pointers into the workspace, built from the ids that key each collection:
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# Reconciliation
|
|
2
|
+
|
|
3
|
+
A model is a claim about a real system. Reconciliation is checking the claim: for each strategic
|
|
4
|
+
intent, go and look in the repository for the evidence behind it, then write down what is
|
|
5
|
+
actually there. The result is `comments` on the intent — grounded statements, each backed by a
|
|
6
|
+
link — and, where the code disagrees with the model, a proposed `disposition`.
|
|
7
|
+
|
|
8
|
+
Strategic intents are the three things evidence hangs off: a **relationship** between two
|
|
9
|
+
contexts, a **consumable** that crosses a boundary, and a **consumption** of one. Internal
|
|
10
|
+
consumables never cross a boundary, so they are not strategic and are never reconciled.
|
|
11
|
+
|
|
12
|
+
## When to reconcile
|
|
13
|
+
|
|
14
|
+
- The user asks: "check the model against the code", "is the ACL actually there", "reconcile".
|
|
15
|
+
- After the interview, for the intents just created, if the codebase is at hand.
|
|
16
|
+
- On opening a workspace whose intents carry no comments: offer it, once, and let the user
|
|
17
|
+
choose. Do not start a reconciliation nobody asked for.
|
|
18
|
+
|
|
19
|
+
The worklist is every intent with no comments — the "No comments" list on the health report,
|
|
20
|
+
and `intentsWithoutComments(workspace)` in the DSL. Work it in the order the report shows and
|
|
21
|
+
stop when the user says enough.
|
|
22
|
+
|
|
23
|
+
## What to look for, per pattern
|
|
24
|
+
|
|
25
|
+
The pattern names the thing that should exist. Search for that thing, in the code of the side
|
|
26
|
+
that owns it. Terms below are starting points, not a script: read the repository's own
|
|
27
|
+
vocabulary first and search in it.
|
|
28
|
+
|
|
29
|
+
| Pattern | Owned by | What should exist | Where to look |
|
|
30
|
+
|---|---|---|---|
|
|
31
|
+
| `anti-corruption-layer` | downstream | An adapter or translator on the downstream side: a mapper between the upstream payload and the downstream model, so no upstream type reaches the domain | The downstream context's code. Names like `adapter`, `translator`, `mapper`, `acl`, `client`, `gateway`; a function taking an upstream DTO and returning a domain type |
|
|
32
|
+
| `conformist` | downstream | Direct use of the upstream types: the upstream's own classes, DTOs or generated client, imported into the downstream domain, with no mapping | Imports in the downstream context from the upstream package, module or generated client |
|
|
33
|
+
| `open-host-service` | upstream | A published contract: an OpenAPI, GraphQL, gRPC or AsyncAPI document that others build against, versioned and documented | The upstream context's repository root and its `docs/`, `api/`, `contracts/`, `proto/` folders; a published API reference |
|
|
34
|
+
| `published-language` | upstream | A shared interchange format independent of either side: a JSON Schema, Protobuf, Avro or industry format the events are emitted in | A schema registry, a `schemas/` or `proto/` folder, a package holding only message definitions |
|
|
35
|
+
| `shared-kernel` | both | A shared package, library or schema both sides depend on, small and jointly owned | A workspace package both contexts declare as a dependency; a shared database schema or migration folder |
|
|
36
|
+
| `partnership` | both | Joint planning and release: one pipeline, one release cadence, or features that land in both at once | Release workflows, a shared changelog, commits that touch both sides together |
|
|
37
|
+
| `customer-supplier` | upstream | The downstream's requirements visible on the upstream side: contract tests, a consumer-driven test suite, downstream tickets in the upstream backlog | Contract test folders (`pact`, `contract`, `consumer`), the upstream's issue references |
|
|
38
|
+
| `upstream-downstream` | downstream | A directed dependency, and nothing on the upstream side that plans for the downstream | The downstream's dependency on the upstream, and the absence of the customer-supplier evidence above |
|
|
39
|
+
| `separate-ways` | neither | Nothing: no dependency, no shared package, no calls between the two | Search for the dependency and report that there is none. Finding one is the interesting result |
|
|
40
|
+
|
|
41
|
+
## The three outcomes
|
|
42
|
+
|
|
43
|
+
Every intent you look at ends in exactly one of these. Write a comment in all three cases; a
|
|
44
|
+
reconciliation that says nothing is worse than one that says "I looked and found nothing".
|
|
45
|
+
|
|
46
|
+
**It is there.** Write one comment saying what you found and where, with a link to it. Leave the
|
|
47
|
+
disposition alone; an absent disposition already means `by-design`.
|
|
48
|
+
|
|
49
|
+
**It is not what the model says.** Write one comment saying what is there instead, with a link,
|
|
50
|
+
and propose a disposition:
|
|
51
|
+
|
|
52
|
+
- `tolerated` — the code disagrees with the pattern, the reason is understood, and nobody plans
|
|
53
|
+
to change it. A compromise someone is living with.
|
|
54
|
+
- `refactor` — the intent should be removed or replaced, and someone means to do it. Say in the
|
|
55
|
+
comment what it should become.
|
|
56
|
+
|
|
57
|
+
Propose, never apply. The disposition is the author's judgement about their own architecture:
|
|
58
|
+
show the comment and the disposition you would set, say why in one sentence, and wait.
|
|
59
|
+
|
|
60
|
+
**You cannot tell.** Write one comment naming what you searched for and where, and propose no
|
|
61
|
+
disposition. Then ask the user where it lives. "No adapter or translator found in the Sales
|
|
62
|
+
context; searched for a mapper between the Catalog pet payload and Sales' own types" is useful
|
|
63
|
+
evidence. A guess is not.
|
|
64
|
+
|
|
65
|
+
## Writing the comment
|
|
66
|
+
|
|
67
|
+
A comment is `{ text, link? }`. The text is one or two sentences, in the present tense, saying
|
|
68
|
+
what is in the system — not what you did, and not what the pattern means in general. The link is
|
|
69
|
+
`{ kind, url, label? }` with `kind` one of `code`, `contract`, `adr`, `runbook`, `dashboard`.
|
|
70
|
+
|
|
71
|
+
- Cite what you actually opened. A path or a URL, never a guess at where a file probably is.
|
|
72
|
+
- One statement per comment. Two findings are two comments; the report lists them separately.
|
|
73
|
+
- `label` is what a reader should see instead of the URL: the repo-relative path, the endpoint,
|
|
74
|
+
the ADR's title.
|
|
75
|
+
- Use the repository's own words for its own parts, and the model's words for the model's.
|
|
76
|
+
- Never delete or rewrite a comment an author wrote. Add yours next to it.
|
|
77
|
+
- A comment with no link is still evidence when there is nothing to link to; a link you cannot
|
|
78
|
+
justify is not.
|
|
79
|
+
|
|
80
|
+
In the DSL, `comments` and `disposition` go in the options of `upstreamOf`, `downstreamOf`,
|
|
81
|
+
`partnerOf`, `sharesKernelWith`, `separateWaysFrom`, `provides` and `consumes`:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
salesBC.downstreamOf(catalogBC, {
|
|
85
|
+
type: "customer-supplier",
|
|
86
|
+
upstreamRoles: ["open-host-service"],
|
|
87
|
+
downstreamRoles: ["anti-corruption-layer"],
|
|
88
|
+
description: "Sales needs pet availability; Catalog commits to the summary contract",
|
|
89
|
+
disposition: "tolerated",
|
|
90
|
+
comments: [
|
|
91
|
+
{
|
|
92
|
+
text: "Sales calls the Catalog summary endpoint directly from its order service; there is no translator, so the Catalog pet payload reaches the Sales domain unchanged.",
|
|
93
|
+
link: {
|
|
94
|
+
kind: "code",
|
|
95
|
+
url: "https://github.com/example/petstore/blob/main/sales/src/orders/service.ts",
|
|
96
|
+
label: "sales/src/orders/service.ts",
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
In JSON, both are optional fields on the relationship, the consumable and the consumption:
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"relationships": [
|
|
108
|
+
{
|
|
109
|
+
"upstream": { "$ref": "#/boundedcontexts/catalog" },
|
|
110
|
+
"downstream": { "$ref": "#/boundedcontexts/sales" },
|
|
111
|
+
"type": "customer-supplier",
|
|
112
|
+
"upstreamRoles": ["open-host-service"],
|
|
113
|
+
"downstreamRoles": ["anti-corruption-layer"],
|
|
114
|
+
"description": "Sales needs pet availability; Catalog commits to the summary contract",
|
|
115
|
+
"disposition": "tolerated",
|
|
116
|
+
"comments": [
|
|
117
|
+
{
|
|
118
|
+
"text": "Sales calls the Catalog summary endpoint directly from its order service; there is no translator, so the Catalog pet payload reaches the Sales domain unchanged.",
|
|
119
|
+
"link": {
|
|
120
|
+
"kind": "code",
|
|
121
|
+
"url": "https://github.com/example/petstore/blob/main/sales/src/orders/service.ts",
|
|
122
|
+
"label": "sales/src/orders/service.ts"
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
]
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
A consumable carries the same two fields beside its `pattern`, and so does a consumption:
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
"provides": {
|
|
135
|
+
"get_pet_summary": {
|
|
136
|
+
"name": "GetPetSummary",
|
|
137
|
+
"description": "GET /pets/{id}/summary",
|
|
138
|
+
"type": "operation",
|
|
139
|
+
"pattern": "open-host-service",
|
|
140
|
+
"comments": [
|
|
141
|
+
{
|
|
142
|
+
"text": "The summary endpoint is in the published Catalog OpenAPI document and is versioned with the rest of the API.",
|
|
143
|
+
"link": {
|
|
144
|
+
"kind": "contract",
|
|
145
|
+
"url": "https://github.com/example/petstore/blob/main/catalog/openapi.yaml",
|
|
146
|
+
"label": "GET /pets/{id}/summary"
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
"consumes": [
|
|
156
|
+
{
|
|
157
|
+
"consumable": {
|
|
158
|
+
"$ref": "#/boundedcontexts/catalog/services/pet_app/provides/get_pet_summary"
|
|
159
|
+
},
|
|
160
|
+
"pattern": "anti-corruption-layer",
|
|
161
|
+
"disposition": "refactor",
|
|
162
|
+
"comments": [
|
|
163
|
+
{
|
|
164
|
+
"text": "No adapter or translator found in Sales; searched the order service and its client folder for a mapper between the Catalog pet payload and the Sales order model.",
|
|
165
|
+
"link": {
|
|
166
|
+
"kind": "code",
|
|
167
|
+
"url": "https://github.com/example/petstore/tree/main/sales/src/orders",
|
|
168
|
+
"label": "sales/src/orders"
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
]
|
|
172
|
+
}
|
|
173
|
+
]
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`by-design` is the meaning of an absent `disposition` and is never written to the file. Writing
|
|
177
|
+
`"disposition": "by-design"` is the same as leaving it out; leave it out.
|
|
178
|
+
|
|
179
|
+
## Reporting back
|
|
180
|
+
|
|
181
|
+
When the pass is done, summarise in the user's words, not in DDD words: how many intents you
|
|
182
|
+
looked at, how many now have evidence, which ones you would mark `tolerated` or `refactor` and
|
|
183
|
+
why, and which ones you could not resolve and need them to answer. Then validate: the health
|
|
184
|
+
report and the opt-in `comments-required` rule both read what you just wrote.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<!-- Generated by packages/skill/scripts/generate.mts from @open-domain-specification/core (PATTERNS); do not edit. -->
|
|
2
|
+
|
|
3
|
+
# Strategic relationships
|
|
4
|
+
|
|
5
|
+
What each strategic pattern means. Explain one to the user in these words: the diagram legend, the hover summaries and the generated documentation all read the same table, so your explanation and what they are looking at agree. The value in the left column is what goes in the model.
|
|
6
|
+
|
|
7
|
+
## Relationship types
|
|
8
|
+
|
|
9
|
+
The `type` of a context relationship. Exactly one per relationship.
|
|
10
|
+
|
|
11
|
+
### `upstream-downstream` — Upstream/Downstream (U/D)
|
|
12
|
+
|
|
13
|
+
One context depends on another; the upstream does not plan around the downstream.
|
|
14
|
+
|
|
15
|
+
A directed dependency with no customer commitment: the upstream evolves on its own schedule and the downstream adapts through its roles.
|
|
16
|
+
|
|
17
|
+
- Upstream keeps full autonomy
|
|
18
|
+
- Downstream carries the integration risk
|
|
19
|
+
|
|
20
|
+
### `customer-supplier` — Customer/Supplier (C/S)
|
|
21
|
+
|
|
22
|
+
Upstream plans for and prioritizes downstream requirements.
|
|
23
|
+
|
|
24
|
+
An asymmetric relationship where downstream needs act as customer requirements and upstream delivery commitments factor in downstream deadlines.
|
|
25
|
+
|
|
26
|
+
- Predictable alignment between collaborating teams
|
|
27
|
+
- Upstream velocity can be constrained by downstream dependencies
|
|
28
|
+
|
|
29
|
+
### `shared-kernel` — Shared Kernel (SK)
|
|
30
|
+
|
|
31
|
+
A shared subset of domain model and code, co-owned by both teams.
|
|
32
|
+
|
|
33
|
+
A strictly bounded shared library, schema, or database subset. Neither team alters the kernel without joint consultation and continuous test verification.
|
|
34
|
+
|
|
35
|
+
- Prevents duplicate modeling and translation costs
|
|
36
|
+
- High coordination friction; degrades autonomy if it grows beyond a small subset
|
|
37
|
+
|
|
38
|
+
### `partnership` — Partnership (P)
|
|
39
|
+
|
|
40
|
+
Mutual co-operation where teams coordinate development and releases.
|
|
41
|
+
|
|
42
|
+
Two contexts succeed or fail together. Features spanning both are planned, co-designed, and released in synchronized cycles.
|
|
43
|
+
|
|
44
|
+
- Tight strategic cohesion across organizational boundaries
|
|
45
|
+
- Requires close communication and joint release cadences
|
|
46
|
+
|
|
47
|
+
### `separate-ways` — Separate Ways (SW)
|
|
48
|
+
|
|
49
|
+
A deliberate decision to forego integration and develop independently.
|
|
50
|
+
|
|
51
|
+
Both contexts solve their requirements without technical links, accepting possible domain overlap to keep complete operational independence.
|
|
52
|
+
|
|
53
|
+
- Maximum operational autonomy with no cross-team dependencies
|
|
54
|
+
- Possible duplication of data and business logic
|
|
55
|
+
|
|
56
|
+
## Upstream roles
|
|
57
|
+
|
|
58
|
+
How the upstream side exposes what it provides: a relationship's `upstreamRoles`, and a consumable's `pattern`.
|
|
59
|
+
|
|
60
|
+
### `open-host-service` — Open Host Service (OHS)
|
|
61
|
+
|
|
62
|
+
A public, stable protocol or API provided by an upstream context.
|
|
63
|
+
|
|
64
|
+
The upstream context commits to maintaining a standardized, backward-compatible interface so multiple downstream subsystems can integrate without bespoke integration logic.
|
|
65
|
+
|
|
66
|
+
- Reduces coupling across multiple consumers
|
|
67
|
+
- Increases upstream maintenance overhead and versioning obligations
|
|
68
|
+
|
|
69
|
+
### `published-language` — Published Language (PL)
|
|
70
|
+
|
|
71
|
+
A well-documented shared interchange format.
|
|
72
|
+
|
|
73
|
+
An explicit schema standard (JSON Schema, Protobuf, an industry XML) that expresses domain operations and events independently of either context's internal representation.
|
|
74
|
+
|
|
75
|
+
- Enables polyglot integrations and widespread consumption
|
|
76
|
+
- Requires governance over schema evolution
|
|
77
|
+
|
|
78
|
+
## Downstream roles
|
|
79
|
+
|
|
80
|
+
How the downstream side protects itself: a relationship's `downstreamRoles`, and a consumption's `pattern`.
|
|
81
|
+
|
|
82
|
+
### `anti-corruption-layer` — Anti-Corruption Layer (ACL)
|
|
83
|
+
|
|
84
|
+
A translating boundary isolating a downstream model from external concepts.
|
|
85
|
+
|
|
86
|
+
A translating mechanism (adapters, facades, mappers) that keeps foreign domain concepts, schema changes, or vendor anomalies from leaking into the downstream model.
|
|
87
|
+
|
|
88
|
+
- Maximum isolation and autonomy for the downstream context
|
|
89
|
+
- Cost of maintaining translation logic and data mappings
|
|
90
|
+
|
|
91
|
+
### `conformist` — Conformist (CF)
|
|
92
|
+
|
|
93
|
+
Downstream adopts the upstream domain model without translation.
|
|
94
|
+
|
|
95
|
+
The downstream team accepts the upstream model as-is, dropping translation layers when the upstream model fits well or translation overhead is unjustified.
|
|
96
|
+
|
|
97
|
+
- No translation and a simpler codebase
|
|
98
|
+
- Exposed to breaking upstream schema changes
|
|
@@ -17,9 +17,9 @@ element lives in a workspace file; `DSL` is the core call that creates it.
|
|
|
17
17
|
| "the old system", "legacy", "nobody understands its schema" | big ball of mud | `"bigBallOfMud": true` | `bigBallOfMud: true` |
|
|
18
18
|
| "A depends on B", "A calls B", "A reads B's data" | upstream-downstream (B upstream) | `relationships[]` `type: "upstream-downstream"` | `a.downstreamOf(b, {...})` |
|
|
19
19
|
| "they ask us before changing", "we're their customer" | customer-supplier | `type: "customer-supplier"` | `a.downstreamOf(b, {type: "customer-supplier", ...})` |
|
|
20
|
-
| "both teams change it together", "we release together" | partnership | `type: "partnership"`, `participants` | `a.partnerOf(b)` |
|
|
21
|
-
| "we share the same tables / library / code" | shared kernel | `type: "shared-kernel"` | `a.sharesKernelWith(b)` |
|
|
22
|
-
| "we deliberately don't integrate" | separate ways | `type: "separate-ways"` | `a.separateWaysFrom(b, why)` |
|
|
20
|
+
| "both teams change it together", "we release together" | partnership | `type: "partnership"`, `participants` | `a.partnerOf(b, {description})` |
|
|
21
|
+
| "we share the same tables / library / code" | shared kernel | `type: "shared-kernel"` | `a.sharesKernelWith(b, {description})` |
|
|
22
|
+
| "we deliberately don't integrate" | separate ways | `type: "separate-ways"` | `a.separateWaysFrom(b, {description: why})` |
|
|
23
23
|
| "we use their API as-is", "we take whatever they send" | conformist | consumption `pattern: "conformist"`; relationship `downstreamRoles` | `agg.consumes(c, {pattern: "conformist"})` |
|
|
24
24
|
| "we copy and reshape their data", "we wrap their API" | anti-corruption layer | `pattern: "anti-corruption-layer"` | `agg.consumes(c, {pattern: "anti-corruption-layer"})` |
|
|
25
25
|
| "we expose a documented API", "there's a REST endpoint" | open host service | operation `pattern: "open-host-service"`; relationship `upstreamRoles` | `svc.provides(name, {type: "operation", pattern: "open-host-service"})` |
|
|
@@ -75,3 +75,11 @@
|
|
|
75
75
|
**Why it matters:** A context that serves no subdomain has no place in the problem-space view, so nobody can see which part of the business it exists for.
|
|
76
76
|
|
|
77
77
|
**Usual fix:** Add the subdomain the context serves to its subdomains list.
|
|
78
|
+
|
|
79
|
+
## `comments-required` (warning)
|
|
80
|
+
|
|
81
|
+
**Requires:** Every context relationship carries at least one comment. Opt-in: set options.rules.commentsRequired on the workspace.
|
|
82
|
+
|
|
83
|
+
**Why it matters:** A relationship is a claim about how two teams meet; without a note saying where that shows up in the real system, nobody can tell whether the map is still true.
|
|
84
|
+
|
|
85
|
+
**Usual fix:** Add a comment to the relationship saying what backs it in the code, or turn options.rules.commentsRequired off while the evidence layer is still being written.
|