Skip to main content
๐Ÿ“œ WAYPOINT LESSON

The Engine's Domain: Entities, Invariants, Validation

โญโญ intermediateโณ 26 min read๐Ÿ“ Lesson 153 of 265

A capstone starts from the data: records that carry invariants, validated once at the boundary so every layer below can trust them.

What you are building

A Task Management Engine โ€” the core of a project tracker:

  • Users (Id, Name, Role โˆˆ Admin/Member),
  • Projects (Id, Name, OwnerId),
  • Tasks (Id, ProjectId, Title, AssigneeId?, Priority โˆˆ Low/Medium/High, Status โˆˆ Todo/InProgress/Done),
  • operations wrapped in a service layer with validation and authorization, backed by a repository, reporting via LINQ, state persisted as JSON.

Domain first, and make invalid states unrepresentable

Model choices as enums, not strings โ€” the compiler then rejects half the bug space:

public enum Priority { Low, Medium, High }
public enum Status { Todo, InProgress, Done }

Validation lives at the boundary (the service methods that accept input), one place, not smeared through every consumer:

  • title: required, non-empty after trimming, โ‰ค 120 chars,
  • priority/status: must be a defined enum value (Enum.IsDefined โ€” a cast (Priority)99 compiles and is garbage),
  • assignee must exist and belong to the same system the task belongs to.

A failed check throws ArgumentException (input problem) โ€” the service's contract. InvalidOperationException is reserved for state problems (completing an already-done task).

Records for entities

Entities are immutable-with-replacement here: changing a task produces a new record in the store. Records give value equality and with โ€” the engine's mutations become visible, comparable state transitions:

public sealed record Task(
    int Id, int ProjectId, string Title,
    int? AssigneeId, Priority Priority, Status Status);

var opened = t with { Status = Status.InProgress, AssigneeId = uid };

IDs and the store

The repository owns ID allocation (monotonic counter). Services never invent IDs. Everything else in the engine is built on this foundation.