← Back to Domain-Driven Design

Domain-Driven Design

Modelling

Choosing a useful account of the problem that makes its behaviour and decisions explicit.

Modelling is choosing a useful account of a problem. The model keeps the distinctions needed to make decisions and leaves the rest out. It does not transcribe every noun, field, or process in the real world.

MiniVerine models a saga timeout as data on a message. Time becomes something the bus can inspect and eventually schedule, not a thread sleeping inside the saga.

Make time part of the model

TimeoutAttribute stores hours, minutes, seconds, and milliseconds, then exposes one delay:

public sealed class TimeoutAttribute : Attribute
{
    public int Hours { get; init; }
    public int Minutes { get; init; }
    public int Seconds { get; init; }
    public int Milliseconds { get; init; }

    public TimeSpan Delay =>
        new(0, Hours, Minutes, Seconds, Milliseconds);
}

The attribute does not start a timer. The tests prove that distinction by reading the metadata and comparing its value. Scheduling is still planned elsewhere in MiniVerine.

This model can answer, “How long after this message should another message become due?” It deliberately cannot answer, “Which worker is sleeping?” No worker should be.

Keep only the state that decides something

MiniVerine’s saga base is equally small:

public abstract class Saga
{
    public bool IsCompleted { get; private set; }

    public void MarkCompleted()
    {
        IsCompleted = true;
    }
}

The model keeps the decision that matters to every saga instance: open or complete. It leaves identity to each concrete saga, because different processes use different id types. It leaves persistence and dispatch out because neither changes what completion means.

That omission is part of the model. Adding a repository, queue, and timer to Saga would make the type look more complete while making the idea less precise.

Let behaviour test the language

The words and the behaviour correct each other. “Timeout” could suggest a running countdown. The implementation sharpens it to “delay metadata on a message”. “Completed” could suggest deleting a row. MarkCompleted sharpens it to a state transition; storage decides what happens to the row later.

A model is useful when those distinctions help answer the next question in the work. It should change when they stop helping. This is why modelling continues with the product instead of finishing before implementation starts.

Once the behaviour is explicit, decide which rules must remain true in one change. That is Aggregates.