Non mi considero un esperto di DDD ma, come architetto di soluzioni, cerco di applicare le migliori pratiche ogni volta che è possibile. So che c'è un sacco di discussioni sui pro e contro dello stile "no" (pubblico) setter in DDD e posso vedere entrambi i lati dell'argomento. Il mio problema è che lavoro su una squadra con una grande diversità di competenze, conoscenze ed esperienza, il che significa che non posso fidarmi del fatto che ogni sviluppatore farà le cose nel modo "giusto". Ad esempio, se i nostri oggetti di dominio sono progettati in modo che le modifiche allo stato interno dell'oggetto siano eseguite da un metodo ma forniscano setter di proprietà pubbliche, qualcuno imposterà inevitabilmente la proprietà invece di chiamare il metodo. Utilizza questo esempio:
public class MyClass
{
public Boolean IsPublished
{
get { return PublishDate != null; }
}
public DateTime? PublishDate { get; set; }
public void Publish()
{
if (IsPublished)
throw new InvalidOperationException("Already published.");
PublishDate = DateTime.Today;
Raise(new PublishedEvent());
}
}
La mia soluzione è stata quella di rendere privati i setter di proprietà, il che è possibile perché l'ORM che stiamo usando per idratare gli oggetti usa la riflessione in modo che sia in grado di accedere ai setter privati. Tuttavia, questo presenta un problema quando si tenta di scrivere test unitari. Ad esempio, quando voglio scrivere un test unitario che verifica il requisito che non possiamo ri-pubblicare, devo indicare che l'oggetto è già stato pubblicato. Posso certamente farlo chiamando Publish due volte, ma il mio test presuppone che Publish sia implementato correttamente per la prima chiamata. Sembra un po 'puzzolente.
Rendiamo lo scenario un po 'più reale con il seguente codice:
public class Document
{
public Document(String title)
{
if (String.IsNullOrWhiteSpace(title))
throw new ArgumentException("title");
Title = title;
}
public String ApprovedBy { get; private set; }
public DateTime? ApprovedOn { get; private set; }
public Boolean IsApproved { get; private set; }
public Boolean IsPublished { get; private set; }
public String PublishedBy { get; private set; }
public DateTime? PublishedOn { get; private set; }
public String Title { get; private set; }
public void Approve(String by)
{
if (IsApproved)
throw new InvalidOperationException("Already approved.");
ApprovedBy = by;
ApprovedOn = DateTime.Today;
IsApproved = true;
Raise(new ApprovedEvent(Title));
}
public void Publish(String by)
{
if (IsPublished)
throw new InvalidOperationException("Already published.");
if (!IsApproved)
throw new InvalidOperationException("Cannot publish until approved.");
PublishedBy = by;
PublishedOn = DateTime.Today;
IsPublished = true;
Raise(new PublishedEvent(Title));
}
}
Voglio scrivere test unitari che verificano:
- Non posso pubblicare se il documento non è stato approvato
- Non riesco a pubblicare nuovamente un documento
- Una volta pubblicati, i valori PublishedBy e PublishedOn sono corretti set
- Una volta pubblicato, l'evento PublishedEvent viene sollevato
Senza l'accesso ai setter, non posso mettere l'oggetto nello stato necessario per eseguire i test. L'apertura dell'accesso ai setter sconfigge lo scopo di impedire l'accesso.
In che modo (hai) hai risolto (d) questo problema?