Czytałem o DDD od kilku dni i potrzebuję pomocy w tym przykładowym projekcie. Wszystkie reguły DDD powodują, że jestem bardzo zdezorientowany tym, jak mam cokolwiek zbudować, gdy obiekty domeny nie mogą pokazywać metod w warstwie aplikacji; gdzie jeszcze koordynować zachowanie? Repozytoria nie mogą być wstrzykiwane do podmiotów, a same podmioty muszą zatem działać na stan. Zatem encja musi wiedzieć coś jeszcze z domeny, ale inne obiekty encji też nie mogą być wstrzykiwane? Niektóre z tych rzeczy mają dla mnie sens, ale niektóre nie. Muszę znaleźć dobre przykłady, jak zbudować całą funkcję, ponieważ każdy przykład dotyczy zamówień i produktów, powtarzając inne przykłady w kółko. Uczę się najlepiej, czytając przykłady i próbowałem zbudować funkcję, korzystając z informacji uzyskanych do tej pory o DDD.
Potrzebuję twojej pomocy, aby wskazać, co robię źle i jak to naprawić, najlepiej za pomocą kodu, ponieważ „nie polecam robienia X i Y” jest bardzo trudne do zrozumienia w kontekście, w którym wszystko jest już niejasno zdefiniowane. Jeśli nie mogę wstrzyknąć bytu innemu, łatwiej byłoby zobaczyć, jak to zrobić poprawnie.
W moim przykładzie są użytkownicy i moderatorzy. Moderator może banować użytkowników, ale z regułą biznesową: tylko 3 dziennie. Podjąłem próbę skonfigurowania diagramu klas, aby pokazać relacje (kod poniżej):
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);
Czy uprawnienie użytkownika powinno zawierać 'is_banned'
pole, które można sprawdzić $user->isBanned();
? Jak usunąć ban? Nie mam pojęcia.
źródło
Odpowiedzi:
To pytanie jest nieco subiektywne i prowadzi raczej do dyskusji niż do bezpośredniej odpowiedzi, która, jak zauważył ktoś inny, nie jest odpowiednia dla formatu przepływu stosu. To powiedziawszy, myślę, że potrzebujesz tylko zakodowanych przykładów radzenia sobie z problemami, więc dam ci szansę, aby dać ci kilka pomysłów.
Pierwszą rzeczą, którą powiem to:
To po prostu nieprawda - chciałbym wiedzieć, skąd to przeczytałeś. Warstwa aplikacji jest koordynatorem między interfejsem użytkownika, infrastrukturą i domeną, dlatego oczywiście musi wywoływać metody na jednostkach domeny.
Napisałem zakodowany przykład rozwiązania tego problemu. Przepraszam, że jest w języku C #, ale nie znam PHP - mam nadzieję, że nadal dostaniesz sedno z perspektywy struktury.
Być może nie powinienem był tego robić, ale nieco zmodyfikowałem twoje obiekty domeny. Nie mogłem się oprzeć wrażeniu, że był nieco wadliwy, ponieważ w systemie istnieje koncepcja „BannedUser”, nawet jeśli ban wygasł.
Na początek oto usługa aplikacji - tak właśnie zadzwoni interfejs użytkownika:
Całkiem prosto. Pobierasz moderatora wykonującego blokadę, użytkownika, którego moderator chce zablokować, i wywołujesz metodę „Banowania” dla użytkownika, przekazując moderatorowi. Spowoduje to modyfikację stanu zarówno moderatora, jak i użytkownika (wyjaśnione poniżej), które następnie muszą przetrwać za pośrednictwem odpowiednich repozytoriów.
Klasa użytkownika:
Niezmiennością dla użytkownika jest to, że nie może wykonać pewnych działań po zbanowaniu, dlatego musimy być w stanie stwierdzić, czy użytkownik jest obecnie zbanowany. Aby to osiągnąć, użytkownik utrzymuje listę zakazów udostępniania wydanych przez moderatorów. Metoda IsBanned () sprawdza wszelkie zakazy udostępniania, które jeszcze nie wygasły. Gdy wywoływana jest metoda Ban (), odbiera moderator jako parametr. Następnie prosi moderatora o wydanie zakazu:
Niezmiennikiem dla moderatora jest to, że może on wydać tylko 3 zakazy dziennie. Zatem po wywołaniu metody IssueBan sprawdza, czy moderator nie ma 3 wydanych zakazów z dzisiejszą datą na liście wydanych zakazów. Następnie dodaje nowo wydany zakaz do swojej listy i zwraca go.
Subiektywnie i jestem pewien, że ktoś nie zgodzi się z tym podejściem, ale mam nadzieję, że daje to pomysł lub sposób, w jaki można go ze sobą połączyć.
źródło
Przenieś całą logikę, która zmienia stan na warstwę usługi (np. ModeratorService), która zna zarówno Encje, jak i Repozytoria.
źródło