Domain-Driven Design
> >
RepositoryAndUnitOfWork

Hello All,
first I want to say that the ideas and concepts in Eric's book help
me very much in thinking about the domain I am working in.
I have started a small project, but I have some difficulties in
implementing Eric's ideas. I'm using C++, and I could not find many
examples in this language. So I'm asking if someone with more
experience could give me some thoughts...

How do I combine the repository in the domain layer with a
persistence mechansim? Let me explain this with an example:

I have an entity in the domain layer, say currency:
class currency {...};

Because an entity has identity, I encapsulate this class in a smart
pointer, so that the services work just with that pointer:
typedef smart_ptr<currency> currency_ptr;

So this is the only type I add/get in the repository:
void currency_repository.add(currency_ptr);
currency_ptr currency_repository.get(string iso_code);

My first thought was to encapsulate all persistence mechanism in the
repository. But Eric states, that the user should decide when to
commit. So this could be in the repository (like repo.commit()), but
then I am mixing the two concepts. The bigger problem is, when
someone changes the object (e.g. currency_ptr->set_name("xy")) the
repository is unaware of this, and nobody but the user knows, that
this object should be saved in the database.

So my next idea was to use Fowler's UnitOfWork (and datamapper etc).
Besides that I don't know how to implement this in c++ (I could only
find java examples), I have to give the repository a reference to
this UnitOfWork, which I don't think is a good solution:
repo.add(uow&, currency_ptr);
otherwise, I would have to do double work:
repo.add(currency_ptr ptr);
uow.registerNew(currency_ptr ptr);?

Has anybody with more experience solved this kind of problem? Or any
hint to some c++ code that I could study?
Thank you,

StefanKluehspies



What if UnitOfWork.commit() caused all newly-registered objects to be added to
the appropriate Repository (in an ACID way)? I can't give an example in C++,
but I could probably conjure one in Java, assuming a way to get the appropriate
Repository for a (type of) domain object. The harder part is the ACID
semantics. Assuming O/R mapping, Repository add() methods would have to execute
insert statements against an RDBMS connection that was finally committed by the
UnitOfWork.

RandyStafford


You mean something like


UnitOfWork.registerNew(currency_ptr ptr) {
(find currency_repository);
repo.add(ptr);
my_new_objects.add(ptr);
}


and then in the client code just


uow.registerNew(currency_ptr ptr);
uow.commit();??


I haven't thought of that until now. I will give it a try.
Thank you very much, Randy,

StefanKluehspies


My general approach is to define objects at the domain level that do
what the customer wants. So for example if the customer wants to build
a purchase order, send it around for approvals and modifications, and
then submit that, then build a purchase order object and the objects
you need for updating it, approving it, etc. in the customer's
language.

On the user interface side, use these abstractions with this
language. The user's experience of the application can then be tested
without an understanding of what repositories, databases, and
transactions are involved "in the back office".

On the backside of the application, create subclasses of these
customer abstractions as needed, e.g. FlatFilePurchaseOrder,
CachedPurchaseOrder, DatabasedPurchaseOrder. (Two or more of these
might be used in conjunction on a complete system.) The key is too hide
all this stuff the customer really doesn't care about (and that might
change independent of changes to the business processes.)

It's here on the backside of the abstracted customer language that the
acts of updating, approving, and submitting are translated into
transactions. i.e. there may be multiple kinds of transactions saving
the objects in various forms up to and including saving the final
purchase order in some system like SAP R3, et al.

Two small examples...

someCachedPurchaseOrder.approve(someManager);

This might update the cached objects and tell the database'd objects
to save themselves. (e.g. the database may be an "operational data
store" for work in progress, ultimately headed for SAP R3.)

someCachedPurchaseOrder.submit();

This might update the cached object, tell the database'd objects to
save themselves, and tell the file-based objects to format themselves
into a purchase order and ftp themselves to the SAP landing zone.

PatrickLogan


> might be used in conjunction on a complete system.) The key is too
hide
> all this stuff the customer really doesn't care about (and that
might
> change independent of changes to the business processes.)

Is the storage medium (flat file, cached, database) a proper
discriminator for subclassing? What if you need to subclass based
on a business reason, rather than a technical one? You might be
challenged to come up with storage medium versions of those new
business subclasses, as well. Hence, you will be discriminating on
the inheritance hierarchy in two ways: storage approach and
business specialization. You might have to use design patterns to
solve that problem.

The cool thing about repositories is that they can help to abstract
the actual storage medium. Also, depending on your programming
language, you may have tools to help you in storage (in conjunction
with the repository). As discussed previously, JDO and Hibernate
are two such tools in the Java world.

ChrisGardner


> Is the storage medium (flat file, cached, database) a proper
> discriminator for subclassing? What if you need to subclass based
> on a business reason, rather than a technical one?

No, I wouldn't make this a subclass relationship. Instead, this would
be a compositional relationship. For example the cached object would
be composed of the objects for domain rules and computations. It would
also be composed of the external objects with their own
responsibilities for making the cache ACID, communicating with
enterprise sources/sinks for the object, etc.

All the "purchase order" objects would know how to speak purchase
orders with each other, but only the cached object knows about caches,
only the SAP object knows about that system and formats, etc.

I would recommend implementing a domain design to get that fairly
settled before integrating all the components.

> The cool thing about repositories is that they can help to abstract
> the actual storage medium.

All the same technologies can be used but the compositional approach
just provides the abstractions for where to isolate the details.

> Also, depending on your programming language, you may have tools to
> help you in storage (in conjunction with the repository). As
> discussed previously, JDO and Hibernate are two such tools in the
> Java world.

I don't know much about these two in particular, but even in a nearly
100% transparent system like Gemstone, there is a benefit to making
the technology aspects isolated within the domain-driven
activities. For example, some of the "Gemstone commits" would be used
to commit work-in-progress. These WIP objects would be isolated from
enterprise objects via the explicit domain-driven relationships. Only
when the domain calls for making something known to the enterprise
would you tie that "Gemstone commit" into an enterprise object. This
benefits from being captured and represented at the domain level, and
only underneath somewhere is the object doing a Gemstone commit.

Hoping this makes some sense in a 5 min. sketch.

PatrickLogan


Dear All,
thank you for your ideas. I have thought about it, and I want to
share my solution with you.

The fact is, that both, the repository, and the client programmer
have to care about some persistence mechanism (of course on different
levels). The repository directly (e.g. via some mappers) while
loading or finding domain objects. The client programmer (via unit of
work) while changing or deleting the objects. If I want to strictly
seperate repositories from unit of work, the client programmer would
have to add new objects to the repository and register it with the
uow.

To make the life easier, there are two ways: unit of work adds the
object to the repo, or the repo registers it as new object. I have
choosen the second way, because for me it is easier to pass an
uow-object than to find the right repository.

So my repository has a function add with a (c++)signature like that:
void currency_repository
add(currency_ptr const&, unit_of_work&);
and all the client has to do is something like
unit_of_work uow;
currency_ptr usd = currency_factory
create_new("USD");
currency_repository
add(usd, uow);
...
uow.commit();
Until now, I think this is some good solution. Of course, I hope that
this was the right decision when my little project grows larger ;-)
Anyway, thanks for the interesting discussions on that list! And if
somebody knows some good O/R-Mapping Tool for C++ (hopefully open-
source) I would appreciate any link.
Bye,

StefanKluehspies


My repositories all have a begin() and commit() method. For example:

repos.begin();
Person p = repos.getById("123");
p.setLastName("Smith");
repos.commit();

The transaction context is stored in a threadlocal variable

DaveFord
----

RepositoryAndUnitOfWork is mentioned on: ThreadView


VeryQuickWiki Version 2.6.3 - HTML Export