0078. Effects are split three ways; services follow Vernon's definition and uses lists their dependencies
0078. Effects are split three ways; services follow Vernon’s definition and uses lists their dependencies
Status
Accepted, with two Open items (DOM-08 and DOM-09 below). Amends ADR-0068 (what actions in an entity file is for).
Rows: DOM-01, DOM-13, DOM-14, DOM-15, DOM-16 (ruled); DOM-08, DOM-09 (open).
Date
2026-10-11
Deciders
operator (Saulo Vallory): DOM-13 and DOM-14 on 2026-10-10; DOM-16 on 2026-10-10 and 2026-10-11; DOM-01 on 2026-10-11 (06:40); DOM-15 on 2026-10-11. DOM-08 and DOM-09 are open.
Context
ADR-0068 let an action call other actions and use tx. That works for an entity’s own parts. Hyper’s “complete task” shows the other case: it touches a Task, a Dependency and a Notification, which belong to different aggregates. The study (sections 2.3 to 2.6 and 3.3) asked where such an effect lives, what a service is, and what a service’s dependency line is called.
Decision
- Effects are split three ways (DOM-15):
- The root writes its own parts in the entity file, through
actions. - An effect across aggregates goes in a service. Until services exist, it is plain code that uses
transactionfrom#mesh. - An effect that may happen later uses
publishand a listener (ADR-0079). - An architecture rule (ADR-0084, DOM-30) warns when an entity action calls another aggregate’s action. It is a warning, not a restriction. Whether the warning can be switched off is not ruled.
- The root writes its own parts in the entity file, through
- Where service files live (DOM-13): domain services in their module folder (
src/domain/<module>/), application services with their surface (src/surfaces/<name>/services/). The domain never imports a surface. - What a service is (DOM-14), after Vernon: a domain service is stateless, cross-entity domain logic; an application service coordinates use cases (transactions, security, orchestration).
- The dependency line is
uses(DOM-01). It replaces the study’scall, because the line also lists entities that are only read. - Declared dependencies stay (DOM-16). They narrow the handles a service receives, and
tscenforces them. Mesh warns when a service operation touches only one entity (it belongs on that entity), andarchitecture.mesh.mxcan switch that warning off.
An illustration (the service construct is Open, so the members are the study’s proposal, and only the keyword uses is ruled; the line shape follows the study’s call line):
service :Work
command :complete-task
uses entity=Task actions=[:complete]
uses entity=Dependency actions=[:release]
uses entity=Notification actions=[:create]
Open
- DOM-09: the shape of the service construct and when it is built. The study’s section 3.4 gave four options: A, nothing new (cascades plus application code over
transaction); B,service :Namewithcommandandquerymembers, grouping allowed; C, as B but onecommandorqueryper file with noserviceword; D, aservicethat groups any operations, single-entity ones included. DOM-16 (Mesh warns when a service operation touches one entity) excludes D. The study recommended A for 1.0 and B after. The roadmap principle (RDM-01) is that no new construct is added before the port unless the port needs it, and that as many planned constructs as possible are added before 1.0. So the open part is the shape (B or C) and the milestone. Not decided. - DOM-08: whether Mesh supports publish-subscribe in general, beyond what Hyper needs. Not decided. M6 builds only what Hyper needs (ADR-0079).
Options considered
The study’s D3 (where services live): (a) src/domain/services/ for domain services and src/apps/<name>/services/ for application services; (b) domain services in their module folder and application services with their surface, the domain never importing a surface (chosen). D4: options A to D above (the ruling fixes Vernon’s definition, and the construct is Open). D5 (cascade or service): (a) an operation whose core is a write to one entity’s own row is that entity’s action with cascades, and one with no leading row or a non-record result is a service command; (b) service commands for every cross-entity write; © no rule. The ruling replaced these with the three-way split above. D6 (the single-entity check): (a) a warning computed from the dependency lines; (b) an error; © no check (the ruling is (a), switchable in architecture.mesh.mx).
Trade-off analysis
Three homes for an effect keep each aggregate’s invariants in one place and make a cross-aggregate write visible. The price is that a beginner must pick among three. The rule is a warning, not a ban.
Consequences
- ADR-0068 is amended:
actionsin an entity file is for the root writing its own parts. ADR-0074 is not changed. - Services are not built. Until they are, the plain-code route is the supported one, and Using your domain already shows it.
- DOM-09 stays Open, so no row says when services arrive. RDM-01 applies: not before the port unless the port needs them, and as many planned constructs as possible before 1.0.
Action items
- Operator: DOM-08 and DOM-09.
- When the architecture rules are built (ADR-0084): the “entity action calls another aggregate’s action” warning and the “service touches one entity” warning.