Eu li sobre DDD há dias e preciso de ajuda com este design de exemplo. Todas as regras do DDD me deixam muito confuso sobre como eu devo criar alguma coisa quando objetos de domínio não têm permissão para mostrar métodos para a camada de aplicativo; onde mais para orquestrar o comportamento? Repositórios não podem ser injetados em entidades e as próprias entidades devem, portanto, trabalhar no estado. Então, uma entidade precisa saber mais alguma coisa do domínio, mas também não é permitido que outros objetos de entidade sejam injetados? Algumas dessas coisas fazem sentido para mim, mas outras não. Ainda não encontrei bons exemplos de como criar um recurso completo, pois todos os exemplos são sobre pedidos e produtos, repetindo os outros exemplos várias vezes. Aprendo melhor lendo exemplos e tentei criar um recurso usando as informações que adquiri sobre DDD até agora.
Preciso da sua ajuda para apontar o que eu faço de errado e como corrigi-lo, de preferência com código como "Eu não recomendo fazer X e Y" é muito difícil de entender em um contexto em que tudo já está vagamente definido. Se eu não puder injetar uma entidade em outra, seria mais fácil ver como fazê-lo corretamente.
No meu exemplo, existem usuários e moderadores. Um moderador pode banir usuários, mas com uma regra de negócios: apenas 3 por dia. Fiz uma tentativa de configurar um diagrama de classes para mostrar os relacionamentos (código abaixo):
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);
A entidade Usuário deve ter um 'is_banned'
campo que possa ser verificado $user->isBanned();
? Como remover uma proibição? Eu não faço ideia.
fonte
Respostas:
Essa pergunta é um tanto subjetiva e leva a mais uma discussão do que uma resposta direta, que, como alguém já apontou - não é apropriada para o formato de fluxo de pilha. Dito isto, acho que você só precisa de alguns exemplos codificados sobre como lidar com problemas, então vou tentar, apenas para lhe dar algumas idéias.
A primeira coisa que eu diria é:
Isso simplesmente não é verdade - eu estaria interessado em saber de onde você leu isso. A camada de aplicativo é o orquestrador entre interface do usuário, infraestrutura e domínio e, portanto, obviamente precisa invocar métodos em entidades de domínio.
Escrevi um exemplo codificado de como resolveria seu problema. Peço desculpas por estar em C #, mas não conheço PHP - espero que você ainda obtenha a essência da perspectiva da estrutura.
Talvez eu não devesse ter feito, mas modifiquei levemente os objetos do seu domínio. Não pude deixar de sentir que era um pouco falho, pois o conceito de 'BannedUser' existe no sistema, mesmo que a proibição tenha expirado.
Para começar, aqui está o serviço de aplicativo - é assim que a interface do usuário chamaria:
Bem direto. Você busca o moderador que bane, o usuário que o moderador deseja banir e chama o método 'Ban' no usuário, passando pelo moderador. Isso modificará o estado do moderador e do usuário (explicado abaixo), que precisa persistir nos repositórios correspondentes.
A classe Usuário:
A invariante para um usuário é que ele não pode executar determinadas ações quando banido, portanto, precisamos identificar se um usuário está banido no momento. Para isso, o usuário mantém uma lista de proibições de veiculação que foram emitidas por moderadores. O método IsBanned () verifica todas as proibições de veiculação que ainda precisam expirar. Quando o método Ban () é chamado, ele recebe um moderador como parâmetro. Isso então pede ao moderador que proíba:
O invariante para o moderador é que ele só pode emitir 3 proibições por dia. Assim, quando o método IssueBan é chamado, ele verifica se o moderador não possui 3 proibições emitidas com a data de hoje em sua lista de proibições emitidas. Em seguida, ele adiciona a proibição recém-emitida à sua lista e a devolve.
Subjetivo, e tenho certeza que alguém discordará da abordagem, mas espero que lhe dê uma idéia ou como ela pode se encaixar.
fonte
Mova toda a sua lógica que altera o estado para uma camada de serviço (ex: ModeratorService) que conhece Entidades e Repositórios.
fonte