Ich habe seit Tagen über DDD gelesen und brauche Hilfe bei diesem Beispieldesign. Alle DDD-Regeln verwirren mich sehr, wie ich überhaupt etwas erstellen soll, wenn Domänenobjekte der Anwendungsebene keine Methoden anzeigen dürfen. Wo sonst kann man Verhalten orchestrieren? Repositorys dürfen nicht in Entitäten injiziert werden, und die Entitäten selbst müssen daher am Status arbeiten. Dann muss eine Entität etwas anderes aus der Domäne wissen, aber andere Entitätsobjekte dürfen auch nicht injiziert werden. Einige dieser Dinge ergeben für mich einen Sinn, andere nicht. Ich habe noch keine guten Beispiele dafür gefunden, wie man ein ganzes Feature erstellt, da es in jedem Beispiel um Bestellungen und Produkte geht und die anderen Beispiele immer wieder wiederholt werden. Ich lerne am besten anhand von Beispielen und habe versucht, anhand der Informationen, die ich bisher über DDD erhalten habe, ein Feature zu erstellen.
Ich brauche Ihre Hilfe, um darauf hinzuweisen, was ich falsch mache und wie ich es beheben kann. Am besten mit Code, da "Ich würde es nicht empfehlen, X und Y zu machen" in einem Kontext, in dem alles nur vage definiert ist, sehr schwer zu verstehen ist. Wenn ich eine Entität nicht in eine andere injizieren kann, ist es einfacher zu sehen, wie es richtig gemacht wird.
In meinem Beispiel gibt es Benutzer und Moderatoren. Ein Moderator kann Benutzer sperren, jedoch mit einer Geschäftsregel: nur 3 pro Tag. Ich habe versucht, ein Klassendiagramm zu erstellen, um die Beziehungen anzuzeigen (Code unten):
interface iUser
{
public function getUserId();
public function getUsername();
}
class User implements iUser
{
protected $_id;
protected $_username;
public function __construct(UserId $user_id, Username $username)
{
$this->_id = $user_id;
$this->_username = $username;
}
public function getUserId()
{
return $this->_id;
}
public function getUsername()
{
return $this->_username;
}
}
class Moderator extends User
{
protected $_ban_count;
protected $_last_ban_date;
public function __construct(UserBanCount $ban_count, SimpleDate $last_ban_date)
{
$this->_ban_count = $ban_count;
$this->_last_ban_date = $last_ban_date;
}
public function banUser(iUser &$user, iBannedUser &$banned_user)
{
if (! $this->_isAllowedToBan()) {
throw new DomainException('You are not allowed to ban more users today.');
}
if (date('d.m.Y') != $this->_last_ban_date->getValue()) {
$this->_ban_count = 0;
}
$this->_ban_count++;
$date_banned = date('d.m.Y');
$expiration_date = date('d.m.Y', strtotime('+1 week'));
$banned_user->add($user->getUserId(), new SimpleDate($date_banned), new SimpleDate($expiration_date));
}
protected function _isAllowedToBan()
{
if ($this->_ban_count >= 3 AND date('d.m.Y') == $this->_last_ban_date->getValue()) {
return false;
}
return true;
}
}
interface iBannedUser
{
public function add(UserId $user_id, SimpleDate $date_banned, SimpleDate $expiration_date);
public function remove();
}
class BannedUser implements iBannedUser
{
protected $_user_id;
protected $_date_banned;
protected $_expiration_date;
public function __construct(UserId $user_id, SimpleDate $date_banned, SimpleDate $expiration_date)
{
$this->_user_id = $user_id;
$this->_date_banned = $date_banned;
$this->_expiration_date = $expiration_date;
}
public function add(UserId $user_id, SimpleDate $date_banned, SimpleDate $expiration_date)
{
$this->_user_id = $user_id;
$this->_date_banned = $date_banned;
$this->_expiration_date = $expiration_date;
}
public function remove()
{
$this->_user_id = '';
$this->_date_banned = '';
$this->_expiration_date = '';
}
}
// Gathers objects
$user_repo = new UserRepository();
$evil_user = $user_repo->findById(123);
$moderator_repo = new ModeratorRepository();
$moderator = $moderator_repo->findById(1337);
$banned_user_factory = new BannedUserFactory();
$banned_user = $banned_user_factory->build();
// Performs ban
$moderator->banUser($evil_user, $banned_user);
// Saves objects to database
$user_repo->store($evil_user);
$moderator_repo->store($moderator);
$banned_user_repo = new BannedUserRepository();
$banned_user_repo->store($banned_user);
Sollte die Benutzerberechtigung ein 'is_banned'
Feld haben, mit dem geprüft werden kann $user->isBanned();
? Wie hebe ich ein Verbot auf? Ich habe keine Ahnung.
quelle
Antworten:
Diese Frage ist etwas subjektiv und führt eher zu einer Diskussion als zu einer direkten Antwort, die, wie jemand anderes darauf hingewiesen hat, für das Stackoverflow-Format nicht geeignet ist. Das heißt, ich denke, Sie brauchen nur ein paar codierte Beispiele, wie Sie Probleme angehen können, also werde ich es versuchen, nur um Ihnen ein paar Ideen zu geben.
Das erste, was ich sagen würde, ist:
Das stimmt einfach nicht - ich würde mich interessieren, woher Sie das gelesen haben. Die Anwendungsschicht ist der Koordinator zwischen Benutzeroberfläche, Infrastruktur und Domäne und muss daher offensichtlich Methoden für Domänenentitäten aufrufen.
Ich habe ein codiertes Beispiel dafür geschrieben, wie ich Ihr Problem angehen würde. Ich entschuldige mich, dass es in C # ist, aber ich kenne PHP nicht - hoffentlich bekommen Sie das Wesentliche aus der Strukturperspektive heraus.
Vielleicht hätte ich das nicht tun sollen, aber ich habe Ihre Domain-Objekte leicht modifiziert. Ich konnte nicht anders, als den Eindruck zu erwecken, dass das Konzept eines 'BannedUser' im System vorhanden ist, auch wenn das Verbot abgelaufen ist.
Hier ist zunächst der Anwendungsservice - so würde die Benutzeroberfläche lauten:
Ziemlich einfach. Sie holen den Moderator, der die Sperre vornimmt, den Benutzer, den der Moderator sperren möchte, und rufen die 'Ban'-Methode für den Benutzer auf und übergeben den Moderator. Dies ändert den Status sowohl des Moderators als auch des Benutzers (siehe unten), der dann über die entsprechenden Repositorys beibehalten werden muss.
Die Benutzerklasse:
Die Invariante für einen Benutzer ist, dass er bestimmte Aktionen nicht ausführen kann, wenn er gesperrt ist. Daher müssen wir in der Lage sein, zu identifizieren, ob ein Benutzer derzeit gesperrt ist. Zu diesem Zweck führt der Benutzer eine Liste der von Moderatoren verhängten Serving-Verbote. Die IsBanned () -Methode prüft, ob Serving-Bans noch nicht abgelaufen sind. Wenn die Ban () -Methode aufgerufen wird, erhält sie einen Moderator als Parameter. Dies fordert dann den Moderator auf, ein Verbot zu erlassen:
Die Invariante für den Moderator ist, dass er nur 3 Sperren pro Tag ausstellen kann. Wenn die IssueBan-Methode aufgerufen wird, wird daher überprüft, ob der Moderator in seiner Liste der erteilten Verbote keine drei gesperrten Verbote mit dem heutigen Datum hat. Es fügt dann das neu ausgestellte Verbot zu seiner Liste hinzu und gibt es zurück.
Subjektiv, und ich bin sicher, dass jemand mit dem Ansatz nicht einverstanden ist, aber hoffentlich gibt es Ihnen eine Idee oder wie es zusammenpassen kann.
quelle
Verschieben Sie Ihre gesamte Logik, die den Status ändert, in eine Serviceebene (z. B. ModeratorService), die sowohl Entitäten als auch Repositorys kennt.
quelle