He leído sobre DDD desde hace días y necesito ayuda con este diseño de muestra. Todas las reglas de DDD me confunden mucho sobre cómo se supone que debo construir algo cuando los objetos de dominio no pueden mostrar métodos en la capa de aplicación; ¿Dónde más orquestar el comportamiento? Los repositorios no pueden inyectarse en entidades y las entidades mismas deben trabajar en estado. ¿Entonces una entidad necesita saber algo más del dominio, pero tampoco se permite inyectar otros objetos de entidad? Algunas de estas cosas tienen sentido para mí, pero otras no. Todavía tengo que encontrar buenos ejemplos de cómo construir una función completa, ya que cada ejemplo trata de Pedidos y Productos, repitiendo los otros ejemplos una y otra vez. Aprendo mejor leyendo ejemplos y he intentado crear una función utilizando la información que he obtenido sobre DDD hasta ahora.
Necesito su ayuda para señalar lo que hago mal y cómo solucionarlo, lo más preferiblemente con el código, ya que "No recomendaría hacer X e Y" es muy difícil de entender en un contexto donde todo ya está vagamente definido. Si no puedo inyectar una entidad en otra, sería más fácil ver cómo hacerlo correctamente.
En mi ejemplo hay usuarios y moderadores. Un moderador puede prohibir a los usuarios, pero con una regla comercial: solo 3 por día. Intenté configurar un diagrama de clase para mostrar las relaciones (código a continuación):
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);
¿La titularidad del usuario debe tener un 'is_banned'
campo con el que se pueda verificar $user->isBanned();
? ¿Cómo eliminar una prohibición? No tengo idea.
fuente
Respuestas:
Esta pregunta es algo subjetiva y lleva a una discusión más que a una respuesta directa, que, como alguien más ha señalado, no es apropiada para el formato stackoverflow. Dicho esto, creo que solo necesita algunos ejemplos codificados sobre cómo abordar los problemas, así que lo intentaré, solo para darle algunas ideas.
Lo primero que diría es:
Eso simplemente no es cierto. Me interesaría saber de dónde has leído esto. La capa de aplicación es el orquestador entre UI, Infraestructura y Dominio y, por lo tanto, obviamente necesita invocar métodos en entidades de dominio.
He escrito un ejemplo codificado de cómo abordaría su problema. Pido disculpas porque está en C #, pero no conozco PHP; espero que aún consigas la esencia desde una perspectiva de estructura.
Quizás no debería haberlo hecho, pero he modificado ligeramente sus objetos de dominio. No pude evitar sentir que era un poco defectuoso, ya que el concepto de 'Usuario Prohibido' existe en el sistema, incluso si la prohibición ha expirado.
Para empezar, aquí está el servicio de aplicación: esto es lo que la UI llamaría:
Muy claro. Busca al moderador que hace la prohibición, al usuario que el moderador desea prohibir y llama al método 'Prohibir' al usuario, pasando al moderador. Esto modificará el estado tanto del moderador como del usuario (explicado a continuación), que luego debe persistir a través de sus repositorios correspondientes.
La clase de usuario:
Lo invariable para un usuario es que no pueden realizar ciertas acciones cuando están prohibidos, por lo que debemos poder identificar si un usuario está actualmente prohibido. Para lograr esto, el usuario mantiene una lista de prohibiciones de publicación emitidas por moderadores. El método IsBanned () verifica cualquier prohibición de publicación que aún no haya caducado. Cuando se llama al método Ban (), recibe un moderador como parámetro. Esto luego le pide al moderador que emita una prohibición:
La invariante para el moderador es que solo puede emitir 3 prohibiciones por día. Por lo tanto, cuando se llama al método IssueBan, verifica que el moderador no tenga 3 prohibiciones emitidas con la fecha de hoy en su lista de prohibiciones emitidas. Luego agrega la prohibición recién emitida a su lista y la devuelve.
Subjetivo, y estoy seguro de que alguien no estará de acuerdo con el enfoque, pero espero que te dé una idea o cómo puede encajar.
fuente
Mueva toda su lógica que altera el estado a una capa de servicio (por ejemplo: ModeratorService) que conoce tanto Entidades como Repositorios.
fuente