Problem
PlantUML entity includes today use C4-inspired pseudo-filenames that don't match anything else in the engine:
!include systems/sys_payment_gateway.iuml
!include systems/dmn_billing.iuml
!include systems/svc_invoice_api.iuml
!include systems/ext/sys_yookassa.iuml
For writers and reviewers, this path has several small frictions that compound:
- Everything lives under
systems/ even when the entity is a domain or service — the directory lies about what it contains.
- Short C4 prefixes (
dmn_ / sys_ / svc_) require mental decoding every time someone reads a diagram. The site itself uses the full words (domain, system) everywhere else.
- Name translation is hidden. A service directory called
payment-gateway is referenced as payment_gateway — hyphen-to-underscore substitution that has no user-visible explanation.
- The kind vocabulary is narrower than the concept.
service doesn't cover libraries, frontends, CLIs, or anything else Backstage treats as a component.
- Two incompatible ref dialects coexist in one repo. Wikilinks (introduced in 0.1.21) use stable, future-proof refs:
[[domain:billing::overview]]. Diagrams use pseudo-filenames. Writers have to learn both.
- The external flag lives inside the path (
ext/), not as an attribute. There's no room to express anything else about how an entity is referenced (deprecated, owned by another team, specific version) without inventing more directories.
These paths aren't files. The engine parses the string (see parse_include_path in crates/rw-diagrams/src/meta_includes.rs), looks up the entity in the live site registry, and emits a C4 macro. Writers and reviewers shouldn't have to pretend it's a filesystem path.
Proposal
Use angle-bracket refs that mirror the wikilink syntax:
!include <system:payments>
!include <domain:billing>
!include <component:payment-sdk>
!include <system:yookassa external>
Why angle brackets
PlantUML's stdlib already uses <…> for "resolvable identifier, not a file" (<C4/C4_Context>, <tupadr3/font-awesome/server>). Stdlib paths are slash-separated and never contain :, so <system:payments> is structurally unambiguous — the : is all the disambiguation the parser needs.
Why not load macros once via C4-PlantUML convention
The natural alternative would be:
!include <rw/entities>
$system(payments)
$system_ext(yookassa)
This doesn't fit rw's trajectory: the entity registry is expected to become large and distributed (fetched on demand from an rw hub). Preloading all entity macros upfront doesn't scale. Each !include <system:foo> is already a targeted lookup — angle brackets make that lookup semantics explicit, matching how PlantUML treats stdlib paths.
Symmetric with wikilinks
Doc side and diagram side share one vocabulary:
| Surface |
Syntax |
| Markdown prose |
[[system:payments]], [[component:payment-sdk::api]] |
Diagram (!include) |
<system:payments>, <component:payment-sdk> |
Same kinds, same names, same mental model. No underscore substitution, no extension, no directory prefix.
Kind vocabulary: add component, deprecate service
Backstage treats component as the umbrella for services, libraries, frontends, and other software units. service is one of several Backstage spec.type values under component. Rename to match:
domain — unchanged
system — unchanged
component — new canonical kind, replaces service
service — accepted for one release as a deprecated alias, with a warning on load
This follows the existing precedent where type is accepted as an alias for kind (see crates/rw-storage/src/metadata.rs).
Modifiers (space-separated, extensible)
Variants live after the ref as whitespace-separated tokens:
!include <system:yookassa external>
Today external is the only modifier (it selects System_Ext instead of System). The shape leaves room to grow without new syntax:
!include <system:old-checkout deprecated>
!include <component:payment-sdk external version=v2>
Unknown modifiers would error at parse time with a clear message.
Migration
- Old iuml paths keep working for one release, with a deprecation warning on use:
systems/sys_foo.iuml, systems/dmn_foo.iuml, systems/svc_foo.iuml, and their systems/ext/… variants.
kind: service keeps working for one release, with a warning, treated as an alias for component.
- Docs (
docs/diagrams.md, docs/metadata.md) rewritten to lead with the new syntax; old syntax mentioned only in a "Migration" section.
CHANGELOG.md entries for both the new syntax and the kind rename.
Out of scope
- URL path structure for pages (e.g.,
/domains/billing/systems/payments/services/invoice-api) — unchanged.
- Wikilink syntax — unchanged (this proposal only brings diagrams into alignment with it).
- PlantUML stdlib path handling — unchanged.
- Actual
!include <rw/entities> macro-library approach — rejected above; revisit only if the rw hub architecture stops making lookup-per-include viable.
Parser sketch
In resolve_includes (crates/rw-diagrams/src/plantuml.rs:45):
- Strip
< / >. If the body contains :, treat as an rw entity ref and dispatch to meta resolution.
- Otherwise pass through as stdlib (existing behavior, unchanged).
- In meta resolution: split on first whitespace into
ref + modifiers, parse kind:name, look up via MetaIncludeSource, render the matching C4 macro with modifier-driven variant selection.
Problem
PlantUML entity includes today use C4-inspired pseudo-filenames that don't match anything else in the engine:
For writers and reviewers, this path has several small frictions that compound:
systems/even when the entity is adomainorservice— the directory lies about what it contains.dmn_/sys_/svc_) require mental decoding every time someone reads a diagram. The site itself uses the full words (domain,system) everywhere else.payment-gatewayis referenced aspayment_gateway— hyphen-to-underscore substitution that has no user-visible explanation.servicedoesn't cover libraries, frontends, CLIs, or anything else Backstage treats as acomponent.[[domain:billing::overview]]. Diagrams use pseudo-filenames. Writers have to learn both.ext/), not as an attribute. There's no room to express anything else about how an entity is referenced (deprecated, owned by another team, specific version) without inventing more directories.These paths aren't files. The engine parses the string (see
parse_include_pathincrates/rw-diagrams/src/meta_includes.rs), looks up the entity in the live site registry, and emits a C4 macro. Writers and reviewers shouldn't have to pretend it's a filesystem path.Proposal
Use angle-bracket refs that mirror the wikilink syntax:
Why angle brackets
PlantUML's stdlib already uses
<…>for "resolvable identifier, not a file" (<C4/C4_Context>,<tupadr3/font-awesome/server>). Stdlib paths are slash-separated and never contain:, so<system:payments>is structurally unambiguous — the:is all the disambiguation the parser needs.Why not load macros once via C4-PlantUML convention
The natural alternative would be:
This doesn't fit rw's trajectory: the entity registry is expected to become large and distributed (fetched on demand from an rw hub). Preloading all entity macros upfront doesn't scale. Each
!include <system:foo>is already a targeted lookup — angle brackets make that lookup semantics explicit, matching how PlantUML treats stdlib paths.Symmetric with wikilinks
Doc side and diagram side share one vocabulary:
[[system:payments]],[[component:payment-sdk::api]]!include)<system:payments>,<component:payment-sdk>Same kinds, same names, same mental model. No underscore substitution, no extension, no directory prefix.
Kind vocabulary: add
component, deprecateserviceBackstage treats
componentas the umbrella for services, libraries, frontends, and other software units.serviceis one of several Backstagespec.typevalues undercomponent. Rename to match:domain— unchangedsystem— unchangedcomponent— new canonical kind, replacesserviceservice— accepted for one release as a deprecated alias, with a warning on loadThis follows the existing precedent where
typeis accepted as an alias forkind(seecrates/rw-storage/src/metadata.rs).Modifiers (space-separated, extensible)
Variants live after the ref as whitespace-separated tokens:
Today
externalis the only modifier (it selectsSystem_Extinstead ofSystem). The shape leaves room to grow without new syntax:Unknown modifiers would error at parse time with a clear message.
Migration
systems/sys_foo.iuml,systems/dmn_foo.iuml,systems/svc_foo.iuml, and theirsystems/ext/…variants.kind: servicekeeps working for one release, with a warning, treated as an alias forcomponent.docs/diagrams.md,docs/metadata.md) rewritten to lead with the new syntax; old syntax mentioned only in a "Migration" section.CHANGELOG.mdentries for both the new syntax and the kind rename.Out of scope
/domains/billing/systems/payments/services/invoice-api) — unchanged.!include <rw/entities>macro-library approach — rejected above; revisit only if the rw hub architecture stops making lookup-per-include viable.Parser sketch
In
resolve_includes(crates/rw-diagrams/src/plantuml.rs:45):</>. If the body contains:, treat as an rw entity ref and dispatch to meta resolution.ref+modifiers, parsekind:name, look up viaMetaIncludeSource, render the matching C4 macro with modifier-driven variant selection.