Mon Oct 27, 2003 1:52 pm
I am looking for an answer to the following question.
How should one implement lazy load for domain objects in DDD?
My thinking thus far on this can be summarized with these points
1. Calling a repository from within a Get method (using either lazy
initialization or "Ghost") creates a dependency outside the model -
so that appears to be out.
2. Not allowing navigation to objects or data that require lazy load
for performance seems to defeat much of the benefit of OOP. The
alternative seems to be simply relying directly on the repsitory to
fetch all instances of an entity after creation. Consider Users and
Groups within a typical security system. Not being able to navigate
betweem these two Entities would make the model cumbersome and
awkward.
3. There seem to be some systems that, when modeled, do not reveal
much business logic at all, instead appearing to be glorified data
structures. Are these models served better without DDD? I want the
answer to this question to be 'no', but i also want to avoid trying
to solve every problem with the same solution.
bast1ano
I know it doesn't directly answer your question but you might want to look at Fowler's Patterns of Enterprise Application Architecture. He goes over this topic pretty well.
JoshKnowles
I think Eric makes the argument in the book that Repositories are necessary
infrastructure for domain models, and it's OK for model code to depend on them.
If that were not the case, and you still wanted "connectedness", then the whole
domain model would have to be loaded monolithically, and it may not fit in
memory and doing that would be slow anyway, or client code of the domain model
would have to retrieve and connect the objects at the appropriate time - which
would likely put way too much knowledge of the internal structure of the domain
model outside the domain model, thereby violating the encapsulation of the
domain model.
In my company's software, we use lazy loading through a feature provided by
TOPLink. We don't need Repositories to use that feature, but I think
Repositories have merit regardless.
Be aware that lazy-loading is not a performance panacea. It can cause "ripple
loading", as Martin describes in PoEAA, which in my experience is painfully
slow. And if you accumulate (via loading) an in-memory domain model with strong
references connecting the domain objects, you can get into heap over-consumption
and long GC pause issues.
If you want to escape all these issues, use GemStone :)
Your third question is the interesting one. In what contexts is DDD
appropriate, and in what contexts is it not?
RandyStafford
bastiano,
your third question was the most interesting so ...
3. low business logic -> no DDD ?
i've only recently finished reading the book. but this is my take on this
issue.
even in cases where there is little business logic, there are benefits to a
DDD approach. IMHO the primary focus of DDD is communication. DDD attempts to
build a bridge between customer expectations and coder implementations. the
language you used in your question indicates an implementation-oriented focus.
but implementation is not where the main benefit of DDD is. if, however, your
model does not improve your ability to communicate with the customer, then the
model is not carrying its own weight.
when there is simple business logic and you don't need to interact with the
customer too much, you can build maintainable, understandable applications
without DDD. but that doesn't mean that the application wouldn't benefit from
DDD.
MarkRim
> If you want to escape all these issues, use GemStone :)
Well, maybe not *all* the issues. 8^(
In any case, I'm working with some application designers and DBAs right
now to "get along", i.e. to see their common ground, etc.
One of the points I go back to again and again when abstract discussions
seem interminable...
When you talk about "data" in an application or system, one of the first
things to identify is "what kind of data"?
The data folks here right now are focusing on "data quality",
identifying "records of origin", "records of reference", "operational
data stores", and so on. The application developers are focusing on
objects. Most of the software that is built here lives around and
between big ERPs like SAP R3 and Peoplesoft, as well as around and
between big data warehouses based on Teradata.
These in between systems fill gaps, provide extra rules, moving data
hither and yon. They often work across multiple geographies with subsets
of the total information of a domain. The timing, freshness, etc. varies
by activity. Sometimes the new systems are records of origin, sometimes
the data is not completely valid until it is in an ERP.
So "what kind of data" implies getting a sense of these aspects, then
basing reasonable responses on specific scenarios.
"Lazy load" could mean loading a subset of data from a record of
reference as of some time period and other selectiveness, holding it for
some maximum amount of time before refresh, etc. Perhaps converting that
cache into a form for the duration that is more object-oriented. Perhaps
updating back through an ERP, which won't be in the DW again for 8-24
hours *if* all goes well.
Sometimes the O/R mapping is the *easy* part!
PatrickLogan
Like Randy Stafford mentions, Eric shows us that Repositories are
equal standing citizens of any domain model. Therefore creating a
dependancy isnt an issue. Your other solution is to create an
interface in the package/component/layer where you have a class that
needs to do lazy loading, and have the implementation inside a
repository or a mapper (depends on what you are doing).
Currently for lazyloading, I have a situation where I allow a domain
object to request from a repository object, though I have interfaced
this. As Randy also mentions, you need to be careful of how these
objects get loaded (i.e. what activates there loading).
We had a situation where an object was being rendered to the screeen
(web page). The object was an Aggregate Root. Unbeknown to the
interaction designer, a property he accessed involved talking to an
internal property that resulted in a lazy load. However this
property access took place during a listing of many data items, and
the whole thing took 1000% longer than it does now with the lazy
loadnig re-addressed.
NickRobinson
> How should one implement lazy load for domain objects in DDD?
>
I have little to add to the discussion so far. I agree that Fowler's
section on "Lazy Load" would be help show the options for solving
the problem.
But I did want to discuss some subtleties of dependencies here. I do
not like to make a "getter" have a reference to a repository. I
generally think of the repositories and factories being in a higher
layer than the entities and values (though I wouldn't want that
layering too firm or leading to separation by packaging). But just
because an entity doesn't have a conceptual dependency on the
repository doesn't mean that an exectution path cannot go that way.
In fact, something like this often happens when proxies and such
techniques are used.
For example, if we have a "Customer" object which references a
collection of "Order" objects, we may wish to lazy load the orders.
Suppose we have a CustomerRepository and an OrderRepository. When we
ask the CustomerRepository for a particular Customer, the "orders"
field will be populated with a proxy.
Now, the proxy encapsulates database access from the Customer, so
the proxy has no knowledge of repositories or queries. Yet when the
Customer asks for its orders, database code will be executed.
The proxy might be created semiautomatically be a database
framework. If not, it would also fit reasonably well within the
responsibility of the OrderRepository to configure the proxy, which
could be inserted by the CustomerRepository or a reconstituting
factory. Frameworks can automate a lot of this, and the repositories
may or may not be involved.
The point is that the execution path necessarily will invoke a
repository or other database framework logic, but this does not add
to the knowledge built into the entity.
EricEvans
Hi Eric,
I certainly like the idea of using a proxy in the lower domain layer, as the
domain objects are completely agnostic to the fact a DB is being used or
not. BUT, given this, how would you cope with Unit of Work? Do you see this
again as a role of a proxy?
Thanks Eric,
NickRobinson
Eric Evans wrote (some snipping done to remove non-relevant text):
How should one implement lazy load for domain objects in DDD?
For example, if we have a "Customer" object which references a collection of "Order" objects, we may wish to lazy load the orders. Suppose we have a CustomerRepository and an OrderRepository. When we ask the CustomerRepository for a particular Customer, the "orders" field will be populated with a proxy.
Now, the proxy encapsulates database access from the Customer, so the proxy has no knowledge of repositories or queries. Yet when the Customer asks for its orders, database code will be executed.
Eric,
I want to make sure I understand what you are saying here. In the first sentence of the second paragraph, did you mean to say "so the CUSTOMER has no knowledge of repositories or queries"??? Or am I misunderstanding something?
BillDavis
Oops! What I meant to say was that the *Customer* has no knowledge of
repositories or queries. The proxy does have that knowledge, which is
its whole reason for being. But it encapsulates it and presents an
abstraction of an attribute holder for the benefit of the Customer.
Sorry if I confused people.
EricEvans
Good question, Nick. Transactionality is an interesting aspect of domain-driven
design. I use TOPLink, which has both proxies and UnitOfWork, and which uses
the "editing copy" approach (as opposed to the "dirty object pool" approach) to
dirtiness detection. I try to make my domain layer code unaware of the presence
or absence of transactions, and I think that's easy enough, as long as the
persistence-layer infrastructure, e.g. proxies and repositories, is sensitive to
transactions. However I've found that framework code in higher layers (service
and application layers) has to know which approach is taken to dirtiness
detection. Eric, I too am interested in your thoughts on this.
RandyStafford
Hi Randy.
I have to admit in the past I have given in to the ease of informing the UOW
from within the domain layer, though it always troubled me. Thinking from
an XP perspective, it seems some of these concepts come at a price -
complexity. I know TOPLink is a Java framework, but for C# there isnt
really much out there, so we have to hand craft our infrastructures...this
is why this particular discussion is intriguing. I am still trying to
develop an informed opinion on the use of proxies and repositories - I have
never used proxies in this way before. I would love to see some concrete
examples. I didnt really understand how a proxy, whose purpose is to
interject a message to then hit the DB, could actually have no knowledge of
repositories or queries?
NickRobinson
> examples. I didnt really understand how a proxy, whose purpose is
to
> interject a message to then hit the DB, could actually have no
knowledge of
> repositories or queries?
A proxy definitely does have knowledge of a query, and possibly a
repository. The point was that it encapsulates this knowledge so
that the domain object that holds it only sees it as an abstract
container of an attribute. The domain object therefore has no
knowledge of the query. Nor does it know where the query came from,
because the proxy was inserted when the object was reconstituted in
its own repository (or factory).
EricEvans
> > not. BUT, given this, how would you cope with Unit of Work?
> > Do you see this
> > again as a role of a proxy?
This is a good question. I basically agree with Randy's answer:
>to the "dirty object pool" approach) to dirtiness detection. I try
>to make my domain layer code unaware of the presence or absence of
>transactions, and I think that's easy enough, as long as the
>persistence-layer infrastructure, e.g. proxies and repositories, is
>sensitive to transactions. However I've found that framework code
>in higher layers (service and application layers) has to know which
I like the application layer to control the transaction, the
entities and value objects to be ignorant of it (except that
dependency info and aggregate boundaries are at this level), and for
the framework to encapsulate all the how-to part of it. But
the "dirty" domain objects have to be added to the unit of work
somehow. In a really minimal framework, the application might end up
responsible for that, but that lets some domain knowledge slip up
into the domain layer. In some frameworks, setter methods are
generated. Now, this means there is very specific framework code
embedded in the domain model classes. But these are generated
methods that the developer neither modifies nor needs to read. Other
frameworks get that stuff more out of the way. Either way, the idea
(I think) is that the class isn't the important boundary between
what is model and what is infrastructure. With such a framework, a
*part* of the class (the part that has hand-coded behavior and
declares the nature of the attributes) is the model expression,
while the generated methods are infrastructure, and should ideally
be filtered out of our view (but can't easily be in most tools).
These are examples of the sorts of compromises I'm talking about
when I say we have to keep focused on deeper principles to deal with
situations that can obscure model-driven design.
By the way, there are other approaches too. I'd welcome more
comments from others here who have experience with those.
EricEvans
With such a framework, a
> *part* of the class (the part that has hand-coded behavior and
> declares the nature of the attributes) is the model expression,
> while the generated methods are infrastructure, and should ideally
> be filtered out of our view (but can't easily be in most tools).
>
You're lucky if you have the framework to work with. I am finding this more
interesting looking at it from a poor mans project - we dont have any tools
or framework support. We have to handcraft. How can we handcraft, yet
retain the high quality and standards of code that could be achieved when
uing a framework?
> These are examples of the sorts of compromises I'm talking about
> when I say we have to keep focused on deeper principles to deal with
> situations that can obscure model-driven design.
>
> By the way, there are other approaches too. I'd welcome more
> comments from others here who have experience with those.
>
When we started to look at implementing UOW on the current C#/ASP.NET
application I am working on, the original idea was to just let the domain
objects talk to a UOW interface. As a first idea, it wasnt bad - it got us
thinking about what we really needed the UOW for. But it concerned me. So
after playing around with .NET, we had the idea of using Attributes in .NET
to mark classes and methods:
[delete(input(typeof(Order))]
public void Remove(Order order)
{
}
I just made that up, but you can see the approach. If you have to put some
framework into the model code (based on compromises that you raise above),
then at least using attributes would leave the core model code unscathed, as
it were.
But, then theres a problem with this approach as all your classes needed to
descend from MarshalByValue, which isnt very intention revealing nor
understandable. We scrapped the idea. Concious of never wanting to pollute
the model code with cross-cutting concerns (uh oh), I wrote something to
proxy an assembly (in exploration) to see if we could put a framework
completely outside of the core model. Using attributes and proxying via
dynamic code generation, the original ability to keep the model code clean
is possible. Indeed you could probably drop the attributes and use another
method for specification.
There are lots of ways to do this, but unless you have the time to build
something or the money to buy, you're back to square 1 - the square I am
standing on. It is clear, that compromise is the friend we must look to
when there are other battles to fight. But after making the compromise, and
moving on, the perfectionist in me wants to return and deal with the
compromises, to learn from them. This is why I am fascinated by other
peoples approaches to this thorny subject.
`NickRobinson`
LazyLoad is mentioned on: ThreadView