Este no es un artículo sobre DDD
Hace poco publiqué en twitter dos mensajes sobre la relación entre Domain Driven Design y la fase de análisis o diseño que siempre nos recalcaron en la universidad:
Yo no soy experto en DDD -lo escuché por primera vez en septiembre de 2019-, pero después de estudiarlo un poco vi que muchos de los conceptos ya los conocía. Empezaron a encajar en vacíos que tenía en mi código y caí en cuenta que había cometido muchos errores en la fase de diseño. El principal de ellos: no dedicar suficiente tiempo a identificar todos contextos del problema, es decir, no dividir para vencer... o dividir mal. Esa fase duró casi un mes; no quiero imaginar con qué hubiera terminado de haberla ignorado totalmente.
Quiero hablar de contexto y no entrar en tecnicismos de Bounded Context, Aggregates y el resto del lenguaje que nos propone la metodología. Contexto en el sentido de que una entidad puede ser distinta dependiendo del requerimiento específico en el que estamos trabajando.
¿Qué me alertó? primero, crear una clase que representa la misma entidad pero con menos atributos -hasta allí todo bien- y ponerle un nombre distinto -¡OUCH!-. Este ejemplo puede parecerte familiar:
Supongamos un perfil de usuario con muchos datos de contacto. Es probable que la base de datos tenga una tabla
Si has leido artículos recientes de este blog, sabrás que me gusta usar propiedades de sólo lectura. Aquí uso
Sabemos de programación orientada a objetos, así que tenemos una clase
Piensen por un instante en qué sucede con la clase
Qué tal esta invocación:
Cuando pudo ser:
O qué tal cargar las tareas directamente y mostrarlas en la interfaz:
Cuando nos insistieron un montón en respetar las jerarquías:
Allí es cuando debemos parar, tomar lápiz y papel y volver a diseñar. Esta vez recordando que hay distintos contextos, que se vale tener varias clases
Si la aplicación es pequeña, vale el esfuerzo hacer el refactoring. Si no, es mejor esperar e ir reparando conforme las nuevas funcionalidades lo requieran. Créanme que en muy poco tiempo habrán cubierto todo el código sin tomar riesgos innecesarios.
Seguro hay casos sencillos en donde sirve una entidad como
DDD me gusta mucho y creo que todos debemos estudiar -al menos- sus fundamentos, pero debo decir que no es una novedad. Allí no hay nada distinto a lo que aprendí entre 2004 y 2009 en la Universidad. Mis profesores de algoritmos siempre enseñaron lo que debían enseñar: conceptos que nunca cambian. Me acordé mucho de ellos mientras escribía este post.
DDD es la parte de la programación orientada a objetos en la que más énfasis nos hicieron durante la Universidad, pero que olvidamos por andar pensando en entregar rápido. Ahora toca estudiarla otra vez, para arreglar el despelote que también nos advirtieron que íbamos a armar.
En esa época tenía un nombre menos fancy: le llamaban análisis. Con variantes como “divide y vencerás”, “primero resuelve el problema y luego escribe el código”, “identifica el contexto”, etc.
Quiero hablar de contexto y no entrar en tecnicismos de Bounded Context, Aggregates y el resto del lenguaje que nos propone la metodología. Contexto en el sentido de que una entidad puede ser distinta dependiendo del requerimiento específico en el que estamos trabajando.
¿Qué me alertó? primero, crear una clase que representa la misma entidad pero con menos atributos -hasta allí todo bien- y ponerle un nombre distinto -¡OUCH!-. Este ejemplo puede parecerte familiar:
Supongamos un perfil de usuario con muchos datos de contacto. Es probable que la base de datos tenga una tabla
profile
con columnas userId
, name
, lastName
, email
, address
, phoneNumber
y bio
. Seguramente existe la clase Profile
que tiene todos los atributos/propiedades:public class Profile
{
public Guid UserId { get; set; }
public string Name { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Address { get; set; }
public string PhoneNumber { get; set; }
public string Bio { get; set; }
}
{ get; set; }
por simplicidad.Sabemos de programación orientada a objetos, así que tenemos una clase
User
muy parecida a esta:public class User
{
public Guid Id { get; }
public Profile Profile { get; }
private IList<Task> _tasks;
public IList<Task> Tasks => _tasks.AsReadOnly();
public User(Guid id, Profile profile)
{
Id = id;
Profile = profile;
_tasks = new List<Task>();
}
public User(Guid id, Profile profile, IEnumerable<Task> tasks)
: this(id, profile)
{
_tasks.AddRange(tasks);
}
public void AddTask(Task t)
{
_tasks.Add(t);
}
}
Pero conforme la aplicación crezca irán apareciendo páginas en donde debemos mostrar información básica del usuario, por ejemplo, el nombre y el correo electrónico. Como sabemos que cargar todo el perfil sería un desperdicio de recursos, podríamos darle vía libre a nuestros némesis: SimpleProfile
, ShortProfile
, ProfileDetails
, DetailedProfile
, ExtendedProfile
, ... Como sea que se llamen. Y Profile
es sólo una de las quince entidades que tenemos.Piensen por un instante en qué sucede con la clase
User
, que tiene una referencia a Profile
. Sin darnos cuenta nos olvidamos de ella y destruimos la orientación a objetos de nuestro software. ¿O cuál de todas las versiones de Profile
usamos allí?. A estas alturas ya tenemos servicios que retornan trozos de información que de alguna forma se enlazan con la interfaz gráfica. La lógica de negocios quedó en esos servicios y no en las entidades, como nos enseñaron. Los métodos entre capas ya no reciben objetos sino parámetros independientes según las distintas funcionalidades. ¿Les parece familiar?.Qué tal esta invocación:
_ = await _store.CreateProfile(
id: Guid.NewGuid(),
name: profile.Name,
lastname: profile.Lastname,
email: profile.Email,
address: profile.Address,
phoneNumber: profile.PhoneNumber,
bio: profile.Bio
);
_ = await _store.SaveProfile(profile);
// o dependiendo del diseño:
_ = await _store.SaveProfile(user.Profile);
var tasks = await.GetTasksForUser(id);
var model = tasks.Select(t => MapTaskToViewModel(t));
var user = await GetUser(id);
var model = user.Tasks.Select(t => TaskViewModel.FromTask(t));
Profile
y que algunas tendrán lógica de negocios y otras serán de sólo lectura. Que es válido retornar o recibir como parámetros instancias de Profile
o de otras clases en distintas capas de nuestro software, siempre que estemos en el mismo contexto.Si la aplicación es pequeña, vale el esfuerzo hacer el refactoring. Si no, es mejor esperar e ir reparando conforme las nuevas funcionalidades lo requieran. Créanme que en muy poco tiempo habrán cubierto todo el código sin tomar riesgos innecesarios.
Seguro hay casos sencillos en donde sirve una entidad como
SimpleProfile
. Se me ocurre el de mostrar el nombre de usuario y la foto de perfil en el menú. Pero hay que saberlo manejar y ser conscientes de que es una libertad puntual que nos estamos dando como desarrolladores; una excepción y no la regla.DDD me gusta mucho y creo que todos debemos estudiar -al menos- sus fundamentos, pero debo decir que no es una novedad. Allí no hay nada distinto a lo que aprendí entre 2004 y 2009 en la Universidad. Mis profesores de algoritmos siempre enseñaron lo que debían enseñar: conceptos que nunca cambian. Me acordé mucho de ellos mientras escribía este post.
Comentarios
Publicar un comentario