Skip to content

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

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

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

Conversation

@alexanderlinne

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 flat diagrams show. Nested diagrams draw slices under their qualified names (PR 2), so they don't change:

pr3-1-flat-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, as they would be drawn under the same name (PR 2).

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 flat 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 image was made

It renders the BuildUmlBySlices_SingleAsterisk snapshot at PR 2 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 97.87234% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 86.67%. Comparing base (e8f8233) to head (676c0b6).

Files with missing lines Patch % Lines
ArchUnitNET/Domain/SliceIdentifier.cs 96.96% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@                        Coverage Diff                         @@
##           nest-slices-in-packages-option     #515      +/-   ##
==================================================================
+ Coverage                           86.56%   86.67%   +0.10%     
==================================================================
  Files                                 261      261              
  Lines                               12766    12801      +35     
  Branches                             1245     1249       +4     
==================================================================
+ Hits                                11051    11095      +44     
+ Misses                               1368     1359       -9     
  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.

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 flat diagrams show. Nested diagrams draw
slices under their qualified names, so they don't change.

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 such slices in one diagram, as they would be drawn under the
same name.

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 flat 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 5b1ca9a to 676c0b6 Compare October 24, 2026 20:10
@alexanderlinne
alexanderlinne marked this pull request as draft October 25, 2026 19:39

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