Read or download the complete SDK project, then follow its setup on your computer. The separate C# companion practice below runs in SharpRune’s .NET 9 console environment.
The architectural boundary
Vertical slices organize change around features and their use cases rather than forcing every edit to travel through broad technical folders. This example groups creating and finding labels while retaining an inward dependency boundary between application behavior and storage. These are compatible design choices: a feature-oriented layout does not automatically remove every layer, and layer boundaries need not scatter one feature across unrelated generic services. The portable code demonstrates the behavior in one file. The external application project includes a Features path, while separate projects and an actual checker enforce the chosen assembly references.
Walk through the portable program
CreateLabel receives the text with spaces around a1, rejects null, trims the spaces, and converts it to uppercase using invariant rules. The normalized nonempty Code A1 becomes an immutable Label saved through the write contract. FindLabel requests exactly A1 through the read contract, and the ordinal memory store finds that key. The host prints A1. Lookup does not itself normalize input, so asking for lowercase a1 would not be the same operation in this implementation. Following both paths prevents an unstated assumption that every feature automatically shares the creator's ingestion policy.
Build real physical dependencies
The external Application Features file contains the relevant handlers and their narrow read/write contracts. Domain contains the small Label value, and Infrastructure implements both contracts with one dictionary. Host selects the concrete store and drives the feature. These roles are illustrative, not a claim that each command needs its own project or one universal folder convention. The project checker inspects actual references; a folder move alone cannot make a forbidden dependency disappear. Choose the level of physical isolation according to the kinds of changes and ownership boundaries the application must support.
Pitfalls and unproved guarantees
Feature orientation can still produce duplication or hidden coupling if every slice reaches directly into another feature's private mutable state. Conversely, mechanically extracting every repeated line into a global service can couple unrelated use cases prematurely. The sample deliberately keeps normalization with creation and lookup policy explicit. It does not provide persistence, uniqueness under concurrent writes, or a complete label domain invariant enforced at construction. The memory store replaces equal keys by design. If those policies are inappropriate, define them and their rejection outcomes before claiming a particular folder arrangement solved the problem.
Practice one: isolate behavior
First query lowercase a1 after the baseline creation and predict missing under the ordinal read contract. Then create whitespace-only input and predict rejection before Save. Hint: normalization and domain acceptance are separate steps, and the read path currently has its own explicit input expectations. Use fresh stores when comparing independent scenarios. Add a second valid label and verify that the first remains readable, with direct expected codes rather than merely checking that both calls returned some object.
Practice two: test a boundary
For another exercise, introduce a rename-label feature with a stated policy for source existence and destination collision. Keep its behavior close to that use case and decide which narrow storage capability it requires. Hint: do not expose the dictionary merely to avoid defining a meaningful boundary. Then reorganize the source file into multiple feature files inside the same Application project and verify unchanged behavior and reference checks. Explain which changes improve navigation and which would actually affect compiler-enforced assembly isolation.
Review the evidence
Review slices by asking what business change they contain, what contracts they share, and which implementation details they keep private. Review dependencies separately through real project references and access modifiers. The example shows one coherent label feature with two operations, not a universal prescription for all applications. Vertical slices, layered models, and modular boundaries can be combined when their purposes are clear. Use the external solution to inspect physical dependencies, and avoid claiming that a feature folder alone enforces a boundary or that every cross-feature concern should become a globally shared abstraction.
Vertical slices alongside dependency boundaries — physical dependency lab
Set up the SDK project
- Save every file at its exact relative path under a fresh directory.
- Use a .NET 10 SDK. Run dotnet build ArchitectureLab.slnx.
- Run dotnet run --project Host/Host.csproj --no-build.
- From the solution directory run dotnet run --project ArchitectureCheck/ArchitectureCheck.csproj --no-build -- .
Download complete SDK project ↗
Project files
ArchitectureLab.slnx
Download file ↗<Solution>
<Project Path="Domain/Domain.csproj" />
<Project Path="Application/Application.csproj" />
<Project Path="Infrastructure/Infrastructure.csproj" />
<Project Path="Host/Host.csproj" />
<Project Path="ArchitectureCheck/ArchitectureCheck.csproj" />
</Solution>
Domain/Domain.csproj
Download file ↗<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>14.0</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
Application/Application.csproj
Download file ↗<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>14.0</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../Domain/Domain.csproj" />
</ItemGroup>
</Project>
Infrastructure/Infrastructure.csproj
Download file ↗<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>14.0</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../Domain/Domain.csproj" />
<ProjectReference Include="../Application/Application.csproj" />
</ItemGroup>
</Project>
Host/Host.csproj
Download file ↗<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>14.0</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../Domain/Domain.csproj" />
<ProjectReference Include="../Application/Application.csproj" />
<ProjectReference Include="../Infrastructure/Infrastructure.csproj" />
</ItemGroup>
</Project>
ArchitectureCheck/ArchitectureCheck.csproj
Download file ↗<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>14.0</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
Domain/Model.cs
Download file ↗using System;
using System.Collections.Generic;
using System.Linq;
namespace BoundaryLab.Domain;
public record Label(string Code);
Application/Features/Labels.cs
Download file ↗using System;
using System.Collections.Generic;
using System.Linq;
using BoundaryLab.Domain;
namespace BoundaryLab.Application;
public interface ILabelWrite { void Save(Label label); }
public interface ILabelRead { Label? Find(string code); }
public sealed class CreateLabel(ILabelWrite store)
{
public void Execute(string text)
{
ArgumentNullException.ThrowIfNull(text);
string code = text.Trim().ToUpperInvariant();
if (code.Length == 0) throw new ArgumentException("Code required", nameof(text));
store.Save(new Label(code));
}
}
public sealed class FindLabel(ILabelRead store) { public Label? Execute(string code) => store.Find(code); }
Infrastructure/Model.cs
Download file ↗using System;
using System.Collections.Generic;
using System.Linq;
using BoundaryLab.Domain;
using BoundaryLab.Application;
namespace BoundaryLab.Infrastructure;
public sealed class LabelStore : ILabelWrite, ILabelRead
{
private readonly Dictionary<string, Label> labels = new(StringComparer.Ordinal);
public void Save(Label label) => labels[label.Code] = label;
public Label? Find(string code) => labels.TryGetValue(code, out var label) ? label : null;
}
Host/Program.cs
Download file ↗using System;
using System.Collections.Generic;
using System.Linq;
using BoundaryLab.Domain;
using BoundaryLab.Application;
using BoundaryLab.Infrastructure;
var store = new LabelStore();
new CreateLabel(store).Execute(" a1 ");
Console.WriteLine(new FindLabel(store).Execute("A1")?.Code ?? "missing");
ArchitectureCheck/Program.cs
Download file ↗using System.Xml.Linq;
string root = Path.GetFullPath(args.Length == 0 ? "." : args[0]);
var allowed = new Dictionary<string, HashSet<string>>(StringComparer.Ordinal)
{
["Domain"] = new(),
["Application"] = new() { "Domain" },
["Infrastructure"] = new() { "Domain", "Application" },
["Host"] = new() { "Domain", "Application", "Infrastructure" }
};
int violations = 0;
foreach (var item in allowed)
{
string path = Path.Combine(root, item.Key, item.Key + ".csproj");
var document = XDocument.Load(path);
foreach (var reference in document.Descendants().Where(element => element.Name.LocalName == "ProjectReference"))
{
string include = (string?)reference.Attribute("Include") ?? throw new InvalidOperationException("Missing Include");
string targetPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(path)!, include));
string target = Path.GetFileNameWithoutExtension(targetPath);
string expectedPath = Path.Combine(root, target, target + ".csproj");
if (!item.Value.Contains(target) || targetPath != expectedPath)
{
violations++;
Console.WriteLine($"violation:{item.Key}->{target}");
}
}
}
Console.WriteLine($"projects:{allowed.Count}");
Console.WriteLine($"violations:{violations}");
return violations == 0 ? 0 : 1;
Expected result
Host output: A1 Physical ProjectReference policy check output: projects:4 violations:0 Both commands exit zero. The checker inspects the four business-project files; its own tooling project is outside that policy. It checks declared project-reference direction, not all runtime behavior or package dependencies.
Use the listed SDK and dependencies for this project. Open C# practice runs the separate portable exercise.
C# companion practice
using System;
using System.Collections.Generic;
using System.Linq;
var store = new LabelStore();
new CreateLabel(store).Execute(" a1 ");
Console.WriteLine(new FindLabel(store).Execute("A1")?.Code ?? "missing");
public record Label(string Code);
public interface ILabelWrite { void Save(Label label); }
public interface ILabelRead { Label? Find(string code); }
public sealed class CreateLabel(ILabelWrite store)
{
public void Execute(string text)
{
ArgumentNullException.ThrowIfNull(text);
string code = text.Trim().ToUpperInvariant();
if (code.Length == 0) throw new ArgumentException("Code required", nameof(text));
store.Save(new Label(code));
}
}
public sealed class FindLabel(ILabelRead store) { public Label? Execute(string code) => store.Find(code); }
public sealed class LabelStore : ILabelWrite, ILabelRead
{
private readonly Dictionary<string, Label> labels = new(StringComparer.Ordinal);
public void Save(Label label) => labels[label.Code] = label;
public Label? Find(string code) => labels.TryGetValue(code, out var label) ? label : null;
}
C# practice output
A1
Add another feature independently, preserving project-reference direction. Feature folders aid navigation but do not prevent forbidden assembly dependencies.