Entities - Sofka-XT/ddd-generic-java GitHub Wiki
El segundo concepto importante en la DDD táctica y el hermano de los objetos de valor es la entidad. Una entidad es un objeto cuya identidad es importante. Para poder determinar la identidad de una entidad, cada entidad tiene una identificación única que se asigna cuando se crea la entidad y permanece sin cambios durante la vida útil de la entidad.
Dos entidades del mismo tipo y con el mismo ID se consideran la misma entidad incluso si todas las demás propiedades difieren. Asimismo, dos entidades del mismo tipo y con las mismas propiedades pero diferentes ID se consideran entidades diferentes, al igual que dos personas con el mismo nombre no se consideran iguales.
A diferencia de los objetos de valor, las entidades son mutables. Sin embargo, eso no significa que deba crear métodos de establecimiento para cada propiedad. Intente modelar todas las operaciones de alteración de estado como verbos que correspondan a operaciones comerciales. Un colocador solo le dirá qué propiedad está cambiando, pero no por qué. Por ejemplo: supongamos que tiene una entidad EmploymentContract y tiene una propiedad endDate. Los contratos de trabajo pueden terminar porque, para empezar, fueron sólo temporales, debido a una transferencia interna de una sucursal de la empresa a otra, porque el empleado renunció o porque el empleador lo despidió. En todos estos casos, endDate se modifica, pero por razones muy diferentes. Además, puede haber otras acciones que deban tomarse dependiendo de la razón por la que se terminó el contrato. Un método terminateContract (reason, finalDay) ya dice mucho más que un método setEndDate (finalDay).
Dicho esto, los setters todavía tienen su lugar en DDD. En el ejemplo anterior, podría haber un método setEndDate (..) privado que se asegura de que la fecha de finalización sea posterior a la fecha de inicio antes de configurarla. Este setter sería utilizado por los otros métodos de entidad pero no expuesto al mundo exterior. Para datos maestros y de referencia y para propiedades que describen una entidad sin alterar su estado comercial, tiene más sentido usar establecedores que intentar modificar las operaciones en verbos. Un método llamado setDescription (..) es posiblemente más legible que describe (..).
Voy a ilustrar esto con otro ejemplo. Digamos que tiene una entidad Persona que representa, bueno, una persona. La persona tiene una propiedad firstName y lastName. Ahora, si esto fuera solo una libreta de direcciones simple, permitiría que el usuario cambiara esta información según sea necesario y podría usar los establecedores setFirstName(..) y setLastName(..). Sin embargo, si está creando un registro oficial de ciudadanos del gobierno, cambiar un nombre es más complicado. Podría terminar con algo como changeName (nombre, apellido, motivo, fecha efectiva). Una vez más, el contexto lo es todo.
Una nota sobre los captadores Los métodos getter se introdujeron en Java como parte de la especificación JavaBean. Esta especificación no estaba presente en la primera versión de Java, por lo que puede encontrar algunos métodos que no se ajustan a ella en la API estándar de Java (por ejemplo, String.length() en contraposición a String.getLength()).
Para mí, personalmente, me gustaría ver soporte para propiedades reales en Java. Aunque podrían estar usando captadores y definidores detrás de escena, me gustaría acceder al valor de una propiedad de la misma manera que si fuera un campo ordinario: mycontact.phoneNumber. No podemos hacer eso en Java todavía, pero podemos acercarnos bastante dejando de lado el sufijo get de nuestros getters. En mi opinión, esto hace que el código sea más fluido, especialmente si necesita profundizar en una jerarquía de objetos para buscar algo: mycontact.address(). StreetNumber ().
Sin embargo, también hay una desventaja de deshacerse de los captadores, y ese es el soporte de herramientas. Todos los IDE de Java y muchas bibliotecas se basan en el estándar JavaBean, lo que significa que puede terminar escribiendo manualmente código que podría haberse generado automáticamente para usted y agregando anotaciones que podrían haberse evitado al ceñirse a las convenciones.
No siempre es fácil saber si modelar algo como un objeto de valor o como una entidad. El mismo concepto del mundo real exacto se puede modelar como una entidad en un contexto y como un objeto de valor en otro. Tomemos la dirección postal como ejemplo.
Si está creando un sistema de facturación, la dirección postal es solo algo que imprime en la factura. No importa qué instancia de objeto se utilice siempre que el texto de la factura sea correcto. En este caso, la dirección postal es un objeto de valor.
Si está construyendo un sistema para un servicio público, necesita saber exactamente qué línea de gas o qué línea de electricidad entra en un apartamento determinado. En este caso, la dirección postal es una entidad e incluso puede dividirse en entidades más pequeñas, como un edificio o un apartamento.
Los objetos de valor son más fáciles de trabajar porque son inmutables y pequeños. Por lo tanto, debe apuntar a un diseño con pocas entidades y muchos objetos de valor.
Una entidad Member en Java podría verse así (el código no está probado y algunas implementaciones de métodos se han omitido para mayor claridad):
ublic class Person extends Entity<PersonId> {
private PersonName name;
private LocalDate birthDate;
private StreetAddress address;
private EmailAddress email;
private PhoneNumber phoneNumber;
public Person(PersonId personId, PersonName name, LocalDate birthDate, StreetAddress address, EmailAddress email, PhoneNumber phoneNumber){
super(personId);
this.name = name;
this.birthDate = birthDate;
this.address = address;
this.email = email;
this.phoneNumber = phoneNumber;
}
private Person(PersonId personId){
super(personId);
}
public static Person form(PersonId personId, PersonName name,LocalDate birthDate, StreetAddress address, EmailAddress email, PhoneNumber phoneNumber) {
var person = new Person(personId);
person.name = name;
person.birthDate = birthDate;
person.address = address;
person.email = email;
person.phoneNumber = phoneNumber;
return person;
}
public void changeName(PersonName name, String reason) {
Objects.requireNonNull(name);
this.name = name;
}
public PersonName name() {
return name;
}
public LocalDate birthDate() {
return birthDate;
}
public StreetAddress address() {
return address;
}
public EmailAddress email() {
return email;
}
public PhoneNumber phoneNumber() {
return phoneNumber;
}
}
Algunas cosas a tener en cuenta en este ejemplo:
-
Un objeto de valor, PersonId, se utiliza para el ID de entidad. Podríamos haber usado un UUID, una cadena o un largo también, pero un objeto de valor nos dice inmediatamente que se trata de una identificación que identifica a una Persona en particular.
-
Además del ID de entidad, esta entidad también usa muchos otros objetos de valor: PersonName, LocalDate (sí, este también es un objeto de valor aunque es parte de la API estándar de Java), StreetAddress, EmailAddress y PhoneNumber.
-
En lugar de usar un establecedor para cambiar el nombre, usamos un método comercial que también almacena el cambio en un registro de eventos, junto con la razón por la cual se cambió el nombre.
-
Hay un captador para recuperar el historial de cambios de nombre.
-
equals y hashCode solo verifican el ID de la entidad.
Diseño basado en dominios y CRUD Ahora hemos llegado a un punto en el que es apropiado abordar la pregunta sobre DDD y CRUD. CRUD significa Crear, Recuperar, Actualizar y Eliminar y también es un patrón de interfaz de usuario común en aplicaciones empresariales:
-
La vista principal consiste en una cuadrícula, tal vez con filtrado y ordenación, donde puede buscar entidades (recuperar).
-
En la vista principal, hay un botón para crear nuevas entidades. Al hacer clic en el botón, aparece un formulario vacío y cuando se envía el formulario, la nueva entidad aparece en la cuadrícula (crear).
-
En la vista principal, hay un botón para editar la entidad seleccionada. Al hacer clic en el botón, aparece un formulario que contiene los datos de la entidad. Cuando se envía el formulario, la entidad se actualiza con la nueva información (actualización).
-
En la vista principal, hay un botón para eliminar la entidad seleccionada. Al hacer clic en el botón se elimina la entidad de la cuadrícula (eliminar).
Este patrón ciertamente tiene su lugar, pero debería ser la excepción y no la norma en una aplicación dirigida por dominios. La razón es la siguiente: una aplicación CRUD solo se trata de estructurar, mostrar y editar datos. Normalmente no es compatible con el proceso empresarial subyacente. Cuando un usuario ingresa algo en el sistema, cambia algo o elimina algo, hay una razón comercial detrás de esa decisión. ¿Quizás el cambio está ocurriendo como parte de un proceso comercial más amplio? En un sistema CRUD, el motivo de un cambio se pierde y el proceso empresarial está en la cabeza del usuario.
Una verdadera interfaz de usuario impulsada por el dominio se basará en acciones que son en sí mismas parte del lenguaje ubicuo (y por lo tanto del modelo de dominio) y los procesos comerciales están integrados en el sistema en lugar de en las cabezas de los usuarios. Esto, a su vez, conduce a un sistema más robusto, pero posiblemente menos flexible, que una aplicación CRUD pura. Voy a ilustrar esta diferencia con un ejemplo caricaturesco:
La empresa A tiene un sistema basado en dominios para la gestión de empleados, mientras que la empresa B tiene un enfoque basado en CRUD. Un empleado renuncia en ambas empresas. Sucede lo siguiente:
Empresa A:
-
El gerente busca el registro del empleado en el sistema.
-
El gerente selecciona la acción 'Terminar contrato de trabajo'.
-
El sistema solicita la fecha de terminación y el motivo.
-
El gerente ingresa la información necesaria y hace clic en 'Terminar contrato'.
-
El sistema actualiza automáticamente los registros de los empleados, revoca las credenciales de usuario del empleado y la clave de la oficina electrónica y envía una notificación al sistema de nómina.
Empresa B:
-
El gerente busca el registro del empleado en el sistema.
-
El gerente marca la casilla de verificación 'Contrato terminado' e ingresa la fecha de terminación, luego hace clic en 'Guardar'.
-
El administrador inicia sesión en el sistema de administración de usuarios, busca la cuenta del usuario, marca la casilla de verificación "Deshabilitado" y hace clic en "Guardar".
-
El administrador inicia sesión en el sistema de administración de claves de la oficina, busca la clave del usuario, marca la casilla de verificación "Deshabilitado" y hace clic en "Guardar".
-
El gerente envía un correo electrónico al departamento de nómina notificándoles que el empleado ha renunciado.
Las conclusiones clave son las siguientes: no todas las aplicaciones son adecuadas para el diseño basado en dominios, y una aplicación basada en dominios no solo tiene un backend basado en dominios, sino también una interfaz de usuario basada en dominios.