← ClaudeAtlas

domain-modeling-and-primitiveslisted

Review C# domain models - primitive obsession, value objects, records vs classes, enums vs polymorphism, invariant enforcement in constructors, and anemic model smells. Use when reviewing domain entities, value types, or business-logic placement.
Sarmkadan/dotnet-senior-skills · ★ 0 · AI & Automation · score 72
Install: claude install-skill Sarmkadan/dotnet-senior-skills
# Domain Modeling and Primitives ## Primitive obsession: the threshold A raw primitive is fine until it acquires rules or gets confused with its neighbors. The triggers for wrapping: - Two same-typed parameters that must not be swapped: `Transfer(Guid fromAccountId, Guid toAccountId)` compiles happily with the arguments reversed. `AccountId` as a wrapped type turns the swap into a compile error. - A validation rule enforced in more than one place: if `email` is regex-checked in the controller, the service, and the importer, the string should have been an `Email` type validating once, in its constructor. - Money as `decimal`: adding EUR to USD compiles. `Money(decimal Amount, Currency Currency)` with an addition operator that throws on currency mismatch does not. ```csharp // WRONG: every consumer re-validates or trusts blindly public void Register(string email) { ... } // RIGHT: an Email that exists is valid; the rule lives in one place public readonly record struct Email { public string Value { get; } public Email(string value) { if (!MailAddress.TryCreate(value, out _)) throw new ArgumentException($"Invalid email: '{value}'", nameof(value)); Value = value.Trim().ToLowerInvariant(); } public override string ToString() => Value; } ``` Do not wrap everything: a `PageNumber` type over an `int` used in one method is ceremony. The threshold is rules or confusability, not typing zeal. For EF mapping, value objects bind via `HasCo