Angenommen, wir haben ein Aufgabenprotokollierungssystem. Wenn eine Aufgabe protokolliert wird, gibt der Benutzer eine Kategorie an und die Aufgabe hat standardmäßig den Status "Ausstehend". Angenommen, in diesem Fall müssen Kategorie und Status als Entitäten implementiert werden. Normalerweise würde ich das machen:
Anwendungsschicht:
public class TaskService
{
//...
public void Add(Guid categoryId, string description)
{
var category = _categoryRepository.GetById(categoryId);
var status = _statusRepository.GetById(Constants.Status.OutstandingId);
var task = Task.Create(category, status, description);
_taskRepository.Save(task);
}
}
Entität:
public class Task
{
//...
public static void Create(Category category, Status status, string description)
{
return new Task
{
Category = category,
Status = status,
Description = descrtiption
};
}
}
Ich mache das so, weil mir durchweg gesagt wird, dass Entities nicht auf die Repositories zugreifen sollen, aber es würde für mich viel sinnvoller sein, wenn ich das mache:
Entität:
public class Task
{
//...
public static void Create(Category category, string description)
{
return new Task
{
Category = category,
Status = _statusRepository.GetById(Constants.Status.OutstandingId),
Description = descrtiption
};
}
}
Das Status-Repository ist ohnehin von Abhängigkeiten abhängig, es besteht also keine wirkliche Abhängigkeit, und dies erscheint mir eher so, als ob es die Domäne ist, die die Entscheidung trifft, für die eine Aufgabe standardmäßig ausstehend ist. Die vorherige Version scheint der Antragsteller zu sein, der diese Entscheidung trifft. Warum sind Repository-Verträge oft in der Domain, wenn dies keine Möglichkeit sein sollte?
Hier ist ein extremeres Beispiel, hier entscheidet die Domain über die Dringlichkeit:
Entität:
public class Task
{
//...
public static void Create(Category category, string description)
{
var task = new Task
{
Category = category,
Status = _statusRepository.GetById(Constants.Status.OutstandingId),
Description = descrtiption
};
if(someCondition)
{
if(someValue > anotherValue)
{
task.Urgency = _urgencyRepository.GetById
(Constants.Urgency.UrgentId);
}
else
{
task.Urgency = _urgencyRepository.GetById
(Constants.Urgency.SemiUrgentId);
}
}
else
{
task.Urgency = _urgencyRepository.GetById
(Constants.Urgency.NotId);
}
return task;
}
}
Es gibt keine Möglichkeit, alle möglichen Versionen von Dringlichkeit zu übergeben, und es gibt keine Möglichkeit, diese Geschäftslogik in der Anwendungsebene zu berechnen. Dies ist also mit Sicherheit die am besten geeignete Methode.
Ist dies also ein triftiger Grund für den Zugriff auf Repositorys von der Domäne aus?
EDIT: Dies könnte auch bei nicht statischen Methoden der Fall sein:
public class Task
{
//...
public void Update(Category category, string description)
{
Category = category,
Status = _statusRepository.GetById(Constants.Status.OutstandingId),
Description = descrtiption
if(someCondition)
{
if(someValue > anotherValue)
{
Urgency = _urgencyRepository.GetById
(Constants.Urgency.UrgentId);
}
else
{
Urgency = _urgencyRepository.GetById
(Constants.Urgency.SemiUrgentId);
}
}
else
{
Urgency = _urgencyRepository.GetById
(Constants.Urgency.NotId);
}
return task;
}
}
quelle
Status = _statusRepository.GetById(Constants.Status.OutstandingId)
einer Geschäftsregel aufzeigt , die Sie als "Das Unternehmen schreibt den Anfangsstatus aller Aufgaben als herausragend vor" bezeichnen können, und aus diesem Grund Diese Codezeile gehört nicht in ein Repository, dessen einziges Anliegen die Datenverwaltung über CRUD-Operationen ist.Ich weiß nicht, ob es sich bei Ihrem Statusbeispiel um echten Code handelt oder ob es sich hier nur um Demonstrationszwecke handelt, aber es erscheint mir merkwürdig, dass Sie Status als Entität implementieren sollten (ganz zu schweigen von einem aggregierten Stamm), wenn dessen ID eine definierte Konstante ist im Code -
Constants.Status.OutstandingId
. Hält das nicht den Zweck von "dynamischen" Status außer Kraft, von denen Sie so viele in die Datenbank aufnehmen können, wie Sie möchten?Ich würde hinzufügen, dass in Ihrem Fall der Aufbau eines
Task
Objekts (einschließlich der Aufgabe, den richtigen Status aus dem StatusRepository zu erhalten, falls erforderlich) möglicherweise ein Objekt verdient,TaskFactory
anstatt im eigenen Objekt zu bleibenTask
, da es sich um eine nicht triviale Zusammenstellung von Objekten handelt.Aber :
Diese Aussage ist im besten Fall ungenau und zu simpel, im schlimmsten Fall irreführend und gefährlich.
In domänengetriebenen Architekturen ist es allgemein anerkannt, dass eine Entität nicht wissen sollte, wie sie sich selbst speichert - das ist das Prinzip der Persistenz-Ignoranz. Es werden also keine Aufrufe an das Repository gesendet, um sich dem Repository hinzuzufügen. Sollte es wissen, wie (und wann) andere Entitäten zu speichern sind ? Auch hier scheint diese Verantwortung in ein anderes Objekt zu gehören - vielleicht ein Objekt, das den Ausführungskontext und den Gesamtfortschritt des aktuellen Anwendungsfalls kennt, wie ein Application-Layer-Service.
Könnte eine Entität ein Repository verwenden, um eine andere Entität abzurufen ? In 90% der Fälle sollte dies nicht erforderlich sein, da die benötigten Entitäten normalerweise im Bereich ihrer Gesamtheit liegen oder durch Durchqueren anderer Objekte verfügbar sind. Aber es gibt Zeiten, in denen dies nicht der Fall ist. Wenn Sie beispielsweise eine hierarchische Struktur verwenden, müssen Entitäten im Rahmen ihres intrinsischen Verhaltens häufig auf alle ihre Vorfahren, ein bestimmtes Enkelkind usw. zugreifen. Sie haben keinen direkten Bezug zu diesen entfernten Verwandten. Es wäre unpraktisch, diese Verwandten als Parameter der Operation an sie weiterzugeben. Warum also nicht ein Repository verwenden, um sie abzurufen - vorausgesetzt, es handelt sich um aggregierte Wurzeln?
Es gibt noch einige andere Beispiele. Die Sache ist, manchmal gibt es ein Verhalten, das Sie nicht in einen Domain-Service einfügen können, da es perfekt in eine vorhandene Entität zu passen scheint. Dennoch muss diese Entität auf ein Repository zugreifen, um eine Wurzel oder eine Sammlung von Wurzeln zu hydratisieren, die nicht an sie übergeben werden können.
Ein Repository von einer Entität so den Zugriff auf nicht schlecht an sich , kann es aus einer anderen Formen dieses Ergebnis nehmen Vielzahl von Design - Entscheidungen von katastrophalen zu akzeptablen Bereich.
quelle
Dies ist einer der Gründe, warum ich in meiner Domain keine Enums oder reinen Nachschlagetabellen verwende. Dringlichkeit und Status sind beide Zustände, und es gibt eine Logik, die mit einem Zustand verknüpft ist, der direkt zu dem Zustand gehört (z. B. welche Zustände kann ich angesichts meines aktuellen Zustands wechseln). Wenn Sie einen Zustand als reinen Wert aufzeichnen, verlieren Sie auch Informationen darüber, wie lange sich die Aufgabe in einem bestimmten Zustand befand. Ich repräsentiere Status als Klassenhierarchie. (In C #)
Die Implementierung von CompletedTaskStatus wäre ziemlich gleich.
Hier sind einige Dinge zu beachten:
Ich mache die Standardkonstruktoren geschützt. Auf diese Weise kann das Framework es aufrufen, wenn ein Objekt aus der Persistenz gezogen wird (sowohl EntityFramework Code-first als auch NHibernate verwenden Proxys, die von Ihren Domänenobjekten abgeleitet sind, um ihre Magie auszuführen).
Viele der Immobilienmakler sind aus dem gleichen Grund geschützt. Wenn ich das Enddatum eines Intervalls ändern möchte, muss ich die Funktion Interval.End () aufrufen (dies ist Teil von Domain Driven Design und bietet sinnvolle Vorgänge anstelle von anämischen Domänenobjekten.
Ich zeige es hier nicht an, aber der Task würde ebenfalls die Details darüber verbergen, wie er seinen aktuellen Status speichert. Normalerweise habe ich eine geschützte Liste von HistoricalStates, die die Öffentlichkeit abfragen kann, wenn sie interessiert sind. Andernfalls mache ich den aktuellen Status als Getter verfügbar, der HistoricalStates.Single (state.Duration.End == null) abfragt.
Die TransitionTo-Funktion ist von Bedeutung, da sie Logik darüber enthalten kann, welche Zustände für den Übergang gültig sind. Wenn Sie nur eine Aufzählung haben, muss diese Logik woanders liegen.
Hoffentlich hilft dies Ihnen dabei, den DDD-Ansatz ein wenig besser zu verstehen.
quelle
Ich habe versucht, das gleiche Problem für einige Zeit zu lösen, entschied ich, dass ich Task.UpdateTask () so aufrufen kann, obwohl ich es lieber domänenspezifisch wäre, in Ihrem Fall würde ich es Task.ChangeCategory nennen (...) um auf eine Aktion hinzuweisen und nicht nur auf CRUD.
Wie auch immer, ich habe dein Problem ausprobiert und mir das ausgedacht ... nimm meinen Kuchen und iss ihn auch. Die Idee ist, dass Aktionen auf der Entität stattfinden, ohne jedoch alle Abhängigkeiten einzuschleusen. Stattdessen wird mit statischen Methoden gearbeitet, damit sie auf den Status der Entität zugreifen können. Die Fabrik fasst alles zusammen und hat normalerweise alles, was sie braucht, um die Arbeit zu erledigen, die das Unternehmen tun muss. Der Client-Code sieht jetzt sauber und klar aus, und Ihre Entität ist von keiner Repository-Injection abhängig.
quelle