Skip to content

Property and Field Validation

When a public set is sneakily added to a domain class, immutability breaks. Even if a setter is added to Product.Price, compilation succeeds and existing tests pass since they only read values. The problem only surfaces in production when someone calls product.Price = -100. In this chapter, you will learn how to verify architecture rules on class properties and fields — enforcing rules like public setter prohibition, instance field prohibition, and primitive-type-only constraints through tests.

“Immutability is not sufficient with just getter-only properties and private constructors. It must be continuously verified through architecture tests.”

  1. Required property existence verification

    • Guarantee core domain model properties with RequireProperty(name)
    • Immediately detect if a property is accidentally removed or renamed
  2. Immutability enforcement

    • Prohibit public setters in domain classes with RequireNoPublicSetters()
    • Restrict instance field usage with RequireNoInstanceFields() (backing fields automatically excluded)
  3. Property type restriction

    • Allow only primitive types with RequireOnlyPrimitiveProperties()
    • Extension options to specify additional allowed types
  • Product: Verify Name, Price property existence and public setter prohibition
  • OrderLine: Verify instance field prohibition and primitive type properties
  • ProductViewModel: Example excluded from domain rule scope

Domain classes that ensure immutability through getter-only properties and private constructors.

public sealed class Product
{
public string Name { get; }
public decimal Price { get; }
public int Quantity { get; }
private Product(string name, decimal price, int quantity)
{
Name = name;
Price = price;
Quantity = quantity;
}
public static Product Create(string name, decimal price, int quantity)
=> new(name, price, quantity);
}

Unlike domain classes, ViewModels have public setters. Since this class resides in the ViewModels namespace rather than the Domains namespace, it is not subject to domain rules.

public class ProductViewModel
{
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
public int Quantity { get; set; }
}

Verify that specific properties must exist using RequireProperty.

[Fact]
public void Product_ShouldHave_NameAndPriceProperties()
{
ArchRuleDefinition
.Classes()
.That()
.ResideInNamespace("PropertyAndFieldValidation.Domains")
.And()
.HaveNameEndingWith("Product")
.ValidateAllClasses(Architecture, @class => @class
.RequireProperty("Name")
.RequireProperty("Price"),
verbose: true)
.ThrowIfAnyFailures("Product Property Rule");
}

Enforce immutability by prohibiting public setters in domain classes.

[Fact]
public void DomainClasses_ShouldNotHave_PublicSetters()
{
ArchRuleDefinition
.Classes()
.That()
.ResideInNamespace("PropertyAndFieldValidation.Domains")
.ValidateAllClasses(Architecture, @class => @class
.RequireNoPublicSetters(),
verbose: true)
.ThrowIfAnyFailures("No Public Setter Rule");
}

Prohibit instance field usage in domain classes. Backing fields automatically generated by the compiler are excluded.

[Fact]
public void DomainClasses_ShouldNotHave_InstanceFields()
{
ArchRuleDefinition
.Classes()
.That()
.ResideInNamespace("PropertyAndFieldValidation.Domains")
.ValidateAllClasses(Architecture, @class => @class
.RequireNoInstanceFields(),
verbose: true)
.ThrowIfAnyFailures("No Instance Field Rule");
}

Verify that domain class properties use only primitive types (string, int, decimal, double, etc.).

[Fact]
public void DomainClasses_ShouldHave_OnlyPrimitiveProperties()
{
ArchRuleDefinition
.Classes()
.That()
.ResideInNamespace("PropertyAndFieldValidation.Domains")
.ValidateAllClasses(Architecture, @class => @class
.RequireOnlyPrimitiveProperties(),
verbose: true)
.ThrowIfAnyFailures("Primitive Property Rule");
}

The following table summarizes the property/field verification APIs and the design principles they protect.

APIDesign Principle ProtectedMeaning When Violated
RequireProperty(name)Domain model completenessCore property is missing
RequireNoPublicSetters()ImmutabilityState can be changed externally
RequireNoInstanceFields()EncapsulationState exposed through fields instead of properties
RequireOnlyPrimitiveProperties(additionalAllowed)Type safetyComplex types penetrating the domain
AspectDomain Classes (Domains NS)ViewModel (ViewModels NS)
Public SetterProhibitedAllowed
Instance FieldsProhibitedNo restriction
Property TypesPrimitive types onlyNo restriction
Application MethodAutomatically separated by namespace filterOutside rule scope

Q1: Does RequireNoPublicSetters also prohibit init setters?

Section titled “Q1: Does RequireNoPublicSetters also prohibit init setters?”

A: init setters can only set values during object initialization, so they do not harm immutability. RequireNoPublicSetters() verifies only public set and allows init setters. This is compatible with C# record types and the required init pattern.

Q2: How does RequireNoInstanceFields automatically exclude backing fields?

Section titled “Q2: How does RequireNoInstanceFields automatically exclude backing fields?”

A: The C# compiler automatically generates backing fields with names like <Name>k__BackingField for auto-properties (public string Name { get; }). RequireNoInstanceFields() detects these compiler-generated fields by name pattern and excludes them from verification targets. Only instance fields explicitly declared by developers are reported as violations.

Q3: How are additional allowed types specified in RequireOnlyPrimitiveProperties?

Section titled “Q3: How are additional allowed types specified in RequireOnlyPrimitiveProperties?”

A: Pass the full names of additional allowed types as strings like RequireOnlyPrimitiveProperties("System.DateTime", "System.Guid"). The specified types are allowed in addition to the default primitive types (string, int, decimal, double, bool, etc.).

Q4: Why are domain rules not applied to ViewModels?

Section titled “Q4: Why are domain rules not applied to ViewModels?”

A: ViewModels are data transfer objects for UI binding and require public set for two-way binding. Applying domain class immutability rules to ViewModels would break compatibility with UI frameworks. Using the ResideInNamespace("...Domains") filter to target only the domain namespace naturally achieves this separation.

Q5: Can multiple verifications be combined in a single test?

Section titled “Q5: Can multiple verifications be combined in a single test?”

A: Yes, you can chain them like @class.RequireProperty("Name").RequireNoPublicSetters().RequireNoInstanceFields(). However, it is better to separate tests when their verification purposes differ, because when a test fails, you can immediately identify which rule was violated.


With property and field verification, you can now continuously ensure domain class immutability. The next Part covers advanced techniques including RequireImmutable() with 6-dimension immutability verification, nested classes, interface verification, and custom rule composition.

-> Part 3 Ch 1: Immutability Rules