Skip to content

Identify and name slices by their captures, like ArchUnit - #515

Open
alexanderlinne wants to merge 1 commit into
nest-slices-in-packages-optionfrom
identify-slices-by-captures
Open

alexanderlinne wants to merge 1 commit into
nest-slices-in-packages-optionfrom
identify-slices-by-captures

Conversation

@alexanderlinne

@alexanderlinne alexanderlinne commented Oct 24, 2026 •

Copy link
Copy Markdown
Collaborator

Why

A slice is identified by its name, a single string, and named after what the pattern captured, with nothing to mark it as a slice:

  • A custom SliceAssignment has no way to say what its slice consists of.
  • A slice name reads like a namespace.
  • Once PR 4 gives every capture group a meaning, a single string can't hold several captured values without picking a separator that becomes part of the slice's identity.

This PR settles identity and naming while main's matcher still captures exactly one value per type, so the PR changes only names, not which types form a slice. PR 4 then only has to produce several values.

What changes

Identity. A slice is identified by its parts, as SliceIdentifier is in ArchUnit ("equal if and only if their parts are equal"). For a slice pattern, the parts are the values its capture groups captured, in group order. SliceIdentifier.Parts holds them, and the following compare the parts (and Ignored) alone:

  • equality,
  • the hash code,
  • CompareTo,
  • therefore the grouping in SliceAssignment.Apply.

SliceIdentifier.Of(IEnumerable<string>) creates a slice with several parts; Of(string) is the same slice as Of(new[] { part }). Every pattern slice still has one part here, so every rule groups exactly as before.

Names. Every slice is named after its parts like ArchUnit's default, "Slice " + string.Join(" - ", Parts):

Slice Before After
App.(*) over App.Orders Orders Slice Orders
App.(**) over App.Orders.Web Orders.Web Slice Orders.Web
Of(new[] { "Orders", "Http" }) – Slice Orders - Http

The Slice prefix keeps a name from being mistaken for a namespace. It is ArchUnit's default, so messages read the same in both libraries. A later NamingSlices("$2 of $1") would replace the same thing ArchUnit's namingSlices replaces. The name applies to every slice, including SlicedBy and custom SliceAssignments, and it is what failure messages and diagrams show, flat or nested:

pr3-1-flat-labels pr3-2-nested-labels

Every changed snapshot changes only labels and aliases; no slice and no arrow differs. Names with spaces aren't valid PlantUML ids, so these slices get an alias such as Slice_Slice1. That, and bracketing component names in every arrow, comes from the fixes already merged in #507–#510.

Freezing. SliceIdentifier no longer derives from StringIdentifier: the parts are all that identifies a slice. Violation stores need a string key, which SlicesShould takes from the slice's name when it reports a violation, e.g. Slice Orders. A store thus holds what failure messages show, as ArchUnit's stores do, which keep violation messages.

Until now, a slice was stored by its bare name, e.g. Orders, so existing stores of slice rules no longer match and have to be regenerated. Since this PR changes every slice failure message anyway, and PR 4 changes which slices some rules produce, the stores are re-frozen once, together with the rest of the stack. The stores in FreezeTests are updated accordingly.

Hand-built parts that contain - can share a name, and thus a key, with another slice: Of(new[] { "A - B" }) and Of(new[] { "A", "B" }) are different slices, both named Slice A - B. Parts captured by a pattern never contain -, and ArchUnit has the same limitation. The exporter rejects two such slices in one diagram with an ArgumentException naming both slices' parts, as they would be drawn under the same name; until this PR, two distinct slices couldn't share a name.

Guide. The "Cycle Rule" section becomes "Slice Rules": how slices are named, how they are frozen, and the NotDependOnEachOther rule next to BeFreeOfCycles. Like the code, the guide describes slices on their own terms and doesn't refer to ArchUnit.

Breaking changes

  • Slices are named Slice … after their parts, which changes every slice failure message and slice diagram label.
  • Frozen slice violations are stored by the slice's name, so stores of slice rules written before no longer match and have to be regenerated.
  • New are SliceIdentifier.Parts and SliceIdentifier.Of(IEnumerable<string>).
  • SliceIdentifier no longer derives from StringIdentifier, so its Identifier field is gone, and ToString() returns the slice's name.

Not in this PR

An ArchUnit-style NamingSlices("$2 of $1"), so that users can choose slice names themselves. It would be a non-breaking addition: slices are identified by their parts, not their names, and the exporter already rejects two slices that would be drawn under the same name. As in ArchUnit, renaming the slices of a frozen rule would mean re-freezing it.

How the images were made

They render the BuildUmlBySlices_SingleAsterisk and BuildUmlBySlicesNested_SingleAsterisk snapshots at #513 and at this PR, with PlantUML 1.2025.4 and Graphviz, with the remote !include of C4-PlantUML replaced by PlantUML's bundled copy (!include <C4/C4_Container>).

@alexanderlinne
alexanderlinne added this pull request to stack #514 October 24, 2026 19:47
@codecov-commenter

codecov-commenter commented Oct 24, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.33333% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 86.48%. Comparing base (8a2c09b) to head (d4ef3a8).

Files with missing lines Patch % Lines
ArchUnitNET/Domain/SliceIdentifier.cs 96.87% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@                        Coverage Diff                         @@
##           nest-slices-in-packages-option     #515      +/-   ##
==================================================================
+ Coverage                           86.36%   86.48%   +0.12%     
==================================================================
  Files                                 261      261              
  Lines                               12623    12676      +53     
  Branches                             1226     1232       +6     
==================================================================
+ Hits                                10902    10963      +61     
+ Misses                               1374     1366       -8     
  Partials                              347      347              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@alexanderlinne
alexanderlinne force-pushed the identify-slices-by-captures branch from 5b1ca9a to 676c0b6 Compare October 24, 2026 20:10
@alexanderlinne
alexanderlinne marked this pull request as draft October 25, 2026 19:39
A slice was identified by its name, a single string, and named after
what the pattern captured with nothing to mark it as a slice. A custom
SliceAssignment had no way to say what its slice consists of, and a
slice name read like a namespace.

Identify a slice by its parts, as ArchUnit's SliceIdentifier does: for
a slice pattern, the values its capture groups captured, in group order.
SliceIdentifier.Parts holds them, and equality, hash code, CompareTo and
therefore the grouping in SliceAssignment.Apply compare the parts (and
Ignored) alone. The matcher still captures a single value per type, so
every pattern slice has one part and groups exactly as before.
SliceIdentifier.Of(IEnumerable<string>) creates a slice with several
parts; Of(string) is the same slice as Of(new[] { part }).

Name every slice after its parts like ArchUnit's default: "Slice "
followed by the parts joined with " - ":

  App.(*)              App.Orders               Slice Orders
  App.(**)             App.Orders.Web           Slice Orders.Web
  Of(new[] { "Orders", "Http" })                Slice Orders - Http

The prefix keeps a name from being mistaken for a namespace. It applies
to every slice, including SlicedBy and custom SliceAssignments, and it
is what failure messages and diagrams show, flat or nested.

SliceIdentifier no longer derives from StringIdentifier: the parts are
all that identifies a slice. Violation stores need a string key, which
SlicesShould takes from the slice's name when it reports a violation, so
a store holds what failure messages show, e.g. "Slice Orders", as
ArchUnit's stores, which keep violation messages, do.

Hand-built parts that contain " - " can share a name, and thus a key,
with another slice: Of(new[] { "A - B" }) and Of(new[] { "A", "B" }) are
different slices, both named "Slice A - B". Parts captured by a pattern
never contain " - ", and ArchUnit has the same limitation. The exporter
rejects two slices that would be drawn under the same name, instead of
drawing them as one node.

The guide's "Cycle Rule" section becomes "Slice Rules", with how slices
are named and frozen, and the NotDependOnEachOther rule.

Breaking changes:
- Slices are named "Slice ..." from their parts, which changes every
  slice failure message and slice diagram label.
- Frozen slice violations are stored by the slice's name, so stores
  written before no longer match and have to be regenerated.
- New are SliceIdentifier.Parts and SliceIdentifier.Of(IEnumerable).
- SliceIdentifier no longer derives from StringIdentifier, so its
  Identifier field is gone, and ToString() returns the slice's name.

Signed-off-by: Alexander Linne <alexander.linne@tngtech.com>
@alexanderlinne
alexanderlinne force-pushed the identify-slices-by-captures branch from 676c0b6 to d4ef3a8 Compare October 29, 2026 21:35
@alexanderlinne
alexanderlinne marked this pull request as ready for review October 29, 2026 21:44

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants