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.
Hansom 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; }Hours, minutes, seconds, milliseconds are delay parts. They are not a running timer.
public TimeSpan Delay =>
new(0, Hours, Minutes, Seconds, Milliseconds);Delay is data the bus can inspect. No worker should be sleeping inside the saga.
}
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 Hansom.
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
Hansom’s saga base is equally small:
public abstract class Saga
{
public bool IsCompleted { get; private set; }IsCompleted is the only decision this base keeps: open or complete.
public void MarkCompleted()
{
IsCompleted = true;MarkCompleted is a state transition. It is not a delete; storage decides what happens to the row later.
}
}
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.