Excepciones en DDD

11

Estoy aprendiendo DDD y estoy pensando en lanzar excepciones en ciertas situaciones. Entiendo que un objeto no puede entrar en un mal estado, así que aquí las excepciones están bien, pero en muchos ejemplos también se lanzan excepciones, por ejemplo, si intentamos agregar un nuevo usuario con un correo electrónico existente en la base de datos.

public function doIt(UserData $userData)
{
    $user = $this->userRepository->byEmail($userData->email());
    if ($user) {
        throw new UserAlreadyExistsException();
    }

    $this->userRepository->add(
        new User(
            $userData->email(),
            $userData->password()
        )
    );
}

Entonces, si el usuario con este correo electrónico existe, entonces podemos detectar una excepción en el servicio de aplicaciones, pero no debemos controlar el funcionamiento de la aplicación utilizando el bloque try-catch.

¿Cuál es la mejor manera para esto?

yadiv
fuente
1
Tener servicio de dominio (manejadores) lanzando excepciones está bien.
Andy

Respuestas:

20

Comencemos esto con una breve revisión del espacio del problema: uno de los principios fundamentales de DDD es colocar las reglas de negocio lo más cerca posible de los lugares donde deben hacerse cumplir. Este es un concepto extremadamente importante porque hace que su sistema sea más "coherente". Mover las reglas "hacia arriba" es generalmente un signo de un modelo anémico; donde los objetos son solo bolsas de datos y las reglas se inyectan con esos datos para que se apliquen.

Un modelo anémico puede tener mucho sentido para los desarrolladores que recién comienzan con DDD. Crea un Usermodelo y EmailMustBeUnqiueRulese le inyecta la información necesaria para validar el correo electrónico. Sencillo. Elegante. La cuestión es que este "tipo" de pensamiento es fundamentalmente de naturaleza procesal. No DDD Lo que termina sucediendo es que te queda un módulo con docenas de Rulesenvoltorios y encapsulados, pero están completamente desprovistos de contexto hasta el punto en que ya no se pueden cambiar porque no está claro cuándo / dónde se aplican. ¿Tiene sentido? Se puede ser auto-evidente que EmailMustBeUnqiueRulese aplicará sobre la creación de una User, pero ¿qué pasa UserIsInGoodStandingRule?. Lento pero seguro, la granularización de extraer elRulesfuera de su contexto te deja con un sistema que es difícil de entender (y por lo tanto no se puede cambiar). Las reglas deben encapsularse solo cuando el procesamiento / ejecución real es tan detallado que su modelo comienza a perder el foco.

Ahora pase a su pregunta específica: el problema con tener el Service/ CommandHandlerthrow the Exceptiones que su lógica de negocios está comenzando a filtrarse ("hacia arriba") fuera de su dominio. ¿Por qué su Service/ CommandHandlernecesidad de saber que un correo electrónico debe ser único? La capa de servicio de la aplicación se usa generalmente para la coordinación en lugar de la implementación. La razón de esto puede ilustrarse simplemente si agregamos un ChangeEmailmétodo / comando a su sistema. Ahora AMBOS métodos / manejadores de comandos necesitarían incluir su verificación única. Aquí es donde un desarrollador puede verse tentado a "extraer" un EmailMustBeUniqueRule. Como se explicó anteriormente, no queremos seguir esa ruta.

Un poco de conocimiento adicional puede llevarnos a una respuesta más DDD. La singularidad de un correo electrónico es una invariante que debe aplicarse en una colección de Userobjetos. ¿Hay un concepto en su dominio que represente una "colección de Userobjetos"? Creo que probablemente puedas ver a dónde voy aquí.

Para este caso particular (y muchos más que implican hacer cumplir invariantes en las colecciones), el mejor lugar para implementar esta lógica será en su Repository. Esto es especialmente conveniente porque Repositorytambién "conoce" la infraestructura adicional necesaria para ejecutar este tipo de validación (el almacén de datos). En su caso, colocaría esta verificación en el addmétodo. Esto tiene sentido ¿verdad? Conceptualmente, es este método el que realmente agrega Usera su sistema. El almacén de datos es un detalle de implementación.

tobogán lateral
fuente
¿Puedo preguntar a qué te refieres Moving rules "upwards" is generally a sign of an anemic model? ¿Medios ascendentes a la capa de aplicación? o al modelo de dominio?
Anyname Donotcare el
1
"Hacia arriba" significa hacia su capa de aplicación (piense en una arquitectura en capas). Ya mencioné esto anteriormente, pero la razón por la cual mover las reglas hacia arriba es una señal de un modelo anémico es que representa la separación de datos y comportamiento de una manera que anula el propósito de tener un modelo de dominio central. Si la validación de un correo electrónico se encuentra en un módulo diferente al envío de un correo electrónico, su Emailmodelo debe ser una bolsa de datos que se inspecciona para detectar invariantes antes de enviarlo. Ver: softwareengineering.stackexchange.com/questions/372338/…
king-side-slide
2
Mover la lógica del dominio al repositorio está mal. debe aplicar la regla de dominio agregando raíz en la capa de dominio.
Arash
1
La validación de @Arash Set es complicada y, a menudo, no funciona bien con DDD. Te queda elegir elegir validar que algún proceso funcionará antes de intentar ese proceso (agregar un User) o aceptar que este tipo específico de invariante no se encapsulará perfectamente en tu dominio. El primero representa una separación de datos y comportamiento que resulta en un paradigma de procedimiento. Esto es menos que ideal. La validación debería existir con datos, no alrededor de datos. Además, ¿qué beneficio hay para crear un servicio de dominio que solo sirve para verificar esta regla? Ostensiblemente, este servicio ...
King-Side-Slide el
1
@Arash combina tu Repository, Usery esta invariante y, por lo tanto, no logra la visión purista que estás presionando de todos modos. Las implementaciones de repositorio son parte del dominio y, por lo tanto, pueden recibir ciertas responsabilidades.
King-side-slide el
0

Puede imponer la validación en cada capa de su aplicación. Dónde imponer qué reglas dependen de su aplicación.

Por ejemplo, las entidades implementan métodos que representan reglas comerciales mientras que los casos de uso implementan reglas específicas para su aplicación.

Si está creando un servicio de correo electrónico como Gmail, se podría argumentar que la regla "los usuarios deben tener una dirección de correo electrónico única" es una regla comercial.

Si está creando un proceso de registro para un nuevo sistema CRM, esta regla probablemente se implemente mejor en un caso de uso, ya que es probable que esta regla también sea parte de su caso de uso. Además, también podría ser más fácil de probar, ya que puede bloquear fácilmente las llamadas al repositorio en sus pruebas de casos de uso.

Para seguridad adicional, también puede aplicar esta regla en su repositorio.

el novato
fuente