Domain-Driven Design
> >
RepositoriesAndAutomatedPersistence

Wed Jan 14, 2004 3:05 am

> In larger systems, the O-R mapping and
> querying capabilities should be built on rich frameworks with
appropriate
> use of generated code. The repository and aggregate patterns are
fully
> compatible with this.

Hmmmm... So, even with a wonderful persistence framework I still
should write repository classes. I will have to try it before I
believe it:

Which orders has customer 123 placed during January 2004?

1. Quick and dirty implementation

customer := TCustomer.FindObjectsUsingSql( ObjectDatabase,
'custno = "123" );
orders := customer.FindLinkedObjectsUsingSql( 'orders',
'date between "2004-01-01" and "2004-01-31" );

Observation:
* Client knows about object database and search methods.

2. How I have solved it so far

customer := TCustomer.FindInstanceByCustNo( ObjectDatabase, '123' );
orders := customer.FindOrdersForPeriod( '2004-01-01', '2004-01-31' );

This means I have to implement:

class function TCustomer.FindInstanceByCustNo(
objectDatabase : TObjectDatabase; custNo : string ) : TCustomer;
begin
result := FindObjectsUsingSql( objectDatabase,
'custno = "' + custNo + '"' );
end;

function TCustomer.FindOrdersForPeriod( startDate,
endDate : string ) : TOrderList;
begin
result := FindLinkedObjectsUsingSql( 'orders',
Format( 'date between "%s" and "%s", [ startDate, endDate ] ) );
end;

Observations:
* Client code does not know about search methods. That functionality
is now hidden by the Customer and Order classes.

3. Repository solution

customer := TCustomerRepository.Instance.FindInstanceByCustNo(
'123' );
orders := TOrderRepository.Instance.FindOrdersForCustomerAndPeriod(
customer, '2004-01-01', '2004-01-31' );

I leave out implementation details. The repositories are supposed to
be linked to the object database so ObjectDatabase is not in the
client code any more.

Observations:
* The Customer and Order classes don't know any longer about the
object database and the FindUsingSql methods. That knowledge has been
moved to the repositories. Customer and Order can concentrate on
their other duties.

So, Eric, you might have a point here. Repositories provide better
cohesion.

However, I am still not fully convinced:

* For most classes there is no need for finding an instance by
attribute value. Once I locate the root object I can start
navigating. Likewise, most classes with 1-n associations (where n is
small) can use navigation instead of searching in the persistence
system. In other words, repositories will not be needed for every
entity class. In fact, they will be needed for very few classes. From
a client's perspective that must be confusing, how to know whether
there is a repository or not.

* What happens if I want to change an implementation from searching
to navigating. Should the method move from the repository to the
persistent class? In this case, it would help if the repository was
hidden behind the interface of the persistent class. Then I could
change the implementation without affecting the client code.

Or, am I completely off the track?

JohanNilsson



> In other words, repositories will not be needed for every
> entity class. In fact, they will be needed for very few classes.

I agree, Repositories are only meant for aggregate roots (I think you
called these roots earlier). So it's fine to not have an OrderRepository.

Secondly the FindOrdersForPeriod method implementation has some database
code in it which you're not happy about. The method selects some orders. I
would probably have just iterated over this Customer's orders and picked
the ones I wanted. That would avoid the database specific code in the
method implementation.

I don't yet have an answer to the `that's inefficient' argument. It may
be that the selection of orders is implemented using Specifications,
and the book describes sql Specification implementations. In my current
frame of mind, I would only worry about this after profiling the code,
and finding a performance problem.

VibhuMohindra


One answer is, "It doesn't always matter." I asked my client how many X
records he expects in the database. He said "never more than 800." I
write a performance test for retrieving 800 Xs and their related objects
from the database. 300 ms? Great. Problem solved.

JBRainsberger


I should add that all filtering of the aggregate root was done after
retrieving all the records from the database, in the Smalltalk fashion
of "select". No discernable performance issue, even on a P2-400 with 256
MB RAM.

JBRainsberger


> system. In other words, repositories will not be needed for every
> entity class. In fact, they will be needed for very few classes. From
> a client's perspective that must be confusing, how to know whether
> there is a repository or not.

I think you and Eric are in agreement. Repositories are only needed for
entities that are the roots of aggregates. Navigation from roots is used to
access sub-objects. Whether that access returns in-memory cached objects (i.e.
instance variable values) or query results is a matter of implementation
optimization.

> * What happens if I want to change an implementation from searching
> to navigating. Should the method move from the repository to the
> persistent class? In this case, it would help if the repository was
> hidden behind the interface of the persistent class. Then I could
> change the implementation without affecting the client code.
>
> Or, am I completely off the track?

In your first two examples, you made it a responsibility of Customer to return
its Orders for a given date range. I agree with that factoring of
responsibility. That allows you to switch from searching to navigating easily,
without client code knowing, as follows:

public class Customer {

public Collection getOrders(DateRange dateRange)
{
return OrderRepository.getOrdersForCustomer(this, dateRange);
}

}

Now the switch:


public class Customer {

   private Collection orders;

   public Collection getOrders(DateRange dateRange)
   {
      if (orders == nullorders = OrderRepository.getOrdersForCustomer(this);
      return selectOrdersInDateRange(dateRange);
   }

   private Collection selectOrdersInDateRange(DateRange dateRange) {
   //Collections is a location for Foreign Methods (see Fowler's Refactoring)
   //such as select(Collection, Predicate) which returns the subset of elements
   //in the argument Collection for which the argument Predicate is true.
      return Collections.select(orders, getInDateRangePredicate(dateRange));
   }
}


Best Regards,
RandyStafford


> > * What happens if I want to change an implementation from
searching
> > to navigating. Should the method move from the repository to the
> > persistent class? In this case, it would help if the repository
was
> > hidden behind the interface of the persistent class. Then I could
> > change the implementation without affecting the client code.
>
> In your first two examples, you made it a responsibility of
Customer to return its Orders for a given date range. I agree with
that factoring of responsibility. That allows you to switch from
searching to navigating easily, without client code knowing...

Yes, in this special case, but is this a general rule? The more I
think about it, the more I see searching a 1-n relation as some sort
of qualified navigation. Finding a class instance by attribute value
could also be looked upon as a qualified navigation between a class
type and its instances. It seems odd that repositories should be
responsible for qualified navigation whereas persistent classes carry
out unqualified navigation themselves.

JohanNilsson




RepositoriesAndAutomatedPersistence is mentioned on: ThreadView


VeryQuickWiki Version 2.6.3 - HTML Export