php - ¿Quién debería manejar las condiciones en las consultas complejas, el asignador de datos o la capa de servicio?
domain-driven-design datamapper (2)
Esta pregunta hizo un muy buen trabajo al aclarar un poco mi confusión al respecto, pero me cuesta mucho encontrar fuentes confiables sobre cuáles deberían ser las limitaciones exactas de la capa de servicio.
Para este ejemplo, supongamos que estamos tratando con libros, y queremos obtener libros por autor. El BookDataMapper
podría tener un método genérico de get()
que acepte condiciones como el identificador único del libro, el nombre del autor, etc. Esta implementación es bastante trivial (lógicamente), pero ¿qué sucede si queremos tener varias condiciones que requieran más? consulta compleja?
Digamos que queremos obtener todos los libros escritos por un determinado autor bajo un editor específico. Podríamos expandir el BookDataMapper->get()
para analizar múltiples condiciones, o podríamos escribir un nuevo método como BookDataMapper->getByAuthorAndPublisher()
.
¿Es preferible que la capa de servicio llame directamente a estos métodos [más específicos], o que se BookDataMapper->get()
las condiciones antes de llamar al método más genérico BookDataMapper->get()
con múltiples condiciones aprobadas? En este último escenario, la capa de servicio haría más del "levantamiento pesado" lógico, dejando al asignador de datos bastante simple. La primera opción reduciría la capa de servicio casi por completo a un intermediario, dejando la lógica condicional al asignador de datos en métodos como BookDataMapper->getByAuthorAndPublisher()
.
La preocupación obvia por permitir que la capa de servicio analice las condiciones es que parte de la lógica del dominio se sale del mapeador de datos. (esto se explica en la pregunta enlazada here . Sin embargo, si la capa de servicio debía manejar las condiciones, la lógica no lograría salir de la capa modelo; el controlador llamaría a $book_service->getByAuthorAndPublisher()
independientemente.
El patrón del asignador de datos solo le indica qué se supone que debe hacer, no cómo debe implementarse.
Por lo tanto, todas las respuestas en este tema deben considerarse subjetivas, ya que reflejan las preferencias personales de cada autor.
Generalmente trato de mantener la interfaz del asignador lo más simple posible:
-
fetch()
, recupera datos en el objeto o colección de dominio, -
save()
, guarda (actualiza existente o inserta nuevo) el objeto o colección del dominio -
remove()
, elimina el objeto de dominio o la colección del medio de almacenamiento
Mantengo la condición en el propio objeto de dominio:
$user = new User;
$user->setName( ''Jedediah'' );
$mapper = new UserMapper;
$mapper->fetch( $user );
if ( $user->getFlags() > 5 )
{
$user->setStatus( User::STATUS_LOCKED );
}
$mapper->save( $user );
De esta manera, puede tener múltiples condiciones para la recuperación, mientras mantiene la interfaz limpia.
El inconveniente de esto sería que necesita un método público para recuperar información del objeto de dominio para tener dicho método fetch()
, pero lo necesitará de todos modos para realizar save()
.
No hay una manera real de implementar la regla de oro "Diga no preguntar" para la interacción de objetos de dominio y asignador.
En cuanto a "¿Cómo asegurarse de que realmente necesita guardar el objeto de dominio?" , lo que podría ocurrir, se ha cubierto here , con extensos ejemplos de código y algunos bits útiles en los comentarios.
Actualizar
Creo que si espera tratar con grupos de objetos, debería tratar con estructuras diferentes, en lugar de con objetos de dominio simples.
$category = new Category;
$category->setTitle( ''privacy'' );
$list = new ArticleCollection;
$list->setCondition( $category );
$list->setDateRange( mktime( 0, 0, 0, 12, 9, 2001) );
// it would make sense, if unset second value for range of dates
// would default to NOW() in mapper
$mapper = new ArticleCollectionMapper;
$mapper->fetch( $list );
foreach ( $list as $article )
{
$article->setFlag( Article::STATUS_REMOVED );
}
$mapper->store( $list );
En este caso, la colección es una matriz glorificada, con capacidad para aceptar diferentes parámetros, que luego se utilizan como condiciones para el asignador. También debe permitir que el asignador a la lista adquirida cambie los objetos de dominio de esta colección, cuando el asignador esté intentando almacenar la colección.
En este caso, el asignador debe ser capaz de crear (o usar consultas preestablecidas) con todas las condiciones posibles (como desarrollador, conocerá todas esas condiciones, por lo tanto, no necesita hacer que funcione con un conjunto infinito de condiciones) y actualizar o crear nuevas entradas para todos los objetos de dominio no guardados, que contiene la colección.
Nota: En algún aspecto se podría decir que los asignadores están relacionados con los patrones de fábrica / creador. El objetivo es diferente, pero el enfoque para resolver los problemas es muy similar.
Normalmente prefiero que esto sea más concreto, como:
BookDataMapper->getByAuthorAndPublisher($author, $publisher)
Eso es porque no necesito reinventar el SQL. La base de datos es mejor para eso y el mapeador de datos se encarga de que el resto de la aplicación no necesite saber nada sobre cómo se almacenan o consultan las cosas en concreto.
Si lo hace más dinámico, puede tener fácilmente la tendencia de ofrecer demasiada funcionalidad a través de la interfaz. No está bien.
Y echa un vistazo a tu aplicación. Verás que no hay mucho que se va a consultar de manera diferente. Para la parte principal de los datos que normalmente son de 5 a 10 rutinas, en todo caso. Está escrito mucho más rápido que incluso pensar en algún sistema dinámico que, de todos modos, pertenezca a su propia capa.