@alveolus/arch 0.1.0 → 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/README.md +8 -1
- package/dist/bin.mjs +4 -2
- package/dist/bin.mjs.map +1 -1
- package/dist/{cli-CwPCGjDg.mjs → docs-DsQHpTtV.mjs} +287 -38
- package/dist/docs-DsQHpTtV.mjs.map +1 -0
- package/dist/index.d.mts +90 -36
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/docs/core/application/command-handlers.md +617 -0
- package/docs/core/application/event-publishers.md +234 -0
- package/docs/core/application/event-translators.md +329 -0
- package/docs/core/application/index.md +99 -0
- package/docs/core/application/integration-events.md +277 -0
- package/docs/core/application/outbox.md +416 -0
- package/docs/core/application/query-handlers.md +292 -0
- package/docs/core/application/unit-of-work.md +352 -0
- package/docs/core/domain/aggregates.md +822 -0
- package/docs/core/domain/domain-errors.md +251 -0
- package/docs/core/domain/domain-events.md +292 -0
- package/docs/core/domain/domain-services.md +249 -0
- package/docs/core/domain/entities.md +431 -0
- package/docs/core/domain/index.md +93 -0
- package/docs/core/domain/ports.md +284 -0
- package/docs/core/domain/repositories.md +335 -0
- package/docs/core/domain/value-objects.md +425 -0
- package/docs/core/domain/views.md +265 -0
- package/docs/core/index.md +108 -0
- package/docs/core/strategic/anti-corruption-layers.md +349 -0
- package/docs/core/strategic/index.md +83 -0
- package/docs/core/strategic/open-host-services.md +287 -0
- package/docs/core/strategic/published-language.md +265 -0
- package/docs/core/utilities/result.md +413 -0
- package/docs/guide/agents.md +68 -0
- package/docs/guide/existing-project.md +105 -0
- package/docs/guide/getting-started.md +275 -0
- package/docs/guide/learning-path.md +123 -0
- package/docs/guide/project-layout.md +324 -0
- package/docs/guide/versioning.md +42 -0
- package/docs/integrations/index.md +112 -0
- package/docs/integrations/nestjs.md +169 -0
- package/docs/rules/index.md +183 -0
- package/docs/rules/layers/no-driving-shortcut.md +119 -0
- package/docs/rules/layers/no-impure-domain.md +189 -0
- package/docs/rules/layers/no-outward-import.md +184 -0
- package/docs/rules/layers/no-portless-adapter.md +123 -0
- package/docs/rules/strategic/no-cross-context-import.md +140 -0
- package/docs/rules/strategic/no-fat-shared-kernel.md +81 -0
- package/docs/rules/strategic/no-leaky-host-service.md +107 -0
- package/docs/rules/strategic/no-unmapped-context.md +111 -0
- package/docs/rules/tactical/no-aggregate-reference.md +139 -0
- package/docs/rules/tactical/no-foreign-command-dependency.md +119 -0
- package/docs/rules/tactical/no-foreign-query-dependency.md +106 -0
- package/docs/rules/tactical/no-loose-code.md +171 -0
- package/docs/rules/tactical/no-misplaced-class.md +146 -0
- package/docs/rules/tactical/no-public-field.md +113 -0
- package/docs/rules/tactical/no-stateful-service.md +102 -0
- package/docs/rules/tactical/no-thrown-failure.md +162 -0
- package/docs/rules/tooling/no-loose-disable.md +98 -0
- package/package.json +4 -3
- package/dist/cli-CwPCGjDg.mjs.map +0 -1
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Repositories in Domain-Driven Design with TypeScript: ports that load and save aggregates for commands and views for queries, as if they were collections."
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Repositories
|
|
6
|
+
|
|
7
|
+
A repository is a port that loads and saves what the application works on, as if it were a
|
|
8
|
+
collection: aggregates for commands, views for queries.
|
|
9
|
+
|
|
10
|
+
<dl class="al-glance">
|
|
11
|
+
<dt>Layer</dt><dd>Domain, implemented by a driven adapter</dd>
|
|
12
|
+
<dt>File</dt><dd><code>domain/repositories/orders.repository.ts</code></dd>
|
|
13
|
+
<dt>Extends</dt><dd><a href="#api"><code>CommandRepository<Aggregate></code></a>, <a href="#api"><code>QueryRepository<View></code></a></dd>
|
|
14
|
+
<dt>Used by</dt><dd><a href="/core/application/command-handlers">Command handlers</a>, <a href="/core/application/query-handlers">query handlers</a></dd>
|
|
15
|
+
<dt>Checked by</dt><dd><a href="/rules/tactical/no-misplaced-class"><code>tactical/no-misplaced-class</code></a>, <a href="/rules/tactical/no-foreign-command-dependency"><code>tactical/no-foreign-command-dependency</code></a>, <a href="/rules/tactical/no-foreign-query-dependency"><code>tactical/no-foreign-query-dependency</code></a>, <a href="/rules/layers/no-portless-adapter"><code>layers/no-portless-adapter</code></a></dd>
|
|
16
|
+
</dl>
|
|
17
|
+
|
|
18
|
+
## Why
|
|
19
|
+
|
|
20
|
+
Placing an order loads it, changes it and saves it. If the handler writes the SQL, it must reach
|
|
21
|
+
into the private fields of `Order` to store them, every handler that touches orders repeats the
|
|
22
|
+
mapping, and no handler runs without a database.
|
|
23
|
+
|
|
24
|
+
::: tip The fix
|
|
25
|
+
The domain declares `Orders`, a collection of orders: `findById` and `save`. An adapter stores the
|
|
26
|
+
order's snapshot in its tables. The handler asks the collection and never sees a table.
|
|
27
|
+
:::
|
|
28
|
+
|
|
29
|
+
## How it works
|
|
30
|
+
|
|
31
|
+
Alveolus splits repositories by side. A command needs the aggregate, with its rules. A query needs
|
|
32
|
+
a shape to show, without loading the aggregate.
|
|
33
|
+
|
|
34
|
+
| | Command repository | Query repository |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| **Hands out** | an [aggregate](./aggregates.md) | a [view](./views.md) |
|
|
37
|
+
| **Extends** | `CommandRepository<Order>` | `QueryRepository<OrderSummary>` |
|
|
38
|
+
| **Used by** | [command handlers](../application/command-handlers.md) | [query handlers](../application/query-handlers.md) |
|
|
39
|
+
| **Methods** | `findById`, `save`, and yours | yours, read only |
|
|
40
|
+
|
|
41
|
+
The adapter of a command repository goes through the aggregate's snapshot, never its fields:
|
|
42
|
+
|
|
43
|
+
<div class="al-cards">
|
|
44
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">1</span>findById</span>Read the row, rebuild the aggregate with <code>Order.fromSnapshot(…)</code>.</div>
|
|
45
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">2</span>save</span>Take <code>order.toSnapshot()</code>, write it to the tables.</div>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
export abstract class Orders extends CommandRepository<Order> {}
|
|
50
|
+
|
|
51
|
+
export abstract class OrderSummaries extends QueryRepository<
|
|
52
|
+
OrderSummary
|
|
53
|
+
> {
|
|
54
|
+
abstract summaryOf(id: OrderId): Promise<OrderSummary | undefined>;
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Where it fits
|
|
59
|
+
|
|
60
|
+
In the PlaceOrder flow, the command handler uses the repository twice: to load the order, and to
|
|
61
|
+
save it once changed.
|
|
62
|
+
|
|
63
|
+
<div class="al-diagram">
|
|
64
|
+
<svg viewBox="0 0 680 300" role="img" aria-label="The PlaceOrderHandler loads the Order through the Orders repository, calls order.place, saves the order through the repository and adds its events to the outbox.">
|
|
65
|
+
<defs>
|
|
66
|
+
<marker id="repository-flow-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
|
67
|
+
<path class="arrow" d="M 0 0 L 10 5 L 0 10 z" />
|
|
68
|
+
</marker>
|
|
69
|
+
</defs>
|
|
70
|
+
<rect class="box" x="8" y="122" width="130" height="56" rx="8" />
|
|
71
|
+
<text class="label" x="73" y="146" text-anchor="middle">Controller</text>
|
|
72
|
+
<text class="note" x="73" y="166" text-anchor="middle">driving adapter</text>
|
|
73
|
+
<path class="link" d="M 138 150 L 178 150" marker-end="url(#repository-flow-arrow)" />
|
|
74
|
+
<rect class="box" x="180" y="122" width="180" height="56" rx="8" />
|
|
75
|
+
<text class="label" x="270" y="146" text-anchor="middle">PlaceOrderHandler</text>
|
|
76
|
+
<text class="note" x="270" y="166" text-anchor="middle">command handler</text>
|
|
77
|
+
<text class="note" x="270" y="204" text-anchor="middle">one unit of work</text>
|
|
78
|
+
<rect class="boundary" x="440" y="24" width="232" height="48" rx="8" />
|
|
79
|
+
<text class="label" x="556" y="44" text-anchor="middle">1 · orders.findById(id)</text>
|
|
80
|
+
<text class="note" x="556" y="62" text-anchor="middle">this page: loads it</text>
|
|
81
|
+
<rect class="box" x="440" y="92" width="232" height="48" rx="8" />
|
|
82
|
+
<text class="label" x="556" y="112" text-anchor="middle">2 · order.place(…)</text>
|
|
83
|
+
<text class="note" x="556" y="130" text-anchor="middle">rules + event</text>
|
|
84
|
+
<rect class="boundary" x="440" y="160" width="232" height="48" rx="8" />
|
|
85
|
+
<text class="label" x="556" y="180" text-anchor="middle">3 · orders.save(order)</text>
|
|
86
|
+
<text class="note" x="556" y="198" text-anchor="middle">this page: stores it</text>
|
|
87
|
+
<rect class="box" x="440" y="228" width="232" height="48" rx="8" />
|
|
88
|
+
<text class="label" x="556" y="248" text-anchor="middle">4 · outbox.add(events)</text>
|
|
89
|
+
<text class="note" x="556" y="266" text-anchor="middle">hands over its events</text>
|
|
90
|
+
<path class="link" d="M 360 150 L 438 48" marker-end="url(#repository-flow-arrow)" />
|
|
91
|
+
<path class="link" d="M 360 150 L 438 116" marker-end="url(#repository-flow-arrow)" />
|
|
92
|
+
<path class="link" d="M 360 150 L 438 184" marker-end="url(#repository-flow-arrow)" />
|
|
93
|
+
<path class="link" d="M 360 150 L 438 252" marker-end="url(#repository-flow-arrow)" />
|
|
94
|
+
</svg>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
::: tip
|
|
98
|
+
A query takes the other side: the [query handler](../application/query-handlers.md) asks a query
|
|
99
|
+
repository for a [view](./views.md), and no aggregate is loaded.
|
|
100
|
+
:::
|
|
101
|
+
|
|
102
|
+
## API
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { CommandRepository, QueryRepository } from "@alveolus/core";
|
|
106
|
+
// or: from "@alveolus/core/repositories"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Type parameters
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
abstract class CommandRepository<
|
|
113
|
+
Aggregate extends AnyAggregateRoot,
|
|
114
|
+
> extends Port { … }
|
|
115
|
+
|
|
116
|
+
abstract class QueryRepository<View extends object> extends Port {}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
| Parameter | What it is | Constraint |
|
|
120
|
+
| --- | --- | --- |
|
|
121
|
+
| `Aggregate` | The aggregate a command repository holds. | extends `AggregateRoot` |
|
|
122
|
+
| `View` | The view a query repository reads. | an object type |
|
|
123
|
+
|
|
124
|
+
### CommandRepository
|
|
125
|
+
|
|
126
|
+
#### `findById(id)` <Badge type="info" text="abstract" /> <Badge type="tip" text="called by the command handler" />
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
abstract findById(
|
|
130
|
+
id: Aggregate["id"],
|
|
131
|
+
): Promise<Aggregate | undefined>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Loads the aggregate by its identifier; `undefined` when it is missing. Only the identifier of its
|
|
135
|
+
aggregate compiles.
|
|
136
|
+
|
|
137
|
+
#### `save(aggregate)` <Badge type="info" text="abstract" /> <Badge type="tip" text="called by the command handler" />
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
abstract save(aggregate: Aggregate): Promise<void>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Stores the aggregate. It leaves the pending events: the handler pulls them after.
|
|
144
|
+
|
|
145
|
+
Declare the repository abstract in `domain/repositories/`, with the methods your commands need,
|
|
146
|
+
and implement it in `driven/<technology>/adapters/`:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
abstract class Orders extends CommandRepository<Order> {}
|
|
150
|
+
class PgOrders extends Orders { … }
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### QueryRepository
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
abstract class OrderSummaries extends QueryRepository<OrderSummary> {
|
|
157
|
+
abstract findById(id: OrderId): Promise<OrderSummary | undefined>;
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`QueryRepository` has no members of its own: declare the abstract methods your queries need. They
|
|
162
|
+
are called by the query handler and hand out the view, or a collection of it.
|
|
163
|
+
|
|
164
|
+
::: warning Caveats
|
|
165
|
+
- `findById` only accepts the identifier of its aggregate: passing an `OrderId` to a
|
|
166
|
+
`Customers` repository does not compile.
|
|
167
|
+
- A command handler depends on command repositories only, a query handler on query repositories
|
|
168
|
+
only ([`tactical/no-foreign-command-dependency`](../../rules/tactical/no-foreign-command-dependency.md), [`tactical/no-foreign-query-dependency`](../../rules/tactical/no-foreign-query-dependency.md)).
|
|
169
|
+
- No version is kept: to prevent lost updates, put a `version` in the snapshot and check it in the
|
|
170
|
+
adapter's `UPDATE`.
|
|
171
|
+
:::
|
|
172
|
+
|
|
173
|
+
## Usage
|
|
174
|
+
|
|
175
|
+
Build `Orders`, the command repository of the `Order` aggregate, then implement it with
|
|
176
|
+
PostgreSQL. Each step shows the whole file it changes: added lines are highlighted, replaced lines are struck out.
|
|
177
|
+
|
|
178
|
+
<div class="al-cards">
|
|
179
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">1</span><a href="#_1-know-what-it-stores">Know what it stores</a></span>One aggregate, as a snapshot.</div>
|
|
180
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">2</span><a href="#_2-declare-the-repository">Declare the repository</a></span>An abstract class in the domain.</div>
|
|
181
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">3</span><a href="#_3-add-what-the-commands-need">Add what the commands need</a></span>And nothing for screens.</div>
|
|
182
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">4</span><a href="#_4-implement-it-in-a-driven-adapter">Implement it in a driven adapter</a></span>Snapshot in, snapshot out.</div>
|
|
183
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">5</span><a href="#_5-use-it-from-a-handler">Use it from a handler</a></span>Load, change, save.</div>
|
|
184
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">6</span><a href="#_6-check-it">Check it</a></span>Let the rules keep it that way.</div>
|
|
185
|
+
</div>
|
|
186
|
+
|
|
187
|
+
### 1. Know what it stores
|
|
188
|
+
|
|
189
|
+
A command repository loads and saves one aggregate as a whole: here the
|
|
190
|
+
[`Order` aggregate](./aggregates.md), saved as its `OrderSnapshot`.
|
|
191
|
+
|
|
192
|
+
### 2. Declare the repository
|
|
193
|
+
|
|
194
|
+
So that the domain loads and saves an order without knowing the database, the repository is an
|
|
195
|
+
abstract class named after the collection. `findById` and `save` come with `CommandRepository`.
|
|
196
|
+
|
|
197
|
+
```ts [src/ordering/domain/repositories/orders.repository.ts]
|
|
198
|
+
import { CommandRepository } from "@alveolus/core";
|
|
199
|
+
|
|
200
|
+
import type { Order } from "../aggregates/order.aggregate";
|
|
201
|
+
|
|
202
|
+
export abstract class Orders extends CommandRepository<Order> {}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### 3. Add what the commands need
|
|
206
|
+
|
|
207
|
+
Add only what a command needs, in the words of the domain: here, the drafts of a customer. Reads
|
|
208
|
+
for a screen go to a [query repository](#queryrepository) instead.
|
|
209
|
+
|
|
210
|
+
```ts [src/ordering/domain/repositories/orders.repository.ts]
|
|
211
|
+
import { CommandRepository } from "@alveolus/core";
|
|
212
|
+
|
|
213
|
+
import type { Order } from "../aggregates/order.aggregate";
|
|
214
|
+
import type { // [!code ++]
|
|
215
|
+
CustomerId, // [!code ++]
|
|
216
|
+
} from "../value-objects/customer-id.identifier"; // [!code ++]
|
|
217
|
+
|
|
218
|
+
export abstract class Orders extends CommandRepository<Order> {} // [!code --]
|
|
219
|
+
export abstract class Orders extends CommandRepository<Order> { // [!code ++]
|
|
220
|
+
abstract findDraftsOf( // [!code ++]
|
|
221
|
+
customerId: CustomerId, // [!code ++]
|
|
222
|
+
): Promise<readonly Order[]>; // [!code ++]
|
|
223
|
+
} // [!code ++]
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### 4. Implement it in a driven adapter
|
|
227
|
+
|
|
228
|
+
The adapter maps the snapshot to storage: `toSnapshot()` to write, `fromSnapshot()` to read. The
|
|
229
|
+
aggregate never sees a row.
|
|
230
|
+
|
|
231
|
+
```ts [src/ordering/driven/pg/adapters/pg-orders.adapter.ts]
|
|
232
|
+
import { Order } from "../../../domain/aggregates/order.aggregate";
|
|
233
|
+
import {
|
|
234
|
+
Orders,
|
|
235
|
+
} from "../../../domain/repositories/orders.repository";
|
|
236
|
+
import type {
|
|
237
|
+
CustomerId,
|
|
238
|
+
} from "../../../domain/value-objects/customer-id.identifier";
|
|
239
|
+
import type {
|
|
240
|
+
OrderId,
|
|
241
|
+
} from "../../../domain/value-objects/order-id.identifier";
|
|
242
|
+
|
|
243
|
+
export class PgOrders extends Orders {
|
|
244
|
+
constructor(private readonly db: Database) {
|
|
245
|
+
super();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async findById(id: OrderId): Promise<Order | undefined> {
|
|
249
|
+
const row = await this.db
|
|
250
|
+
.selectFrom("orders")
|
|
251
|
+
.where("id", "=", id.value)
|
|
252
|
+
.selectAll()
|
|
253
|
+
.executeTakeFirst();
|
|
254
|
+
if (row === undefined) {
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
return Order.fromSnapshot({
|
|
258
|
+
id: row.id,
|
|
259
|
+
customerId: row.customer_id,
|
|
260
|
+
status: row.status,
|
|
261
|
+
lines: row.lines,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async save(order: Order): Promise<void> {
|
|
266
|
+
const snapshot = order.toSnapshot();
|
|
267
|
+
const row = {
|
|
268
|
+
id: snapshot.id,
|
|
269
|
+
customer_id: snapshot.customerId,
|
|
270
|
+
status: snapshot.status,
|
|
271
|
+
lines: JSON.stringify(snapshot.lines),
|
|
272
|
+
};
|
|
273
|
+
await this.db
|
|
274
|
+
.insertInto("orders")
|
|
275
|
+
.values(row)
|
|
276
|
+
.onConflict((conflict) =>
|
|
277
|
+
conflict.column("id").doUpdateSet(row),
|
|
278
|
+
)
|
|
279
|
+
.execute();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async findDraftsOf(
|
|
283
|
+
customerId: CustomerId,
|
|
284
|
+
): Promise<readonly Order[]> { … }
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### 5. Use it from a handler
|
|
289
|
+
|
|
290
|
+
A [command handler](../application/command-handlers.md) loads the order, calls one business
|
|
291
|
+
method and saves it, in one unit of work.
|
|
292
|
+
|
|
293
|
+
```ts [src/ordering/application/commands/place-order.command.ts]
|
|
294
|
+
const order = await this.orders.findById(new OrderId(orderId));
|
|
295
|
+
if (order === undefined) {
|
|
296
|
+
return err(new OrderNotFound({ orderId }));
|
|
297
|
+
}
|
|
298
|
+
const placed = order.place(this.ids.next(), this.clock.now());
|
|
299
|
+
if (!placed.ok) {
|
|
300
|
+
return placed;
|
|
301
|
+
}
|
|
302
|
+
await this.orders.save(order);
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### 6. Check it
|
|
306
|
+
|
|
307
|
+
Run the checks. Three rules keep the repository the way it is now:
|
|
308
|
+
|
|
309
|
+
```sh
|
|
310
|
+
npx alveolus arch check
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
<div class="al-cards">
|
|
314
|
+
<div class="al-card"><span class="al-card-title"><a href="../../rules/layers/no-portless-adapter"><code>no-portless-adapter</code></a></span><code>PgOrders</code> extends <code>Orders</code>, declared in <code>domain/repositories/</code>.</div>
|
|
315
|
+
<div class="al-card"><span class="al-card-title"><a href="../../rules/tactical/no-foreign-query-dependency"><code>no-foreign-query-dependency</code></a></span>Only command handlers receive <code>Orders</code>; queries read views.</div>
|
|
316
|
+
<div class="al-card"><span class="al-card-title"><a href="../../rules/tactical/no-misplaced-class"><code>no-misplaced-class</code></a></span>It stays alone in <code>domain/repositories/*.repository.ts</code>.</div>
|
|
317
|
+
</div>
|
|
318
|
+
|
|
319
|
+
A query handler that receives it is reported:
|
|
320
|
+
|
|
321
|
+
```
|
|
322
|
+
src/ordering/application/queries/get-order-summary.query.ts
|
|
323
|
+
20 error tactical/no-foreign-query-dependency: The QueryHandler
|
|
324
|
+
GetOrderSummaryHandler receives Orders, a CommandRepository: a
|
|
325
|
+
query handler receives query repositories, ports that do not
|
|
326
|
+
write, and value objects.
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## See also
|
|
330
|
+
|
|
331
|
+
- [Aggregates](./aggregates.md), what a command repository holds, and their snapshots
|
|
332
|
+
- [Views](./views.md), what a query repository returns
|
|
333
|
+
- [Ports](./ports.md), the other dependencies of the domain
|
|
334
|
+
- Rules: [`tactical/no-foreign-command-dependency`](../../rules/tactical/no-foreign-command-dependency.md), [`tactical/no-foreign-query-dependency`](../../rules/tactical/no-foreign-query-dependency.md), [`layers/no-portless-adapter`](../../rules/layers/no-portless-adapter.md)
|
|
335
|
+
- Vaughn Vernon, *Implementing Domain-Driven Design*, chapter 12, "Repositories"
|