@archi-code/ddd 3.0.0-alpha.1 → 3.0.0-alpha.2
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 +478 -376
- package/SKILL.md +10 -8
- package/ddd-skills/archi-code-ddd/imports.md +28 -18
- package/ddd-skills/archi-code-ddd/layers.md +68 -53
- package/ddd-skills/archi-code-ddd/patterns.md +50 -43
- package/ddd-skills/archi-code-ddd/what-not-to-do.md +25 -18
- package/ddd-skills/archi-code-ddd/where-each-class-goes.md +11 -10
- package/ddd-skills/ddd-agregados/GUIA.md +2 -2
- package/ddd-skills/ddd-agregados/references/consistencia-eventual.md +5 -6
- package/ddd-skills/ddd-agregados/references/fabricas.md +5 -8
- package/ddd-skills/ddd-agregados/references/implementacion-ts.md +22 -27
- package/ddd-skills/ddd-arquitectura/GUIA.md +4 -4
- package/ddd-skills/ddd-arquitectura/assets/dependency-cruiser.cjs +13 -6
- package/ddd-skills/ddd-arquitectura/references/estilos-adicionales.md +2 -2
- package/ddd-skills/ddd-arquitectura/references/estructura-hexagonal-ts.md +70 -16
- package/ddd-skills/ddd-auditoria-refactorizacion/GUIA.md +1 -1
- package/ddd-skills/ddd-auditoria-refactorizacion/references/ejemplo-auditoria.md +9 -11
- package/ddd-skills/ddd-auditoria-refactorizacion/references/lista-comprobacion.md +4 -4
- package/ddd-skills/ddd-auditoria-refactorizacion/references/plan-migracion.md +2 -2
- package/ddd-skills/ddd-context-mapping/GUIA.md +5 -5
- package/ddd-skills/ddd-context-mapping/references/anticorrupcion-ts.md +33 -18
- package/ddd-skills/ddd-context-mapping/references/estilos-integracion.md +4 -7
- package/ddd-skills/ddd-event-sourcing-cqrs/GUIA.md +2 -2
- package/ddd-skills/ddd-event-sourcing-cqrs/references/event-sourcing-ts.md +32 -27
- package/ddd-skills/ddd-event-sourcing-cqrs/references/proyecciones-cqrs-ts.md +7 -7
- package/ddd-skills/ddd-eventos-dominio/GUIA.md +3 -3
- package/ddd-skills/ddd-eventos-dominio/references/diseno-eventos.md +15 -16
- package/ddd-skills/ddd-eventos-dominio/references/ejemplo-ecommerce.md +11 -11
- package/ddd-skills/ddd-eventos-dominio/references/publicacion-ts.md +34 -31
- package/ddd-skills/ddd-repositorios-persistencia/GUIA.md +7 -7
- package/ddd-skills/ddd-repositorios-persistencia/references/implementacion-ts.md +50 -32
- package/ddd-skills/ddd-repositorios-persistencia/references/tests-repositorios.md +31 -17
- package/ddd-skills/ddd-repositorios-persistencia/references/transacciones-ts.md +21 -14
- package/ddd-skills/ddd-servicios-capa-aplicacion/GUIA.md +2 -2
- package/ddd-skills/ddd-servicios-capa-aplicacion/references/capa-aplicacion-ts.md +19 -19
- package/ddd-skills/ddd-servicios-capa-aplicacion/references/ejemplo-ecommerce.md +12 -15
- package/ddd-skills/ddd-servicios-capa-aplicacion/references/servicios-dominio-ts.md +5 -6
- package/ddd-skills/ddd-value-objects-entidades/GUIA.md +3 -3
- package/ddd-skills/ddd-value-objects-entidades/references/entidades-ts.md +7 -9
- package/ddd-skills/ddd-value-objects-entidades/references/persistencia-valores.md +5 -5
- package/ddd-skills/ddd-value-objects-entidades/references/validacion.md +9 -9
- package/ddd-skills/ddd-value-objects-entidades/references/value-objects-ts.md +2 -3
- package/dist/cjs/application/event/listens-to.js +4 -4
- package/dist/cjs/{exceptions/application → application/exceptions}/aggregate-not-found.exception.d.ts +1 -1
- package/dist/cjs/{exceptions/application → application/exceptions}/aggregate-not-found.exception.js +4 -4
- package/dist/cjs/application/exceptions/application-exception-code.d.ts +7 -0
- package/dist/cjs/application/exceptions/application-exception-code.js +11 -0
- package/dist/cjs/application/exceptions/application.exception.d.ts +6 -0
- package/dist/cjs/application/exceptions/application.exception.js +16 -0
- package/dist/cjs/{exceptions/application → application/exceptions}/concurrency.exception.d.ts +1 -1
- package/dist/cjs/{exceptions/application → application/exceptions}/concurrency.exception.js +4 -4
- package/dist/cjs/{exceptions/application → application/exceptions}/not-allowed.exception.d.ts +1 -1
- package/dist/cjs/{exceptions/application → application/exceptions}/not-allowed.exception.js +4 -4
- package/dist/cjs/application/exceptions/unexpected-error.exception.d.ts +5 -0
- package/dist/cjs/application/exceptions/unexpected-error.exception.js +17 -0
- package/dist/cjs/application/index.d.ts +7 -0
- package/dist/cjs/application/index.js +15 -1
- package/dist/cjs/application/use-case/use-case.js +3 -2
- package/dist/{esm/domain/type/value-objects/value-objects.d.ts → cjs/application/value-object-builder/value-object-builder.d.ts} +3 -3
- package/dist/cjs/application/value-object-builder/value-object-builder.js +51 -0
- package/dist/cjs/domain/aggregate/aggregate-root.d.ts +0 -5
- package/dist/cjs/domain/aggregate/aggregate-root.js +4 -20
- package/dist/cjs/domain/entity/entity.js +2 -2
- package/dist/cjs/domain/event/event-base.d.ts +3 -9
- package/dist/cjs/domain/event/event-base.js +8 -38
- package/dist/cjs/domain/exceptions/domain-exception-code.d.ts +7 -0
- package/dist/cjs/domain/exceptions/domain-exception-code.js +11 -0
- package/dist/cjs/domain/exceptions/domain.exception.d.ts +6 -0
- package/dist/cjs/domain/exceptions/domain.exception.js +16 -0
- package/dist/cjs/domain/exceptions/invalid-event-definition.exception.d.ts +4 -0
- package/dist/cjs/domain/exceptions/invalid-event-definition.exception.js +19 -0
- package/dist/cjs/{exceptions/domain → domain/exceptions}/required-value.exception.js +5 -6
- package/dist/cjs/{exceptions/domain → domain/exceptions}/type-primitive.exception.js +5 -6
- package/dist/cjs/{exceptions/domain → domain/exceptions}/validation.exception.js +5 -5
- package/dist/cjs/domain/index.d.ts +8 -1
- package/dist/cjs/domain/index.js +14 -2
- package/dist/cjs/domain/primitive/primitive-types.d.ts +1 -3
- package/dist/cjs/domain/type/abstract-type.d.ts +6 -8
- package/dist/cjs/domain/type/abstract-type.js +15 -23
- package/dist/cjs/domain/type/array-type.d.ts +2 -1
- package/dist/cjs/domain/type/array-type.js +17 -18
- package/dist/cjs/domain/type/boolean-type.js +2 -2
- package/dist/cjs/domain/type/composite/composite-brand.d.ts +4 -0
- package/dist/cjs/domain/type/composite/composite-brand.js +15 -0
- package/dist/cjs/domain/type/composite/composite-field.d.ts +7 -8
- package/dist/cjs/domain/type/composite/composite-field.js +70 -47
- package/dist/cjs/domain/type/composite/composite-issues.d.ts +6 -0
- package/dist/cjs/domain/type/composite/composite-issues.js +35 -0
- package/dist/cjs/domain/type/composite/composite.js +15 -19
- package/dist/cjs/domain/type/date-type.d.ts +3 -2
- package/dist/cjs/domain/type/date-type.js +42 -7
- package/dist/cjs/domain/type/enum-type.js +2 -2
- package/dist/cjs/domain/type/id-type.d.ts +1 -1
- package/dist/cjs/domain/type/id-type.js +4 -4
- package/dist/cjs/domain/type/index.d.ts +2 -4
- package/dist/cjs/domain/type/index.js +3 -7
- package/dist/cjs/domain/type/json-type.d.ts +4 -0
- package/dist/cjs/domain/type/json-type.js +33 -4
- package/dist/cjs/domain/type/number-type.js +2 -2
- package/dist/cjs/domain/type/required/required-mark.d.ts +0 -1
- package/dist/cjs/domain/type/string-type.js +2 -2
- package/dist/cjs/domain/type/uuid-type.js +2 -2
- package/dist/cjs/infrastructure/event/domain-event-bus.js +9 -9
- package/dist/cjs/infrastructure/event/event-registry.js +9 -9
- package/dist/cjs/infrastructure/event/in-memory/emitter.js +2 -2
- package/dist/cjs/infrastructure/event/in-memory/in-memory-event-channel.d.ts +1 -1
- package/dist/cjs/infrastructure/event/in-memory/in-memory-event-channel.js +6 -7
- package/dist/cjs/{exceptions/infrastructure → infrastructure/exceptions}/event-delivery.exception.js +4 -4
- package/dist/cjs/infrastructure/exceptions/infrastructure-exception-code.d.ts +6 -0
- package/dist/cjs/infrastructure/exceptions/infrastructure-exception-code.js +10 -0
- package/dist/cjs/infrastructure/exceptions/infrastructure.exception.d.ts +6 -0
- package/dist/cjs/infrastructure/exceptions/infrastructure.exception.js +16 -0
- package/dist/cjs/{exceptions/infrastructure → infrastructure/exceptions}/internal-error.exception.d.ts +1 -1
- package/dist/cjs/{exceptions/infrastructure → infrastructure/exceptions}/internal-error.exception.js +4 -4
- package/dist/cjs/infrastructure/exceptions/kind/exception-classifier.d.ts +4 -0
- package/dist/cjs/infrastructure/exceptions/kind/exception-classifier.js +34 -0
- package/dist/cjs/infrastructure/exceptions/kind/exception-kind.d.ts +9 -0
- package/dist/cjs/infrastructure/exceptions/kind/exception-kind.js +13 -0
- package/dist/cjs/{exceptions/infrastructure → infrastructure/exceptions}/unknown-event.exception.js +4 -4
- package/dist/cjs/infrastructure/index.d.ts +7 -2
- package/dist/cjs/infrastructure/index.js +15 -3
- package/dist/cjs/infrastructure/logging/console-logger.js +2 -2
- package/dist/{esm → cjs/shared}/exceptions/abstract.exception.d.ts +6 -4
- package/dist/cjs/{exceptions → shared/exceptions}/abstract.exception.js +9 -16
- package/dist/cjs/shared/index.d.ts +8 -0
- package/dist/cjs/shared/index.js +13 -0
- package/dist/cjs/shared/issue-collection-error.d.ts +9 -0
- package/dist/cjs/shared/issue-collection-error.js +11 -0
- package/dist/cjs/shared/issue-collector.d.ts +8 -0
- package/dist/cjs/shared/issue-collector.js +41 -0
- package/dist/esm/application/event/listens-to.js +1 -1
- package/dist/esm/{exceptions/application → application/exceptions}/aggregate-not-found.exception.d.ts +1 -1
- package/dist/esm/application/exceptions/aggregate-not-found.exception.js +13 -0
- package/dist/esm/application/exceptions/application-exception-code.d.ts +7 -0
- package/dist/esm/application/exceptions/application-exception-code.js +8 -0
- package/dist/esm/application/exceptions/application.exception.d.ts +6 -0
- package/dist/esm/application/exceptions/application.exception.js +12 -0
- package/dist/esm/{exceptions/application → application/exceptions}/concurrency.exception.d.ts +1 -1
- package/dist/esm/{exceptions/application → application/exceptions}/concurrency.exception.js +4 -4
- package/dist/esm/{exceptions/application → application/exceptions}/not-allowed.exception.d.ts +1 -1
- package/dist/esm/application/exceptions/not-allowed.exception.js +13 -0
- package/dist/esm/application/exceptions/unexpected-error.exception.d.ts +5 -0
- package/dist/esm/application/exceptions/unexpected-error.exception.js +13 -0
- package/dist/esm/application/index.d.ts +7 -0
- package/dist/esm/application/index.js +7 -0
- package/dist/esm/application/use-case/use-case.js +3 -2
- package/dist/{cjs/domain/type/value-objects/value-objects.d.ts → esm/application/value-object-builder/value-object-builder.d.ts} +3 -3
- package/dist/esm/application/value-object-builder/value-object-builder.js +47 -0
- package/dist/esm/domain/aggregate/aggregate-root.d.ts +0 -5
- package/dist/esm/domain/aggregate/aggregate-root.js +4 -20
- package/dist/esm/domain/entity/entity.js +1 -1
- package/dist/esm/domain/event/event-base.d.ts +3 -9
- package/dist/esm/domain/event/event-base.js +8 -38
- package/dist/esm/domain/exceptions/domain-exception-code.d.ts +7 -0
- package/dist/esm/domain/exceptions/domain-exception-code.js +8 -0
- package/dist/esm/domain/exceptions/domain.exception.d.ts +6 -0
- package/dist/esm/domain/exceptions/domain.exception.js +12 -0
- package/dist/esm/domain/exceptions/invalid-event-definition.exception.d.ts +4 -0
- package/dist/esm/domain/exceptions/invalid-event-definition.exception.js +15 -0
- package/dist/esm/{exceptions/domain → domain/exceptions}/required-value.exception.js +4 -5
- package/dist/esm/{exceptions/domain → domain/exceptions}/type-primitive.exception.js +4 -5
- package/dist/esm/{exceptions/domain → domain/exceptions}/validation.exception.js +5 -5
- package/dist/esm/domain/index.d.ts +7 -0
- package/dist/esm/domain/index.js +6 -0
- package/dist/esm/domain/primitive/primitive-types.d.ts +1 -3
- package/dist/esm/domain/type/abstract-type.d.ts +6 -8
- package/dist/esm/domain/type/abstract-type.js +14 -22
- package/dist/esm/domain/type/array-type.d.ts +2 -1
- package/dist/esm/domain/type/array-type.js +16 -17
- package/dist/esm/domain/type/boolean-type.js +1 -1
- package/dist/esm/domain/type/composite/composite-brand.d.ts +4 -0
- package/dist/esm/domain/type/composite/composite-brand.js +11 -0
- package/dist/esm/domain/type/composite/composite-field.d.ts +7 -8
- package/dist/esm/domain/type/composite/composite-field.js +69 -46
- package/dist/esm/domain/type/composite/composite-issues.d.ts +6 -0
- package/dist/esm/domain/type/composite/composite-issues.js +31 -0
- package/dist/esm/domain/type/composite/composite.js +13 -17
- package/dist/esm/domain/type/date-type.d.ts +3 -2
- package/dist/esm/domain/type/date-type.js +39 -4
- package/dist/esm/domain/type/enum-type.js +1 -1
- package/dist/esm/domain/type/id-type.d.ts +1 -1
- package/dist/esm/domain/type/id-type.js +2 -2
- package/dist/esm/domain/type/index.d.ts +2 -4
- package/dist/esm/domain/type/index.js +1 -3
- package/dist/esm/domain/type/json-type.d.ts +4 -0
- package/dist/esm/domain/type/json-type.js +32 -3
- package/dist/esm/domain/type/number-type.js +1 -1
- package/dist/esm/domain/type/required/required-mark.d.ts +0 -1
- package/dist/esm/domain/type/string-type.js +1 -1
- package/dist/esm/domain/type/uuid-type.js +1 -1
- package/dist/esm/infrastructure/event/domain-event-bus.js +5 -5
- package/dist/esm/infrastructure/event/event-registry.js +4 -4
- package/dist/esm/infrastructure/event/in-memory/emitter.js +1 -1
- package/dist/esm/infrastructure/event/in-memory/in-memory-event-channel.d.ts +1 -1
- package/dist/esm/infrastructure/event/in-memory/in-memory-event-channel.js +5 -6
- package/dist/esm/{exceptions/infrastructure → infrastructure/exceptions}/event-delivery.exception.js +4 -4
- package/dist/esm/infrastructure/exceptions/infrastructure-exception-code.d.ts +6 -0
- package/dist/esm/infrastructure/exceptions/infrastructure-exception-code.js +7 -0
- package/dist/esm/infrastructure/exceptions/infrastructure.exception.d.ts +6 -0
- package/dist/esm/infrastructure/exceptions/infrastructure.exception.js +12 -0
- package/dist/esm/{exceptions/infrastructure → infrastructure/exceptions}/internal-error.exception.d.ts +1 -1
- package/dist/esm/infrastructure/exceptions/internal-error.exception.js +13 -0
- package/dist/esm/infrastructure/exceptions/kind/exception-classifier.d.ts +4 -0
- package/dist/esm/infrastructure/exceptions/kind/exception-classifier.js +30 -0
- package/dist/esm/infrastructure/exceptions/kind/exception-kind.d.ts +9 -0
- package/dist/esm/infrastructure/exceptions/kind/exception-kind.js +10 -0
- package/dist/esm/{exceptions/infrastructure → infrastructure/exceptions}/unknown-event.exception.js +4 -4
- package/dist/esm/infrastructure/index.d.ts +7 -2
- package/dist/esm/infrastructure/index.js +7 -1
- package/dist/esm/infrastructure/logging/console-logger.js +1 -1
- package/dist/{cjs → esm/shared}/exceptions/abstract.exception.d.ts +6 -4
- package/dist/esm/{exceptions → shared/exceptions}/abstract.exception.js +9 -16
- package/dist/esm/shared/index.d.ts +8 -0
- package/dist/esm/shared/index.js +5 -0
- package/dist/esm/shared/issue-collection-error.d.ts +9 -0
- package/dist/esm/shared/issue-collection-error.js +7 -0
- package/dist/esm/shared/issue-collector.d.ts +8 -0
- package/dist/esm/shared/issue-collector.js +37 -0
- package/package.json +7 -18
- package/dist/cjs/domain/type/equality/value-equality.d.ts +0 -7
- package/dist/cjs/domain/type/equality/value-equality.js +0 -41
- package/dist/cjs/domain/type/immutable/immutable-date.d.ts +0 -4
- package/dist/cjs/domain/type/immutable/immutable-date.js +0 -37
- package/dist/cjs/domain/type/immutable/immutable.d.ts +0 -4
- package/dist/cjs/domain/type/immutable/immutable.js +0 -19
- package/dist/cjs/domain/type/value-objects/value-objects.js +0 -70
- package/dist/cjs/exceptions/application/application.exception.d.ts +0 -6
- package/dist/cjs/exceptions/application/application.exception.js +0 -17
- package/dist/cjs/exceptions/domain/domain.exception.d.ts +0 -6
- package/dist/cjs/exceptions/domain/domain.exception.js +0 -17
- package/dist/cjs/exceptions/exception-code.d.ts +0 -15
- package/dist/cjs/exceptions/exception-code.js +0 -19
- package/dist/cjs/exceptions/index.d.ts +0 -14
- package/dist/cjs/exceptions/index.js +0 -30
- package/dist/cjs/exceptions/infrastructure/infrastructure.exception.d.ts +0 -6
- package/dist/cjs/exceptions/infrastructure/infrastructure.exception.js +0 -17
- package/dist/cjs/infrastructure/http/http-error-mapper.d.ts +0 -23
- package/dist/cjs/infrastructure/http/http-error-mapper.js +0 -63
- package/dist/esm/domain/type/equality/value-equality.d.ts +0 -7
- package/dist/esm/domain/type/equality/value-equality.js +0 -37
- package/dist/esm/domain/type/immutable/immutable-date.d.ts +0 -4
- package/dist/esm/domain/type/immutable/immutable-date.js +0 -33
- package/dist/esm/domain/type/immutable/immutable.d.ts +0 -4
- package/dist/esm/domain/type/immutable/immutable.js +0 -15
- package/dist/esm/domain/type/value-objects/value-objects.js +0 -66
- package/dist/esm/exceptions/application/aggregate-not-found.exception.js +0 -13
- package/dist/esm/exceptions/application/application.exception.d.ts +0 -6
- package/dist/esm/exceptions/application/application.exception.js +0 -13
- package/dist/esm/exceptions/application/not-allowed.exception.js +0 -13
- package/dist/esm/exceptions/domain/domain.exception.d.ts +0 -6
- package/dist/esm/exceptions/domain/domain.exception.js +0 -13
- package/dist/esm/exceptions/exception-code.d.ts +0 -15
- package/dist/esm/exceptions/exception-code.js +0 -16
- package/dist/esm/exceptions/index.d.ts +0 -14
- package/dist/esm/exceptions/index.js +0 -14
- package/dist/esm/exceptions/infrastructure/infrastructure.exception.d.ts +0 -6
- package/dist/esm/exceptions/infrastructure/infrastructure.exception.js +0 -13
- package/dist/esm/exceptions/infrastructure/internal-error.exception.js +0 -13
- package/dist/esm/infrastructure/http/http-error-mapper.d.ts +0 -23
- package/dist/esm/infrastructure/http/http-error-mapper.js +0 -59
- /package/dist/cjs/{exceptions/domain → domain/exceptions}/required-value.exception.d.ts +0 -0
- /package/dist/cjs/{exceptions/domain → domain/exceptions}/type-primitive.exception.d.ts +0 -0
- /package/dist/cjs/{exceptions/domain → domain/exceptions}/validation.exception.d.ts +0 -0
- /package/dist/cjs/domain/type/required/{required.d.ts → required-type.d.ts} +0 -0
- /package/dist/cjs/domain/type/required/{required.js → required-type.js} +0 -0
- /package/dist/cjs/{exceptions/infrastructure → infrastructure/exceptions}/event-delivery.exception.d.ts +0 -0
- /package/dist/cjs/{exceptions/infrastructure → infrastructure/exceptions}/unknown-event.exception.d.ts +0 -0
- /package/dist/cjs/{exceptions → shared/exceptions}/brand/exception-brand.d.ts +0 -0
- /package/dist/cjs/{exceptions → shared/exceptions}/brand/exception-brand.js +0 -0
- /package/dist/cjs/{exceptions → shared/exceptions}/received/received-value.d.ts +0 -0
- /package/dist/cjs/{exceptions → shared/exceptions}/received/received-value.js +0 -0
- /package/dist/esm/{exceptions/domain → domain/exceptions}/required-value.exception.d.ts +0 -0
- /package/dist/esm/{exceptions/domain → domain/exceptions}/type-primitive.exception.d.ts +0 -0
- /package/dist/esm/{exceptions/domain → domain/exceptions}/validation.exception.d.ts +0 -0
- /package/dist/esm/domain/type/required/{required.d.ts → required-type.d.ts} +0 -0
- /package/dist/esm/domain/type/required/{required.js → required-type.js} +0 -0
- /package/dist/esm/{exceptions/infrastructure → infrastructure/exceptions}/event-delivery.exception.d.ts +0 -0
- /package/dist/esm/{exceptions/infrastructure → infrastructure/exceptions}/unknown-event.exception.d.ts +0 -0
- /package/dist/esm/{exceptions → shared/exceptions}/brand/exception-brand.d.ts +0 -0
- /package/dist/esm/{exceptions → shared/exceptions}/brand/exception-brand.js +0 -0
- /package/dist/esm/{exceptions → shared/exceptions}/received/received-value.d.ts +0 -0
- /package/dist/esm/{exceptions → shared/exceptions}/received/received-value.js +0 -0
|
@@ -17,7 +17,9 @@ Part of the `archi-code-ddd` skill: read it only when that skill routes here.
|
|
|
17
17
|
broker), so it must be a stable literal, unique per listener.
|
|
18
18
|
- Subscribing listeners by a text name or calling them from a use case: the composition root
|
|
19
19
|
subscribes them with `bus.subscribe(listener)`, which reads their `@ListensTo`.
|
|
20
|
-
- Declaring `eventName
|
|
20
|
+
- Declaring `eventName` as a method or an instance field in an event, or adding methods to it:
|
|
21
|
+
declare `static readonly eventName`; the base copies it into the event's `eventName` field, and
|
|
22
|
+
an event is plain data.
|
|
21
23
|
- Importing the repository contracts from `@archi-code/ddd/application`: they live in `/domain`.
|
|
22
24
|
- Do not invent API: there is no root `@archi-code/ddd` import, no `toPrimitives()` in the base
|
|
23
25
|
classes, no generic `ValueObject` (a value object of several fields is `Composite({...})`), no
|
|
@@ -38,7 +40,7 @@ Part of the `archi-code-ddd` skill: read it only when that skill routes here.
|
|
|
38
40
|
(`new OrderPlaced(id.value, customer.value)`): `occurredOn` becomes the system time and tests stop
|
|
39
41
|
being deterministic.
|
|
40
42
|
- A registry factory that does not pass the JSON as metadata
|
|
41
|
-
(`(json) => new OrderShipped(json.
|
|
43
|
+
(`(json) => new OrderShipped(json.orderId as string)`): the event gets a new `eventId`, a listener
|
|
42
44
|
cannot skip a redelivery, and `fromJSON` throws.
|
|
43
45
|
- Subscribing a listener of an event the `EventRegistry` does not know: `bus.subscribe` refuses it
|
|
44
46
|
with `UnknownEventException`; register every event a listener receives.
|
|
@@ -54,8 +56,10 @@ Part of the `archi-code-ddd` skill: read it only when that skill routes here.
|
|
|
54
56
|
- A read query that loads aggregates through `AggregateFinder`, or a repository method that
|
|
55
57
|
returns DTOs: reads go straight to the database through an application interface.
|
|
56
58
|
- Importing `@archi-code/ddd/infrastructure` from domain code, or `application` from the domain.
|
|
57
|
-
- Importing an exception from
|
|
58
|
-
|
|
59
|
+
- Importing an exception from another layer's subpath: each layer exports its own
|
|
60
|
+
(`ValidationException` from `/domain`, `AggregateNotFoundException` from `/application`,
|
|
61
|
+
`InternalErrorException` from `/infrastructure`); there is no `/exceptions` subpath and the
|
|
62
|
+
common base class is not exported (check the three layer bases, or use `ExceptionClassifier`).
|
|
59
63
|
- Catching errors inside `handle` just to rethrow them as `ApplicationException`: let them
|
|
60
64
|
propagate; `execute` keeps package exceptions and wraps the rest.
|
|
61
65
|
- Calling `bus.publish` before `repository.save`, or publishing without `pullDomainEvents()`.
|
|
@@ -68,10 +72,10 @@ Part of the `archi-code-ddd` skill: read it only when that skill routes here.
|
|
|
68
72
|
them with the aggregate in the same atomic write (an outbox) and a relay delivers them.
|
|
69
73
|
- A listener that breaks when a channel delivers the same event twice: delivery is at least once;
|
|
70
74
|
skip an `eventId` already processed.
|
|
71
|
-
- Sending
|
|
75
|
+
- Sending a domain event as it is (`{ ...event }`) to another context: translate it to a published,
|
|
72
76
|
versioned contract.
|
|
73
|
-
- Changing the data of a stored or published event
|
|
74
|
-
contract version).
|
|
77
|
+
- Changing the data of a stored or published event incompatibly under the same `eventName` (or
|
|
78
|
+
the same contract version) when no factory can read both shapes: publish a new event instead.
|
|
75
79
|
- Repository methods with business names or rules (`pendingOlderThan`, `findOverdueInvoices`),
|
|
76
80
|
`nextId()` in a repository, or a use case returning the aggregate to the controller.
|
|
77
81
|
- Throwing `AggregateNotFoundException` from the domain or a repository: the use case throws it when
|
|
@@ -102,7 +106,7 @@ Part of the `archi-code-ddd` skill: read it only when that skill routes here.
|
|
|
102
106
|
- Comparing VOs or entities with `===`: use `equals()`.
|
|
103
107
|
- Calling `assertValid()`: it does not exist; the constructor already throws `ValidationException`.
|
|
104
108
|
- Building the value objects of an input one after another with `new` in `toCommand`: the first
|
|
105
|
-
invalid field hides the rest. Use `
|
|
109
|
+
invalid field hides the rest. Use `ValueObjectBuilder.build` so every field is reported.
|
|
106
110
|
- Moving a value object's rule to the request schema or the controller (`isEmail` in a DTO
|
|
107
111
|
validator instead of the `Email` value object): the adapter may check format for the user
|
|
108
112
|
(empty, length) [IDDD ch. 4], but the value object owns the rule and always checks it.
|
|
@@ -156,17 +160,20 @@ Part of the `archi-code-ddd` skill: read it only when that skill routes here.
|
|
|
156
160
|
- Converting an aggregate to or from primitives outside the aggregate (a domain service, a
|
|
157
161
|
hand-written mapper reading its fields): the aggregate owns `toPrimitives()` and
|
|
158
162
|
`static fromPrimitives()`.
|
|
159
|
-
- Declaring a `version` field in an aggregate, or putting it in `toPrimitives()`: the
|
|
160
|
-
|
|
163
|
+
- Declaring a `version` field in an aggregate, or putting it in `toPrimitives()`: the version is
|
|
164
|
+
the repository's, which keeps it in its own `WeakMap` and stores it next to the primitives.
|
|
161
165
|
- A repository (also the in-memory one of the tests) that saves with `upsert` and no version
|
|
162
166
|
condition, or "last save wins": a stale save must throw `ConcurrencyException`.
|
|
163
|
-
-
|
|
164
|
-
false conflict), or
|
|
165
|
-
|
|
166
|
-
-
|
|
167
|
-
|
|
168
|
-
- Creating events without the aggregate
|
|
169
|
-
yourself for a new event: the base does it.
|
|
167
|
+
- A repository that forgets to remember `version + 1` after a successful write (the next save of
|
|
168
|
+
the same object becomes a false conflict), or the stored version on load (every load looks new,
|
|
169
|
+
and the first save conflicts).
|
|
170
|
+
- Exposing the version to domain or application code: use cases load, change and save; only the
|
|
171
|
+
repository knows it.
|
|
172
|
+
- Creating events without the id of their aggregate as a payload field (`readonly orderId: string`),
|
|
173
|
+
or generating `eventId`/`occurredOn` yourself for a new event: the base does it.
|
|
174
|
+
- Putting value objects, `Date`s, `undefined` or class instances in an event's fields (nothing
|
|
175
|
+
checks it at run time): each event declares only the primitive data it needs (`id.value`,
|
|
176
|
+
money as `{ amount, currency }`, `date.toISOString()`).
|
|
170
177
|
- Wrapping or translating an error (`InternalErrorException`, `ConcurrencyException`,
|
|
171
178
|
`NotAllowedException`, `AggregateNotFoundException`) without `{ cause: error }`.
|
|
172
179
|
- `JsonType` with `{}` or with an array: throws `TypePrimitiveException` (it must be a non-empty
|
|
@@ -183,7 +190,7 @@ Part of the `archi-code-ddd` skill: read it only when that skill routes here.
|
|
|
183
190
|
- Logging from the domain, or importing a logging library (pino, winston, `console`) in a use case:
|
|
184
191
|
inject the `Logger` port and choose the provider in the composition root.
|
|
185
192
|
- Logging an error and rethrowing it in every layer: the edge that handles it logs it once
|
|
186
|
-
(`
|
|
193
|
+
(`DomainEventBus` with a logger already does; an entry point logs an `Internal` failure).
|
|
187
194
|
- Interpolating data into the message (`` `order ${id} failed` ``) or logging passwords, tokens or
|
|
188
195
|
personal data that the entry does not need: keep a stable message and put only what is needed in
|
|
189
196
|
the context.
|
|
@@ -4,27 +4,27 @@ Part of the `archi-code-ddd` skill: read it only when that skill routes here.
|
|
|
4
4
|
|
|
5
5
|
| Class | Layer | May depend on | Never |
|
|
6
6
|
| ------------------------------------------------------------ | --------------------------------------- | ----------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
|
|
7
|
-
| Value object, composite | domain | other value objects, `@archi-code/validation`, exceptions
|
|
7
|
+
| Value object, composite | domain | other value objects, `@archi-code/validation`, domain exceptions | mutate, know about storage or transport |
|
|
8
8
|
| Aggregate root (`extends AggregateRoot`) | domain | value objects, child entities, events; a domain service or loaded data as method argument | read a repository, publish, `new Date()`, services received by constructor |
|
|
9
9
|
| Child entity (`extends Entity`) | domain | its local id, value objects, business methods the root calls | setters, being called from outside the root, loaded or saved on its own |
|
|
10
10
|
| Domain event (`OrderPlaced`) | domain | primitives taken from value objects | behavior, value objects or the whole state in its payload |
|
|
11
11
|
| Repository contract (`AggregateFinder`…) | domain | the aggregate | business logic or queries, an all-in-one `Repository` |
|
|
12
12
|
| Domain service | domain | aggregates, value objects, specifications, repository contracts (to read), domain ports | I/O code, transactions, publishing |
|
|
13
13
|
| Domain port to another context or service (`ProductPricing`) | domain | value objects | remote DTOs, I/O code |
|
|
14
|
-
| Use case (`PlaceOrder`) | application | domain, ports
|
|
14
|
+
| Use case (`PlaceOrder`) | application | domain, ports and exceptions | import another use case or a listener, return an aggregate |
|
|
15
15
|
| Listener (`ReserveStockOnOrderPlaced`) | application | one use case (by constructor), the event | business rules, saving aggregates, calling several use cases |
|
|
16
16
|
| Port (`EventBus`, `Clock`, `Logger`, your own) | application | domain types | code |
|
|
17
17
|
| Read query (`OrderSummaries`, `StaleOrders`) | application (interface), infrastructure | DTOs and ids, `@archi-code/criteria` | loading aggregates, repositories, business rules |
|
|
18
18
|
| Adapter (repository, event channel, ACL translator, logger provider) | infrastructure | domain, application, exceptions, drivers | business rules |
|
|
19
19
|
| Event wiring (`DomainEventBus`, `EventRegistry`, `EventChannel` port and its implementations) | infrastructure (composition root) | `@archi-code/ddd/infrastructure`, domain events, listeners | being seen by use cases (they only see `EventBus`) or listeners |
|
|
20
|
-
| Entry point (controller, consumer, scheduler, CLI) | interfaces | application,
|
|
20
|
+
| Entry point (controller, consumer, scheduler, CLI) | interfaces | application, `ExceptionClassifier` and `ExceptionKind`; domain types only if unavoidable | business rules, repositories or other adapters |
|
|
21
21
|
| Composition root (`composition.ts`) | per context | everything | being imported by any layer |
|
|
22
22
|
|
|
23
|
-
- Dependencies only point inwards: the domain imports only `@archi-code/ddd/domain
|
|
24
|
-
`@archi-code/
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
- Dependencies only point inwards: the domain imports only `@archi-code/ddd/domain` and
|
|
24
|
+
`@archi-code/validation` (not "nothing external" as the suite says: these are the package
|
|
25
|
+
convention); the application adds `@archi-code/ddd/application`; only infrastructure and the
|
|
26
|
+
composition root import `@archi-code/ddd/infrastructure`, except that an entry point imports
|
|
27
|
+
`ExceptionClassifier` and `ExceptionKind` from it to translate errors (they hold no adapter).
|
|
28
28
|
Entry points (`interfaces/`: controllers, message consumers, schedulers, CLI) call use cases
|
|
29
29
|
only. The composition root of each context is the only place that builds and connects everything
|
|
30
30
|
(repositories, event channel, `DomainEventBus`, `EventRegistry`, listeners) and reads
|
|
@@ -53,8 +53,9 @@ Part of the `archi-code-ddd` skill: read it only when that skill routes here.
|
|
|
53
53
|
- Check the direction with dependency-cruiser (`tsPreCompilationDeps: true`) and the suite's
|
|
54
54
|
`ddd-skills/ddd-arquitectura/assets/dependency-cruiser.cjs`, which already allows the `@archi-code/ddd`
|
|
55
55
|
layers above and forbids importing `composition.ts`.
|
|
56
|
-
- Import from the layer, never from the package root (there is none):
|
|
57
|
-
|
|
56
|
+
- Import from the layer, never from the package root (there is none): each exception comes from
|
|
57
|
+
the subpath of its layer (`DomainException` from `/domain`, `AggregateNotFoundException` from
|
|
58
|
+
`/application`, `InternalErrorException` from `/infrastructure`); there is no `/exceptions`.
|
|
58
59
|
- Logic two use cases share never goes in a use case: a business rule becomes a method of the
|
|
59
60
|
aggregate or a domain service; a technical step becomes a port. When an action must follow
|
|
60
61
|
another, the first records an event and a listener runs the second.
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
[Fuera de los libros: convención de @archi-code/ddd]
|
|
10
10
|
|
|
11
|
-
* La raíz no se escribe: extiende `AggregateRoot<Id, Event>` y llama a `super(id)`;
|
|
11
|
+
* La raíz no se escribe: extiende `AggregateRoot<Id, Event>` y llama a `super(id)`; el agregado no declara ni mapea ninguna `version`: la de la concurrencia optimista es solo del repositorio, que recuerda la de cada agregado cargado o guardado en un `WeakMap<Order, number>` y la actualiza solo tras escribir con éxito; el caso de uso nunca la ve.
|
|
12
12
|
* Todo el estado son value objects, composites o entidades hijas con getter público; constructor privado, una factoría de negocio y `fromPrimitives`, y `toPrimitives()` con datos planos (las seis reglas de `layers.md`).
|
|
13
13
|
* Las identidades nuevas salen del value object (`OrderId.generate()`), no del repositorio.
|
|
14
14
|
* Los métodos del dominio reciben `clock: { now(): Date }` y lo pasan a los eventos (`{ clock }`).
|
|
@@ -68,7 +68,7 @@ Guía completa en `ddd-skills/ddd-agregados/references/implementacion-ts.md`. Lo
|
|
|
68
68
|
* Partes internas como **value objects** siempre que se puedan reemplazar enteras [IDDD cap. 10].
|
|
69
69
|
* Comportamiento con nombres del lenguaje; sin setters públicos. Ley de Demeter y "Tell, Don't Ask": los clientes piden a la raíz que haga algo, no navegan por dentro [IDDD cap. 10].
|
|
70
70
|
* **No inyectes repositorios ni servicios en el agregado.** Busca antes lo que necesite (en el servicio de aplicación) y pásaselo al método [IDDD cap. 10].
|
|
71
|
-
* **Concurrencia optimista** con versión en la raíz [IDDD cap. 10]: la `
|
|
71
|
+
* **Concurrencia optimista** con versión en la raíz [IDDD cap. 10]: con el paquete la guarda el repositorio (un `WeakMap` con la versión de cada agregado que cargó o guardó, almacenada junto a los primitivos, nunca dentro) y la comprueba al guardar y al borrar (`ConcurrencyException`) `[Fuera de los libros: convención de @archi-code/ddd]`.
|
|
72
72
|
* El agregado **registra** sus eventos (`record`); el caso de uso los publica tras guardar con `events.publish(order.pullDomainEvents())` (ver `ddd-skills/ddd-eventos-dominio/GUIA.md`).
|
|
73
73
|
* **Distingue comandos de hechos.** Un comando de usuario puede rechazarse con un error de negocio. Un método que registra un hecho ya ocurrido en otro agregado o contexto es idempotente y no lanza: si el hecho no encaja, registra un evento de negocio para que otra política compense [Destilado cap. 6]. Una excepción en un consumidor de mensajes provoca reintentos sin fin.
|
|
74
74
|
* Creación con **fábricas**: método en la raíz de otro agregado cuando el nuevo nace de él (`customer.placeOrder(...)`), o servicio cuando la creación necesita traducir de otro contexto [IDDD cap. 11; PHP: Factories]. Ver `ddd-skills/ddd-agregados/references/fabricas.md`.
|
|
@@ -32,12 +32,12 @@ export class StockReserved extends EventBase {
|
|
|
32
32
|
static readonly eventName = 'warehouse.stock.StockReserved';
|
|
33
33
|
|
|
34
34
|
constructor(
|
|
35
|
-
stockItemId: string,
|
|
35
|
+
readonly stockItemId: string,
|
|
36
36
|
readonly orderId: string,
|
|
37
37
|
readonly reservationId: string,
|
|
38
38
|
metadata?: EventMetadata,
|
|
39
39
|
) {
|
|
40
|
-
super(
|
|
40
|
+
super(metadata);
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
```
|
|
@@ -45,9 +45,8 @@ export class StockReserved extends EventBase {
|
|
|
45
45
|
El caso de uso registra el hecho: un agregado, un `save` atómico, y si el pedido no existe (es de otro canal) vuelve sin error:
|
|
46
46
|
|
|
47
47
|
```ts
|
|
48
|
-
import { ValueObjects } from '@archi-code/ddd/domain';
|
|
49
48
|
import type { AggregateFinder, AggregateSaver } from '@archi-code/ddd/domain';
|
|
50
|
-
import { UseCase } from '@archi-code/ddd/application';
|
|
49
|
+
import { UseCase, ValueObjectBuilder } from '@archi-code/ddd/application';
|
|
51
50
|
import type { Clock, EventBus } from '@archi-code/ddd/application';
|
|
52
51
|
|
|
53
52
|
interface StockReservationInput {
|
|
@@ -74,7 +73,7 @@ export class RegisterStockReservation extends UseCase<
|
|
|
74
73
|
}
|
|
75
74
|
|
|
76
75
|
protected toCommand(input: StockReservationInput): StockReservationCommand {
|
|
77
|
-
return
|
|
76
|
+
return ValueObjectBuilder.build({
|
|
78
77
|
orderId: () => new OrderId(input.orderId),
|
|
79
78
|
reservationId: () => new ReservationId(input.reservationId),
|
|
80
79
|
});
|
|
@@ -127,7 +126,7 @@ const orders = new InMemoryOrderRepository();
|
|
|
127
126
|
const registry = new EventRegistry<OrderEvent | StockReserved>().register(
|
|
128
127
|
StockReserved,
|
|
129
128
|
(json) =>
|
|
130
|
-
new StockReserved(json.
|
|
129
|
+
new StockReserved(json.stockItemId as string, json.orderId as string, json.reservationId as string, json),
|
|
131
130
|
);
|
|
132
131
|
const channel = new InMemoryEventChannel();
|
|
133
132
|
const bus = new DomainEventBus<OrderEvent | StockReserved>(channel, registry);
|
|
@@ -15,9 +15,8 @@ No contradice la regla de una transacción por agregado: el creador no se modifi
|
|
|
15
15
|
`Order`, `OrderId`, `OrderLines` y `ShippingAddress` son los de `references/implementacion-ts.md`:
|
|
16
16
|
|
|
17
17
|
```ts
|
|
18
|
-
import { AggregateRoot, BooleanType, Required, StringType } from '@archi-code/ddd/domain';
|
|
18
|
+
import { AggregateRoot, BooleanType, DomainException, Required, StringType } from '@archi-code/ddd/domain';
|
|
19
19
|
import type { PrimitiveTypes } from '@archi-code/ddd/domain';
|
|
20
|
-
import { DomainException } from '@archi-code/ddd/exceptions';
|
|
21
20
|
import { Validations } from '@archi-code/validation';
|
|
22
21
|
|
|
23
22
|
export class CustomerName extends Required(StringType) {}
|
|
@@ -86,11 +85,9 @@ export class Customer extends AggregateRoot<CustomerId> {
|
|
|
86
85
|
La regla de creación ("un cliente bloqueado no puede realizar pedidos") vive en `Customer`. El caso de uso lee el cliente con `AggregateFinder<Customer>`, genera la identidad nueva con `OrderId.generate()` y guarda solo el pedido con `AggregateSaver<Order>`; `Customer` no se guarda porque no ha cambiado:
|
|
87
86
|
|
|
88
87
|
```ts
|
|
89
|
-
import { ValueObjects } from '@archi-code/ddd/domain';
|
|
90
88
|
import type { AggregateFinder, AggregateSaver } from '@archi-code/ddd/domain';
|
|
91
|
-
import { UseCase } from '@archi-code/ddd/application';
|
|
89
|
+
import { AggregateNotFoundException, UseCase, ValueObjectBuilder } from '@archi-code/ddd/application';
|
|
92
90
|
import type { Clock, EventBus } from '@archi-code/ddd/application';
|
|
93
|
-
import { AggregateNotFoundException } from '@archi-code/ddd/exceptions';
|
|
94
91
|
|
|
95
92
|
interface PlaceOrderInput {
|
|
96
93
|
customerId: string;
|
|
@@ -115,7 +112,7 @@ export class PlaceOrder extends UseCase<PlaceOrderInput, PlaceOrderCommand, stri
|
|
|
115
112
|
}
|
|
116
113
|
|
|
117
114
|
protected toCommand(input: PlaceOrderInput): PlaceOrderCommand {
|
|
118
|
-
return
|
|
115
|
+
return ValueObjectBuilder.build({
|
|
119
116
|
customerId: () => new CustomerId(input.customerId),
|
|
120
117
|
lines: () => OrderLines.fromPrimitives({ items: input.lines }),
|
|
121
118
|
shippingAddress: () => ShippingAddress.fromPrimitives(input.shippingAddress),
|
|
@@ -173,7 +170,7 @@ describe('Customer.placeOrder', () => {
|
|
|
173
170
|
);
|
|
174
171
|
const order = customer.placeOrder(OrderId.generate(), lines, address, clock);
|
|
175
172
|
expect(order.customerId.equals(customer.id)).toBe(true);
|
|
176
|
-
expect(order.pullDomainEvents().map((event) => event.eventName
|
|
173
|
+
expect(order.pullDomainEvents().map((event) => event.eventName)).toEqual(['OrderPlaced']);
|
|
177
174
|
});
|
|
178
175
|
|
|
179
176
|
it('refuses an order from a blocked customer', () => {
|
|
@@ -192,5 +189,5 @@ describe('Customer.placeOrder', () => {
|
|
|
192
189
|
|
|
193
190
|
## Cuándo no
|
|
194
191
|
|
|
195
|
-
* Para reconstituir desde base de datos: eso es `static fromPrimitives(primitives)`, que el repositorio llama directamente (`Order.fromPrimitives(row
|
|
192
|
+
* Para reconstituir desde base de datos: eso es `static fromPrimitives(primitives)`, que el repositorio llama directamente (`Order.fromPrimitives(row.primitives)`; la versión de la fila la recuerda el repositorio en su `WeakMap`, no el agregado), sin reglas de creación ni eventos.
|
|
196
193
|
* "Fábricas" genéricas que reciben un DTO y rellenan campos: un constructor con setters disfrazado.
|
|
@@ -4,7 +4,7 @@ Contenido: 1. Raíz base · 2. Construcción paso a paso · 3. Agregado completo
|
|
|
4
4
|
|
|
5
5
|
## 1. Raíz base
|
|
6
6
|
|
|
7
|
-
La raíz no se escribe a mano: se extiende `AggregateRoot<Id, Event>` de `@archi-code/ddd/domain`, que ya trae lo que IDDD cap. 10 describe para la raíz (identidad, igualdad por identidad
|
|
7
|
+
La raíz no se escribe a mano: se extiende `AggregateRoot<Id, Event>` de `@archi-code/ddd/domain`, que ya trae lo que IDDD cap. 10 describe para la raíz salvo la versión (identidad, igualdad por identidad y lista de eventos registrados); la versión para la concurrencia optimista la guarda el repositorio, no el agregado (sección 5) `[Fuera de los libros: convención de @archi-code/ddd]`.
|
|
8
8
|
|
|
9
9
|
```ts
|
|
10
10
|
import { AggregateRoot } from '@archi-code/ddd/domain';
|
|
@@ -24,10 +24,7 @@ La clase completa está en la sección 3. Lo que da la base:
|
|
|
24
24
|
* `readonly id` y `equals(other)` (misma clase y mismo id) de `Entity<Id>`; un id sin valor lanza `RequiredValueException`.
|
|
25
25
|
* `protected record(event)`: guarda el evento congelado (los hechos no cambian [IDDD cap. 8]).
|
|
26
26
|
* `pullDomainEvents()`: devuelve los eventos en orden (tipados como `Event[]`) y vacía la lista; lo llama el caso de uso tras guardar.
|
|
27
|
-
|
|
28
|
-
* `restoreVersion(v)` y `markPersisted()`: solo para repositorios (al cargar y tras escribir).
|
|
29
|
-
|
|
30
|
-
Sin `markPersisted`, el objeto en memoria conservaría la versión antigua tras guardar y un segundo `save` del mismo objeto (por ejemplo, dos comandos seguidos en el mismo proceso) daría un falso conflicto de concurrencia `[Fuera de los libros]`. Los repositorios que sigan el contrato lo llaman siempre, también el `InMemoryOrderRepository` que el proyecto escribe en `test/support/` para los tests.
|
|
27
|
+
La base no trae versión: el agregado no la declara, no la mapea en `toPrimitives()` y el caso de uso nunca la ve. Cada repositorio recuerda la versión de cada agregado que cargó o guardó en un `WeakMap<Order, number>` propio y la actualiza solo cuando la escritura ha ido bien; así un segundo `save` del mismo objeto (por ejemplo, dos comandos seguidos en el mismo proceso) no da un falso conflicto de concurrencia `[Fuera de los libros: convención de @archi-code/ddd]`. Lo cumplen todos los repositorios, también el `InMemoryOrderRepository` que el proyecto escribe en `test/support/` para los tests.
|
|
31
28
|
|
|
32
29
|
Por qué registrar en vez de publicar: IDDD publica desde el agregado con un publicador estático [IDDD cap. 8]. Registrar y dejar que la aplicación publique no requiere un singleton global en el dominio y, con una bandeja de salida (outbox), evita entregar eventos de cambios que no llegaron a guardarse. *DDD in PHP* discute ambas variantes [PHP: Domain Events]. Con el paquete, el caso de uso llama a `await this.orders.save(order)` y después a `events.publish(order.pullDomainEvents())`; la aplicación no abre transacciones, porque la transacción es del repositorio. Si `publish` falla tras `save`, el evento puede perderse; cuando eso no es admisible, el repositorio guarda el agregado y sus eventos pendientes en la misma escritura atómica (outbox) y un proceso aparte los entrega por un `EventChannel`, y el caso de uso no cambia `[Fuera de los libros: convención de @archi-code/ddd]`.
|
|
33
30
|
|
|
@@ -55,7 +52,7 @@ import {
|
|
|
55
52
|
Required,
|
|
56
53
|
StringType,
|
|
57
54
|
} from '@archi-code/ddd/domain';
|
|
58
|
-
import { DomainException } from '@archi-code/ddd/
|
|
55
|
+
import { DomainException } from '@archi-code/ddd/domain';
|
|
59
56
|
import { Validations } from '@archi-code/validation';
|
|
60
57
|
|
|
61
58
|
export class OrderId extends IdType<'OrderId'> {}
|
|
@@ -191,9 +188,8 @@ export class OrderStatus extends Required(StatusType) {
|
|
|
191
188
|
Eventos (hechos en pasado, con primitivos tomados de los value objects) y errores de negocio:
|
|
192
189
|
|
|
193
190
|
```ts
|
|
194
|
-
import { EventBase } from '@archi-code/ddd/domain';
|
|
191
|
+
import { DomainException, EventBase } from '@archi-code/ddd/domain';
|
|
195
192
|
import type { EventMetadata } from '@archi-code/ddd/domain';
|
|
196
|
-
import { DomainException } from '@archi-code/ddd/exceptions';
|
|
197
193
|
|
|
198
194
|
export interface MoneyData {
|
|
199
195
|
readonly amount: number;
|
|
@@ -210,13 +206,13 @@ export class OrderPlaced extends EventBase {
|
|
|
210
206
|
static readonly eventName = 'OrderPlaced';
|
|
211
207
|
|
|
212
208
|
constructor(
|
|
213
|
-
orderId: string,
|
|
209
|
+
readonly orderId: string,
|
|
214
210
|
readonly customerId: string,
|
|
215
211
|
readonly lines: readonly OrderLineData[],
|
|
216
212
|
readonly total: MoneyData,
|
|
217
213
|
metadata?: EventMetadata,
|
|
218
214
|
) {
|
|
219
|
-
super(
|
|
215
|
+
super(metadata);
|
|
220
216
|
}
|
|
221
217
|
}
|
|
222
218
|
|
|
@@ -224,14 +220,14 @@ export class OrderLineAdded extends EventBase {
|
|
|
224
220
|
static readonly eventName = 'OrderLineAdded';
|
|
225
221
|
|
|
226
222
|
constructor(
|
|
227
|
-
orderId: string,
|
|
223
|
+
readonly orderId: string,
|
|
228
224
|
readonly sku: string,
|
|
229
225
|
readonly quantity: number,
|
|
230
226
|
readonly unitPrice: MoneyData,
|
|
231
227
|
readonly total: MoneyData,
|
|
232
228
|
metadata?: EventMetadata,
|
|
233
229
|
) {
|
|
234
|
-
super(
|
|
230
|
+
super(metadata);
|
|
235
231
|
}
|
|
236
232
|
}
|
|
237
233
|
|
|
@@ -239,11 +235,11 @@ export class OrderConfirmed extends EventBase {
|
|
|
239
235
|
static readonly eventName = 'OrderConfirmed';
|
|
240
236
|
|
|
241
237
|
constructor(
|
|
242
|
-
orderId: string,
|
|
238
|
+
readonly orderId: string,
|
|
243
239
|
readonly total: MoneyData,
|
|
244
240
|
metadata?: EventMetadata,
|
|
245
241
|
) {
|
|
246
|
-
super(
|
|
242
|
+
super(metadata);
|
|
247
243
|
}
|
|
248
244
|
}
|
|
249
245
|
|
|
@@ -251,13 +247,13 @@ export class OrderCancelled extends EventBase {
|
|
|
251
247
|
static readonly eventName = 'OrderCancelled';
|
|
252
248
|
|
|
253
249
|
constructor(
|
|
254
|
-
orderId: string,
|
|
250
|
+
readonly orderId: string,
|
|
255
251
|
readonly reason: string,
|
|
256
252
|
readonly reservationId: string | null,
|
|
257
253
|
readonly paymentId: string | null,
|
|
258
254
|
metadata?: EventMetadata,
|
|
259
255
|
) {
|
|
260
|
-
super(
|
|
256
|
+
super(metadata);
|
|
261
257
|
}
|
|
262
258
|
}
|
|
263
259
|
|
|
@@ -265,13 +261,13 @@ export class PaymentAuthorizationInsufficient extends EventBase {
|
|
|
265
261
|
static readonly eventName = 'PaymentAuthorizationInsufficient';
|
|
266
262
|
|
|
267
263
|
constructor(
|
|
268
|
-
orderId: string,
|
|
264
|
+
readonly orderId: string,
|
|
269
265
|
readonly paymentId: string,
|
|
270
266
|
readonly authorized: MoneyData,
|
|
271
267
|
readonly required: MoneyData,
|
|
272
268
|
metadata?: EventMetadata,
|
|
273
269
|
) {
|
|
274
|
-
super(
|
|
270
|
+
super(metadata);
|
|
275
271
|
}
|
|
276
272
|
}
|
|
277
273
|
|
|
@@ -474,7 +470,7 @@ Cómo encaja con las reglas:
|
|
|
474
470
|
|
|
475
471
|
* `customerId` es una referencia por identidad (regla 3); `stockReservation` es la identidad de otro agregado de otro contexto, y `paymentAuthorization` un value object con lo mínimo que el pedido necesita de Pagos.
|
|
476
472
|
* Las líneas son un value object (`OrderLines`, parte reemplazable entera); `addLine` sustituye la colección por la que devuelve `withLine`, y la regla de SKU único la comprueba el propio value object.
|
|
477
|
-
* Constructor privado, una factoría de negocio (`place`, que registra `OrderPlaced`) y `fromPrimitives`, que reconstruye sin reglas de creación ni eventos; `toPrimitives()` devuelve solo datos planos. La conversión es solo del propio agregado (instantánea): el repositorio, cuyo constructor recibe solo las dependencias de su tecnología, llama directamente a `order.toPrimitives()` al escribir y a `Order.fromPrimitives(row
|
|
473
|
+
* Constructor privado, una factoría de negocio (`place`, que registra `OrderPlaced`) y `fromPrimitives`, que reconstruye sin reglas de creación ni eventos; `toPrimitives()` devuelve solo datos planos. La conversión es solo del propio agregado (instantánea): el repositorio, cuyo constructor recibe solo las dependencias de su tecnología, llama directamente a `order.toPrimitives()` al escribir y a `Order.fromPrimitives(row.primitives)` al leer (la versión de la fila la guarda en su `WeakMap`, no en el agregado), sin mapper ni comprobación de forma en medio, y no lee los campos. Quien añade un campo lo añade a `toPrimitives`/`fromPrimitives`, y el spec del agregado comprueba la ida y vuelta `[Fuera de los libros: convención de @archi-code/ddd]`.
|
|
478
474
|
* Cada método recibe el reloj (`clock: { now(): Date }`) y lo pasa a los eventos (`{ clock }`): el dominio no llama a `new Date()` y los tests son deterministas.
|
|
479
475
|
|
|
480
476
|
### Comandos frente a hechos
|
|
@@ -483,7 +479,7 @@ Cómo encaja con las reglas:
|
|
|
483
479
|
|
|
484
480
|
* Los métodos que reaccionan a hechos son **idempotentes** y **no lanzan errores de negocio**.
|
|
485
481
|
* Si el hecho no encaja con las reglas, el agregado registra un evento con nombre del negocio (`PaymentAuthorizationInsufficient`) y otra política decide la compensación (anular la autorización, avisar al cliente). Así el problema queda visible en el modelo en vez de perdido en una cola de errores.
|
|
486
|
-
* Los errores de negocio con excepción (`DomainException` con su código) quedan para comandos de usuario (`cancel`, `addLine`), donde alguien puede recibir la respuesta (`
|
|
482
|
+
* Los errores de negocio con excepción (`DomainException` con su código) quedan para comandos de usuario (`cancel`, `addLine`), donde alguien puede recibir la respuesta (`ExceptionClassifier.classify(error)` los clasifica como `ExceptionKind.Invalid`, 422 en HTTP, y el punto de entrada puede dar otro estado a su código).
|
|
487
483
|
|
|
488
484
|
Qué protege este agregado (sus invariantes, regla 1):
|
|
489
485
|
* El total es siempre la suma de sus líneas (las líneas son un value object con su regla).
|
|
@@ -499,9 +495,8 @@ No inyectes repositorios ni servicios en el agregado. Busca antes lo necesario y
|
|
|
499
495
|
El caso de uso construye los valores en `toCommand` y se los pasa al agregado en `handle`:
|
|
500
496
|
|
|
501
497
|
```ts
|
|
502
|
-
import { ValueObjects } from '@archi-code/ddd/domain';
|
|
503
498
|
import type { AggregateFinder, AggregateSaver } from '@archi-code/ddd/domain';
|
|
504
|
-
import { UseCase } from '@archi-code/ddd/application';
|
|
499
|
+
import { UseCase, ValueObjectBuilder } from '@archi-code/ddd/application';
|
|
505
500
|
import type { Clock, EventBus } from '@archi-code/ddd/application';
|
|
506
501
|
|
|
507
502
|
interface PaymentAuthorizationInput {
|
|
@@ -531,7 +526,7 @@ export class RegisterPaymentAuthorization extends UseCase<
|
|
|
531
526
|
}
|
|
532
527
|
|
|
533
528
|
protected toCommand(input: PaymentAuthorizationInput): PaymentAuthorizationCommand {
|
|
534
|
-
return
|
|
529
|
+
return ValueObjectBuilder.build({
|
|
535
530
|
orderId: () => new OrderId(input.orderId),
|
|
536
531
|
authorization: () =>
|
|
537
532
|
PaymentAuthorization.fromPrimitives({
|
|
@@ -560,7 +555,7 @@ El agregado recibe valores, no repositorios. Como registra un hecho (lo ejecuta
|
|
|
560
555
|
|
|
561
556
|
## 5. Concurrencia optimista
|
|
562
557
|
|
|
563
|
-
* Versión en la raíz, incrementada con cada comando que cambie estado en cualquier parte del agregado [IDDD cap. 10]. Con el paquete es
|
|
558
|
+
* Versión en la raíz, incrementada con cada comando que cambie estado en cualquier parte del agregado [IDDD cap. 10]. Con el paquete la versión es solo del repositorio: el agregado no la declara ni la mapea y el caso de uso nunca la ve. El repositorio recuerda la versión de cada agregado que cargó o guardó en un `WeakMap<Order, number>` (`0` si no la conoce: es nuevo), escribe solo donde la versión guardada es esa, guarda `version + 1` junto a los primitivos (nunca dentro) y actualiza el `WeakMap` solo cuando la escritura ha ido bien (en SQL, tras el `COMMIT`) `[Fuera de los libros: convención de @archi-code/ddd]`.
|
|
564
559
|
* Si las partes internas son entidades y el ORM no detecta su cambio como cambio de la raíz, es una señal más para reducir el agregado a raíz más value objects [IDDD cap. 10].
|
|
565
560
|
* En almacenes de documentos o clave-valor el agregado se guarda entero y la versión se comprueba en una sola operación [IDDD cap. 10].
|
|
566
561
|
|
|
@@ -572,7 +567,7 @@ UPDATE orders
|
|
|
572
567
|
WHERE id = $1 AND version = $4;
|
|
573
568
|
```
|
|
574
569
|
|
|
575
|
-
Si la sentencia no afecta a ninguna fila, el repositorio lanza `new ConcurrencyException('Order', order.id.value,
|
|
570
|
+
`$4` es la versión que el repositorio recordaba (`this.versions.get(order) ?? 0`). Si la sentencia no afecta a ninguna fila, el repositorio lanza `new ConcurrencyException('Order', order.id.value, version)` (de `@archi-code/ddd/application`; `ExceptionKind.Conflict`, 409 en HTTP; el nombre del agregado va como texto literal porque los minificadores renombran las clases); un agregado nuevo (versión `0`) se inserta y un id que ya existe es también un conflicto. Si escribe, hace `this.versions.set(order, version + 1)`. En los tests lo cumple el `InMemoryOrderRepository` que el proyecto escribe en `test/support/`; el repositorio real y su suite de contrato están en `ddd-repositorios-persistencia`.
|
|
576
571
|
|
|
577
572
|
Reintentar automáticamente solo es seguro si el comando es idempotente o se vuelve a evaluar contra el estado recargado: con el paquete, un bucle con límite en `handle` en el que cada intento vuelve a cargar el agregado y vuelve a guardarlo (unos pocos intentos y después se relanza); un caso de uso que ejecuta un listener no reintenta por su cuenta, lo hace la reentrega del canal `[Fuera de los libros: convención de @archi-code/ddd]`. Ejemplo en `ddd-skills/ddd-repositorios-persistencia/references/transacciones-ts.md`.
|
|
578
573
|
|
|
@@ -673,7 +668,7 @@ describe('Order', () => {
|
|
|
673
668
|
expect(order.isConfirmed()).toBe(false);
|
|
674
669
|
order.registerStockReservation(aReservation(), clock);
|
|
675
670
|
expect(order.isConfirmed()).toBe(true);
|
|
676
|
-
expect(order.pullDomainEvents().map((event) => event.eventName
|
|
671
|
+
expect(order.pullDomainEvents().map((event) => event.eventName)).toContain('OrderConfirmed');
|
|
677
672
|
});
|
|
678
673
|
|
|
679
674
|
it('records an insufficient authorization without throwing and without confirming', () => {
|
|
@@ -681,7 +676,7 @@ describe('Order', () => {
|
|
|
681
676
|
order.registerStockReservation(aReservation(), clock);
|
|
682
677
|
order.registerPaymentAuthorization(anAuthorizationOf(1000), clock);
|
|
683
678
|
expect(order.isConfirmed()).toBe(false);
|
|
684
|
-
expect(order.pullDomainEvents().map((event) => event.eventName
|
|
679
|
+
expect(order.pullDomainEvents().map((event) => event.eventName)).toContain(
|
|
685
680
|
'PaymentAuthorizationInsufficient',
|
|
686
681
|
);
|
|
687
682
|
});
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
[Fuera de los libros: convención de @archi-code/ddd]
|
|
10
10
|
|
|
11
|
-
*
|
|
11
|
+
* Cada subpath del paquete exporta las excepciones de su capa y cada capa importa solo el suyo y los interiores: el dominio importa `@archi-code/ddd/domain` (con `DomainException`, `ValidationException`…) y `@archi-code/validation` (no "nada externo"); la aplicación añade `@archi-code/ddd/application` (con `AggregateNotFoundException`, `ConcurrencyException`, `NotAllowedException`…); solo infraestructura y el composition root usan `@archi-code/ddd/infrastructure` (con `InternalErrorException`), salvo los puntos de entrada, que traducen los errores con `ExceptionClassifier` de `@archi-code/ddd/infrastructure`.
|
|
12
12
|
* No se escriben clases base ni puertos genéricos en el proyecto: `AggregateRoot`, `Entity`, `EventBase`, los tipos de value object y `Composite` vienen de `@archi-code/ddd/domain`; los contratos de repositorio son los roles `AggregateFinder`, `AggregateSaver` y `AggregateRemover` de `@archi-code/ddd/domain` (no hay un `OrderRepository` propio); `EventBus` y `Clock` vienen de `@archi-code/ddd/application` y `EventChannel` de `@archi-code/ddd/infrastructure`. No hay puerto de transacciones: el repositorio es responsable de la transacción y de todo lo relacionado con la persistencia.
|
|
13
13
|
* Un mecanismo de mensajería es solo un `EventChannel` en `infrastructure/messaging/`; el bus es siempre `DomainEventBus`, y cambiar de tecnología cambia una línea del composition root. `DomainEventBus`, `EventRegistry` y el puerto `EventChannel` (con `ChannelHandler` y `ChannelSubscription`) se importan de `@archi-code/ddd/infrastructure`: son cableado de infraestructura. La aplicación solo ve el contrato para publicar (`EventBus`) y para escuchar (`EventListener` + `@ListensTo`) de `@archi-code/ddd/application`.
|
|
14
14
|
* El composition root de cada contexto es el único que construye y conecta todo (repositorios, canal, `DomainEventBus`, suscripción de listeners con `bus.subscribe`) y lee `process.env`; nadie lo importa. El `dependency-cruiser.cjs` de esta guía ya aplica estas reglas.
|
|
@@ -37,10 +37,10 @@ infrastructure (adaptadores salida) ─┘ ▲
|
|
|
37
37
|
|
|
38
38
|
| Zona | Contiene | Puede depender de | Nunca |
|
|
39
39
|
|---|---|---|---|
|
|
40
|
-
| `domain` | Agregados (`AggregateRoot`), entidades (`Entity`), value objects, eventos de dominio (`EventBase`), servicios de dominio, puertos hacia servicios externos (`ProductPricing`); los contratos de repositorio son los roles `AggregateFinder`/`AggregateSaver`/`AggregateRemover` del paquete | Nada externo salvo `@archi-code/ddd/domain
|
|
40
|
+
| `domain` | Agregados (`AggregateRoot`), entidades (`Entity`), value objects, eventos de dominio (`EventBase`), servicios de dominio, puertos hacia servicios externos (`ProductPricing`); los contratos de repositorio son los roles `AggregateFinder`/`AggregateSaver`/`AggregateRemover` del paquete | Nada externo salvo `@archi-code/ddd/domain` (también sus excepciones) y `@archi-code/validation` | Framework web, ORM, cliente HTTP, `process.env`, logger |
|
|
41
41
|
| `application` | Casos de uso (`UseCase`), listeners (`EventListener` + `@ListensTo`), consultas de lectura con sus DTOs, puertos de salida no de dominio (`EventBus` y `Clock` del paquete; los tuyos, como `Warehouse`) | `domain`, `@archi-code/ddd/application` | Express/Fastify, SQL, detalles de mensajería |
|
|
42
42
|
| `infrastructure` | Repositorios concretos (`MongoOrderRepository`), ACL, canales de eventos (`EventChannel`) y el cableado de eventos (`DomainEventBus`, `EventRegistry`, en el composition root), clientes HTTP; el repositorio se encarga de la atomicidad de cada escritura, la versión, la conexión y los errores del driver | `domain`, `application`, `@archi-code/ddd/infrastructure` | Lógica de negocio |
|
|
43
|
-
| `interfaces` | Controladores REST, consumidores de mensajes, CLI, resolvers GraphQL | `application`, `
|
|
43
|
+
| `interfaces` | Controladores REST, consumidores de mensajes, CLI, resolvers GraphQL | `application`, las excepciones de `@archi-code/ddd/domain` y `/application`, `ExceptionClassifier` de `@archi-code/ddd/infrastructure` (y tipos de `domain` solo si es imprescindible) | Lógica de negocio, acceso directo a repositorios |
|
|
44
44
|
|
|
45
45
|
Responsabilidades de los servicios de aplicación según IDDD: sin lógica de dominio, controlan transacciones y seguridad (en el paquete, la seguridad con `NotAllowedException`; la transacción pasa al adaptador del repositorio, que hace atómico cada `save` y `remove` `[Fuera de los libros: convención de @archi-code/ddd]`), pueden enviar notificaciones; son clientes directos del modelo y deben ser **finos**. Si crecen, la lógica de dominio se está escapando y el modelo se vuelve anémico [IDDD cap. 4]. Detalle en `ddd-skills/ddd-servicios-capa-aplicacion/GUIA.md`.
|
|
46
46
|
|
|
@@ -57,7 +57,7 @@ En la interfaz se valida formato (campo obligatorio, longitud), no conocimiento
|
|
|
57
57
|
|
|
58
58
|
## REST sin exponer el modelo
|
|
59
59
|
|
|
60
|
-
Exponer el modelo directamente por REST produce interfaces frágiles: cada cambio del modelo rompe a los clientes. IDDD prefiere separar el modelo de la interfaz: recursos diseñados por casos de uso, construidos a partir de uno o varios agregados [IDDD cap. 4]. En TypeScript: el controlador comprueba la forma de la petición, llama a `execute` del caso de uso con su input, responde con lo que este devuelve (un id, un DTO o nada; nunca el agregado) y traduce los errores
|
|
60
|
+
Exponer el modelo directamente por REST produce interfaces frágiles: cada cambio del modelo rompe a los clientes. IDDD prefiere separar el modelo de la interfaz: recursos diseñados por casos de uso, construidos a partir de uno o varios agregados [IDDD cap. 4]. En TypeScript: el controlador comprueba la forma de la petición, llama a `execute` del caso de uso con su input, responde con lo que este devuelve (un id, un DTO o nada; nunca el agregado) y traduce los errores según el `ExceptionKind` de `ExceptionClassifier.classify(error)` (`@archi-code/ddd/infrastructure`) a su protocolo (404, 403, 409, 422… en HTTP). Ejemplo en `ddd-skills/ddd-arquitectura/references/estructura-hexagonal-ts.md`.
|
|
61
61
|
|
|
62
62
|
## Señales de alarma
|
|
63
63
|
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
* Uso: npx depcruise src --config .dependency-cruiser.cjs
|
|
4
4
|
* Funciona con src/contexts/<contexto>/ y con src/app/contexts/<contexto>/ (proyectos con la lógica en src/app/).
|
|
5
5
|
* Probado con dependency-cruiser 16.10.0 y TypeScript 5.6, con un paquete simulado que expone
|
|
6
|
-
* @archi-code/ddd/{domain,application,infrastructure
|
|
6
|
+
* @archi-code/ddd/{domain,application,infrastructure}. Si la estructura publicada del
|
|
7
7
|
* paquete difiere, ajusta ARCHI_LAYER.
|
|
8
|
+
* Cada subpath exporta las excepciones de su capa: cada capa importa solo el subpath de su capa y
|
|
9
|
+
* los interiores (domain → /domain; application → /domain y /application; infraestructura → todos).
|
|
8
10
|
*/
|
|
9
11
|
// Grupo no capturante para app/: así $1 en las reglas es siempre el nombre del contexto.
|
|
10
12
|
const CTX = '^src/(?:app/)?contexts/([^/]+)/';
|
|
@@ -16,7 +18,7 @@ module.exports = {
|
|
|
16
18
|
{
|
|
17
19
|
name: 'domain-solo-paquetes-permitidos',
|
|
18
20
|
comment:
|
|
19
|
-
'El dominio solo usa @archi-code/ddd (
|
|
21
|
+
'El dominio solo usa @archi-code/ddd/domain (también sus excepciones) y @archi-code/validation; nada más de npm.',
|
|
20
22
|
severity: 'error',
|
|
21
23
|
from: { path: CTX + 'domain/' },
|
|
22
24
|
to: {
|
|
@@ -26,7 +28,9 @@ module.exports = {
|
|
|
26
28
|
},
|
|
27
29
|
{
|
|
28
30
|
name: 'domain-sin-capas-exteriores-del-paquete',
|
|
29
|
-
comment:
|
|
31
|
+
comment:
|
|
32
|
+
'El dominio nunca importa @archi-code/ddd/application ni @archi-code/ddd/infrastructure ' +
|
|
33
|
+
'(tampoco sus excepciones).',
|
|
30
34
|
severity: 'error',
|
|
31
35
|
from: { path: CTX + 'domain/' },
|
|
32
36
|
to: { path: ARCHI_LAYER('application|infrastructure') },
|
|
@@ -39,7 +43,9 @@ module.exports = {
|
|
|
39
43
|
},
|
|
40
44
|
{
|
|
41
45
|
name: 'application-sin-infraestructura',
|
|
42
|
-
comment:
|
|
46
|
+
comment:
|
|
47
|
+
'La aplicación no conoce adaptadores propios ni @archi-code/ddd/infrastructure ' +
|
|
48
|
+
'(tampoco InternalErrorException; un error ajeno lo envuelve UseCase en UnexpectedErrorException).',
|
|
43
49
|
severity: 'error',
|
|
44
50
|
from: { path: CTX + 'application/' },
|
|
45
51
|
to: { path: [CTX + '(?:infrastructure|interfaces)/', ARCHI_LAYER('infrastructure')] },
|
|
@@ -47,8 +53,9 @@ module.exports = {
|
|
|
47
53
|
{
|
|
48
54
|
name: 'interfaces-no-saltan-a-infraestructura',
|
|
49
55
|
comment:
|
|
50
|
-
'Los puntos de entrada llaman a casos de uso; no importan repositorios ni adaptadores. ' +
|
|
51
|
-
'
|
|
56
|
+
'Los puntos de entrada llaman a casos de uso; no importan repositorios ni adaptadores propios. ' +
|
|
57
|
+
'De @archi-code/ddd/infrastructure solo usan ExceptionClassifier y ExceptionKind para traducir ' +
|
|
58
|
+
'los errores (dependency-cruiser no distingue nombres importados: revísalo en la revisión de código).',
|
|
52
59
|
severity: 'error',
|
|
53
60
|
from: { path: CTX + 'interfaces/' },
|
|
54
61
|
to: { path: CTX + 'infrastructure/' },
|
|
@@ -32,13 +32,13 @@ Más detalle en `ddd-skills/ddd-event-sourcing-cqrs/GUIA.md`.
|
|
|
32
32
|
|
|
33
33
|
Contextos que se comunican publicando y consumiendo eventos de dominio. Es la base de la integración robusta descrita en `ddd-skills/ddd-context-mapping/GUIA.md` y del diseño de `ddd-skills/ddd-eventos-dominio/GUIA.md`. Dentro de un contexto, los eventos también desacoplan efectos secundarios del caso de uso: el caso de uso llama a `events.publish(order.pullDomainEvents())` después de `await this.orders.save(order)` y un listener (`PrepareShipmentOnOrderLineAdded`) ejecuta la acción siguiente.
|
|
34
34
|
|
|
35
|
-
Con el paquete, la tecnología es un `EventChannel` del proyecto (`RabbitMqEventChannel`, un relay que entrega el outbox que el repositorio escribió junto al agregado…) bajo el mismo `DomainEventBus`; en tests, `InMemoryEventChannel`. Hacia otros contextos no viaja
|
|
35
|
+
Con el paquete, la tecnología es un `EventChannel` del proyecto (`RabbitMqEventChannel`, un relay que entrega el outbox que el repositorio escribió junto al agregado…) bajo el mismo `DomainEventBus`; en tests, `InMemoryEventChannel`. Hacia otros contextos no viaja el evento de dominio tal cual (`{ ...event }`): el adaptador que habla con el broker lo traduce a un contrato publicado y versionado (`sales.order.OrderPlaced`) [IDDD cap. 13], algo que el paquete no da y escribe el proyecto. `[Fuera de los libros: convención de @archi-code/ddd]`
|
|
36
36
|
|
|
37
37
|
## Event Sourcing
|
|
38
38
|
|
|
39
39
|
El estado de un agregado se guarda como la secuencia de sus eventos y se reconstruye aplicándolos [Destilado cap. 6; IDDD cap. 4]. Aporta historial completo y auditoría, pero cambia por completo la persistencia y las consultas. Ver `ddd-skills/ddd-event-sourcing-cqrs/GUIA.md` antes de decidir.
|
|
40
40
|
|
|
41
|
-
El paquete no trae event store: cada repositorio guarda el `toPrimitives()` del agregado junto a su `
|
|
41
|
+
El paquete no trae event store: cada repositorio guarda el `toPrimitives()` del agregado junto a la versión que él mismo lleva (en su `WeakMap`), que crece 1 por cada `save`, no por evento. Un agregado con Event Sourcing sigue extendiendo `AggregateRoot` y registrando con `record()`, pero su repositorio (en `infrastructure/`, implementando `AggregateFinder`/`AggregateSaver`) lleva la versión del flujo él mismo y reconstruye el agregado aplicando sus eventos, no con `toPrimitives()`/`fromPrimitives()`. `[Fuera de los libros: convención de @archi-code/ddd]`
|
|
42
42
|
|
|
43
43
|
## Tabla de decisión
|
|
44
44
|
|