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
Validation crosses several boundaries: an adapter may reject malformed representation, the application may reject an unsupported request, and the domain must preserve its own rules. This example begins with typed integers and evaluates one bounded rule: quantities one through five are accepted. EvaluateQuantity returns a named result instead of using an exception for this ordinary decision. ResultText formats its code outside the rule. The portable program demonstrates those real values, while the external solution compiles Domain, Application, and presentation separately without inventing a framework Result type or pretending a DTO supplies validation automatically.
Walk through the portable program
The first request uses zero, which is below the accepted range. UnitRule returns false and the application creates a QuantityResult with Accepted false and Code quantity. The second request uses three, so it creates Accepted true and Code accepted. The formatter labels those decisions and prints bad:quantity followed by good:accepted. It does not re-run the rule or change the result. A reader can independently predict both codes from the stated range. The boolean remains useful structured data even though this particular display helper prints only the stable code.
Build real physical dependencies
The external Domain project owns the quantity rule, Application owns its decision representation and operation, and Infrastructure supplies text formatting. These are actual project references checked by the separate tool. That arrangement keeps a wording change from becoming a domain-policy edit. A real HTTP or JSON adapter would additionally map representation failures and valid typed requests, with its own tests. The current source deliberately starts after parsing. It therefore establishes neither transport status codes nor a complete error protocol, and the external console host does not pretend to be a web framework integration.
Pitfalls and unproved guarantees
A public record containing a boolean and a string can be constructed inconsistently by arbitrary callers. The baseline operation creates only its two valid combinations, but the type itself does not enforce that relationship. If invalid combinations matter, use a representation with controlled creation or a discriminated design appropriate to the codebase. Also distinguish an expected invalid quantity from an unexpected storage failure. Returning quantity for every caught exception would hide defects. Typed results are a communication choice; they do not remove the need for clear failure categories and real exception handling at suitable boundaries.
Practice one: isolate behavior
First test one and five and predict accepted for each, then zero and six and predict quantity for each. Hint: use adjacent boundary cases to distinguish inclusive comparisons from a plausible strict-comparison defect. Inspect both Accepted and Code rather than asserting only the formatted string. Keep expected combinations literal and independent of EvaluateQuantity. Then change only the display label and verify that the structured decision remains unchanged, demonstrating which part of the behavior belongs to presentation rather than to acceptance policy.
Practice two: test a boundary
For another exercise, replace arbitrary public result construction with named accepted and rejected creation paths that cannot produce contradictory fields. Hint: define the valid states before choosing syntax; renaming the same unrestricted constructor would not enforce them. Add a parsing adapter with separate malformed-text and typed-invalid-quantity codes, keeping the domain range unchanged. Test one input for each stage. Do not catch all exceptions and translate them into the ordinary rejection code merely to make every failure resemble a known user mistake.
Review the evidence
Review a validation flow by identifying its input representation, typed rule, result states, stable error meaning, and presentation mapping. Choose exceptions or explicit results according to the actual contract rather than a universal slogan. The sample verifies a small range decision and a separate formatting boundary; its external graph verifies responsibility placement. Neither guarantees that every caller constructs valid results or that infrastructure cannot fail. Make those additional concerns explicit so a convenient result type communicates useful evidence instead of becoming a wrapper that conceals ambiguity.
Validation results across 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 static class UnitRule { public static bool Valid(int units) => units >= 1 && units <= 5; }
Application/Model.cs
Download file ↗using System;
using System.Collections.Generic;
using System.Linq;
using BoundaryLab.Domain;
namespace BoundaryLab.Application;
public record QuantityResult(bool Accepted, string Code);
public sealed class EvaluateQuantity
{
public QuantityResult Execute(int units) => UnitRule.Valid(units) ? new(true, "accepted") : new(false, "quantity");
}
Infrastructure/Model.cs
Download file ↗using System;
using System.Collections.Generic;
using System.Linq;
using BoundaryLab.Domain;
using BoundaryLab.Application;
namespace BoundaryLab.Infrastructure;
public static class ResultText { public static string Render(string label, QuantityResult result) => label + ":" + result.Code; }
Host/Program.cs
Download file ↗using System;
using System.Collections.Generic;
using System.Linq;
using BoundaryLab.Domain;
using BoundaryLab.Application;
using BoundaryLab.Infrastructure;
var service = new EvaluateQuantity();
Console.WriteLine(ResultText.Render("bad", service.Execute(0)));
Console.WriteLine(ResultText.Render("good", service.Execute(3)));
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: bad:quantity good:accepted 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 service = new EvaluateQuantity();
Console.WriteLine(ResultText.Render("bad", service.Execute(0)));
Console.WriteLine(ResultText.Render("good", service.Execute(3)));
public static class UnitRule { public static bool Valid(int units) => units >= 1 && units <= 5; }
public record QuantityResult(bool Accepted, string Code);
public sealed class EvaluateQuantity
{
public QuantityResult Execute(int units) => UnitRule.Valid(units) ? new(true, "accepted") : new(false, "quantity");
}
public static class ResultText { public static string Render(string label, QuantityResult result) => label + ":" + result.Code; }
C# practice output
bad:quantity good:accepted
Test one and five as accepted boundaries, then zero and six as rejections. A typed result does not replace exception handling for unexpected infrastructure failure.