Capítulo 2. Lo que aprendí de DDD. Value Objects
Los Value Objects nos permiten encapsular elementos de nuestro dominio que representan un valor por sí mismos, como un email, una fecha o una puntuación.
Una de las ideas que más me han gustado del enfoque DDD son los “Value Objects”, los cuales nos permiten encapsular los elementos de nuestra aplicación que representan un valor por sí mismos.
La idea en la que se basan es realmente sencilla y de hecho cobra gran sentido si estamos trabajando con un lenguaje que admita Programación Orientada a Objetos. Presta atención.
Imagina una clase User
con el siguiente constructor:
public function __constructor(string $email, string $fullname)
{
...
}
Sí, está claro que $email
representa una dirección de correcto electrónico, pero, ¿quién nos asegura que es un email válido? Declarando el argumento como un string
plano estamos perdiendo mucho contenido semántico de ese argumento, es decir, su significado dentro de nuestra aplicación.
Sin embargo, si tuviéramos creada una clase Email
a la que perteneciesen todos los objetos de tipo email que creamos en nuestra aplicación:
class Email
{
private string $value; public function __construct(string $value)
{
$this->ensureIsValid($value);
$this->value = $value;
} public function value(): string
{
return $this->value;
} private function ensureIsValid(string $value): void
{
// checks and throw exception if it is not valid
}
}
El constructor de la clase User
adquiriría verdadero significado cuando lo leamos:
public function __constructor(Email $email, string $fullname)
{
...
}
Y no tendríamos que hacerla responsable de asegurar que el email es válido, sino que dicha comprobación se realizaría donde más sentido tiene: al construir un objeto de la clase Email.
Para mí esta es la mayor ventaja de la que nos proveen los Value Objects: acercar lo máximo posible las responsabilidades y la integridad de nuestros objetos al lugar donde el sentido común nos diría que deben realizarse.
¿No os da la sensación de que ésta es la verdadera programación orientada a objetos?
Citas famosas
Ahora dejemos a gente que sabe bastante más que yo hablar de los “Value Objects”.
Ward Cunningham
Examples of value objects are things like numbers, dates, monies and strings. Usually, they are small objects which are used quite widely. Their identity is based on their state rather than on their object identity. This way, you can have multiple copies of the same conceptual value object.
Martin Fowler
A small Object such as a Money or the date range object. Their key property is that they follow value semantics rather than reference semantics. You can usually tell them because their notion of equality isn’t based on identity, instead two Value Objects are equal if all their fields are equal. Although all fields are equal, you don’t need to compare all fields if a subset is unique — for example currency codes for currency objects are enough to test equality. A general heuristic is that Value Objects should be entirely immutable.
Características
Las definiciones anteriores ilustran muy bien las principales características de los Value Objects.
Dos “value objects” son iguales si poseen el mismo valor, independientemente de que sean “objetos” distintos en memoria.
Los “value objects” son preferiblemente inmutables, es decir, si queremos modificar su valor crearemos uno nuevo con el resultado de la operación en vez de modificar el valor interno del “value object” que ya tenemos.
Los “value objects” son reemplazables, en el sentido de que podemos reemplazar un value object por otro sin temor a que se produzcan efectos secundarios.
Los “value objects” pueden tener varias propiedades. Un ejemplo sencillo es el “value object”
Price
, compuesto de “cantidad” y “moneda” o el “value object”RangeDate
compuesto de las propiedades “desde” y “hasta”.
En definitiva, los “value objects” nos permiten modelar los valores de nuestra aplicación que no representan una entidad por sí mismos.
Diferencia con las entidades
Los “value objects” se distinguen de las entidades en un aspecto fundamental: sólo representan un tipo de valor dentro nuestra aplicación pero no tienen identidad por sí mismos, es decir, no representan un concepto como puede ser un “Usuario” o un “Artículo”.
Por esa razón, los value objects no son almacenados en tablas individuales de nuestra base de datos, sino que sus datos pertenecen a las entidades que los usan, los intercambian y realizan operaciones sobre ellos.
Me gusta mucho el ejemplo que menciona la propia Wikipedia para explicar la diferencia:
Example: Most airlines distinguish each seat uniquely on every flight. Each seat is an entity in this context. However, Southwest Airlines, EasyJet and Ryanair do not distinguish between every seat; all seats are the same. In this context, a seat is actually a value object.
Te dejo otro en español:
Un teléfono es un ejemplo muy tontorrón de “value object” en casi cualquier aplicación. Sin embargo, si imaginamos un sistema de gestión de una empresa telefónica, los teléfonos tendrán entidad propia, pues gozarán de sus propias propiedades como “está Asignado” o “fecha de vencimiento”.
¿Preocupado por el consumo de memoria?
Igual te estás preguntando si esto de los “value objects” no empeorará el rendimiento de la aplicación al crear objetos extra para representar strings. La respuesta es no, el impacto es mínimo, pues realmente la huella en memoria la sitúa el propio valor y no el value object por sí mismo, que actúa como un mero contenedor.
Persistiendo en base de datos
Y terminamos con la parte más interesante. ¿Cómo trabajar con ellos cuando necesitamos persistir información en base de datos?
A día de hoy sigo trabajando con Symfony para el backend, por lo que esta respuesta gira en torno al ORM Doctrine. Si usas otro framework / ORM te recomiendo que busques en su documentación porque es posible que exista una alternativa parecida a la que voy a describir.
Desde la versión 2.5 de Doctrine es posible emplear lo que se conocen com Embeddables, los cuales son perfectos para trabajar con Value Objects. Volvamos al ejemplo de la clase User
y el value object Email
. Para integrar los “embeddables” de Doctrine lo único que tenemos que hacer es lo siguiente:
use Doctrine\ORM\Mapping as ORM;/**
* @ORM\Embeddable
*/
class Email
{
/** @ORM\Column(type="string", name="email", length=255) */
private string $value; public function __construct(string $value)
{
$this->ensureIsValid($value);
$this->value = $value;
} ...
}class User
{
/**
* @ORM\Embedded(class="App\Entity\Email", columnPrefix=false))
*/
private Email $email; ...
}
Gracias al par de anotaciones Embeddable / Embedded podremos emplear nuestros Value Objects para persistirlos en base de datos mediante Doctrine.
Como veis, es posible añadir un prefijo a la columna creada en base de datos en el caso de que, por ejemplo, estemos usando el mismo “valor” varias veces en nuestra entidad (por ejemplo, “teléfono móvil” y “teléfono de casa”).
En este vídeo de mi curso gratuito de Symfony también hablo de ellos y así podéis verlo en directo:
🙏🏻 Gracias a Garaje de Ideas
Los amigos de Garaje de ideas patrocinan Latte and Code y buscan talento: http://bit.ly/garaje-tech-talento
Conclusiones
Y hasta aquí el artículo de hoy. Creo que el concepto detrás de los “value objects” es muy muy potente y que representa a la perfección la filosofía de la programación orientada a objetos y de la separación de conceptos.
¡Hasta el siguiente capítulo!
Excellent, thanks!
Muy bien explicado y muy útiles cuando usas DDD. El problema que tuve en su momento con Value Objects, es a la hora de crear los tests, que se llegaban a enrevesar mucho por ellos. Podrías cubrir un poco esa parte de testing, por favor?