Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Who should handle the conditions in complex queries, the data mapper or the service layer?

this question did a very good job of clearing my confusion a bit on the matter, but I'm having a hard time finding reliable sources on what the exact limitations of the service layer should be.

For this example, assume we're dealing with books, and we want to get books by author. The BookDataMapper could have a generic get() method that accepts condition(s) such as the book's unique identifier, author name, etc. This implementation is fairly trivial (logically), but what if we want to have multiple conditions that require a more complex query?

Lets say we want to get all book written by a certain author under a specific publisher. We could expand the BookDataMapper->get() method to parse out multiple conditions, or we could write a new method such as BookDataMapper->getByAuthorAndPublisher().

Is it preferable to have the service layer call these [more specific] methods directly, or have the conditions parsed before calling the more generic BookDataMapper->get() method with multiple conditions passed? In the latter scenario, the service layer would do more of the logical "heavy lifting," leaving the data mapper fairly simple. The former option would reduce the service layer almost entirely to just a middle-man, leaving conditional logic to the data mapper in methods like BookDataMapper->getByAuthorAndPublisher().

The obvious concern with letting the service layer parse the conditions is that some of the domain logic leaks out of the data mapper. (this is explained in the linked question here. However if the service layer was to handle the conditions, the logic wouldn't make it out of the model layer; The controller would call $book_service->getByAuthorAndPublisher() regardless.

like image 529
orourkek Avatar asked Aug 13 '12 21:08

orourkek


2 Answers

The data mapper pattern only tells you, what it is supposed to do, not how it should be implemented.
Therefore all the answers in this topic should be treated as subjective, because they reflect each authors personal preferences.

I usually try to keep mapper's interface as simple as possible:

  • fetch(), retrieves data in the domain object or collection,
  • save(), saves (updates existing or inserts new) the domain object or collection
  • remove(), deletes the domain object or collection from storage medium

I keep the condition in the domain object itself:

$user = new User;
$user->setName( 'Jedediah' );

$mapper = new UserMapper;
$mapper->fetch( $user );

if ( $user->getFlags() > 5  )
{
    $user->setStatus( User::STATUS_LOCKED );
}

$mapper->save( $user );

This way you can have multiple conditions for the retrieval, while keeping the interface clean.

The downside to this would be that you need a public method for retrieving information from the domain object to have such fetch() method, but you will need it anyway to perform save().

There is no real way to implement the "Tell Don't Ask" rule-of-thumb for mapper and domain object interaction.

As for "How to make sure that you really need to save the domain object?", which might occur to you, it has been covered here, with extensive code examples and some useful bits in the comments.

Update

I case if you expect to deal with groups of objects, you should be dealing with different structures, instead of simple Domain Objects.

$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 );

In this case the collection is glorified array, with ability to accept different parameters, which then are used as conditions for the mapper. It also should let the mapper to acquired list changed domain objects from this collection, when mapper is attempting to store the collection.

The mapper in this case should be capable of building (or using preset ones) queries with all the possible conditions (as a developer you will know all of those conditions, therefore you do not need to make it work with infinite set of conditions) and update or create new entries for all the unsaved domain object, that collection contains.

Note: In some aspect you could say, that the mapper are related to builder/factory patterns. The goal is different, but the approach to solving the problems is very similar.

like image 127
tereško Avatar answered Oct 16 '22 12:10

tereško


I normally prefer this to be more concrete, like:

BookDataMapper->getByAuthorAndPublisher($author, $publisher)

That is because I do not need to re-invent SQL. The database is better for that and the data-mapper takes care here that the rest of the application does not need to know anything about how things are stored or queried in concrete either.

If you make that more dynamic you can easily have the tendency to offer too much functionality via the interface. Not good.

And take a look at your application. You'll see that there is not that much going to be queried differently. For the main part of data that are normally about 5-10 routines if at all. It's written much faster than to even think about some dynamic system that actually would belong into it's own layer anyway.

like image 5
hakre Avatar answered Oct 16 '22 14:10

hakre