Domain Object, while not necessarily a design pattern, can at least be considered as a design "concept", and one of its points is:
https://wiki.c2.com/?DomainObject
Domain objects should not direct their persistance. They don't know how to be persisted and even if they will be persisted. The infrastructure layer is there for that.
However, how far does this go in practice? Because to me, a straight up reading of "doesn't know how to be persisted and even if it will be persisted" means to me it should not be designed at all with persistence in mind. But the problem is, persistence fundamentally requires the ability to save the exact state of the internals of the object and then recreate that exact state when we want to load it again or, at least, that it must be able to save and load enough to return the object to a behaviorally equivalent internal state.
But suppose we have an object like this one. Originally I had a sort of vague example here, but now here is an exact, concrete one in Java language.
class RandomCircleMaker {
// Takes a random number generator and a maximum radius.
public RandomCircleMaker(IRng rng, float maxRadius) {
mRng = rng;
mMaxRadius = maxRadius;
}
// Pumps out Circle objects with a random radius between 0
// and the maximum.
public Circle makeCircle() {
float radius = mRng.randomInRange(0.0f, mMaxRadius);
return new Circle(radius);
}
private IRng mRng;
private float mMaxRadius;
};
The question is, if I have something that then tries to persist this object, it at the very least has to be able to persist the maximum-radius field. Yet, it is quite clear that that field is private
, as it should be to ensure encapsulation. Moreover, because the "spec" on which RandomCircleMaker
was designed doesn't say anything about persistence, only that it should be able to be set up with a max radius and then pump out Circle
s with that fixed max radius, the designer naturally did not include a getMaxRadius
method to get this parameter.
Hence, what happens when it turns out that we want persistence? I can only see a few possible options, none of which are ideal:
In a language like the Java used above, it is possible to invade the internals of a class using reflection which, of course, obviously isn't something you generally should be doing, but persistence could be argued to be a special case. On the other hand, it means any modification of the domain object's internals requires a corresponding modification of the persistence layer (though not vice versa!). So we have a one-way tight coupling that, while natural, still "feels a bit too tight".
Regardless of that, not all languages permit such a move. In C++, for example, we don't have such a thing as reflection. In that case, it seems the only way we can get around that is to add a method like
getMaxRadius
, or else make "friend
s" with the persistence classes - but does that count as "knowledge" of persistence for the sake of the Domain Object concept, even if it isn't strictly a persistence-related method like including an explicit "save
" and/or "load
" method? Or is this, much like reflection itself, an acceptable "break" from this particular design concept's sub-principle? If it is knowledge and also not acceptable, how on earth is one supposed to persist the above object?Because the
maxRadius
parameter does have impact on the circles generated, the persistence system could perhaps try to guess it statistically by generating zillions of circles. The problem here is this is not exact persistence, it has a chance of failing and it has a chance of taking an extremely long time to come up with a good result. Not to mention that it depends on assumptions about the implementation that may not be in the contract, such as the probability distribution for the particular random generator passed (which would technically depend on the contract forIRng
, i.e. is thatrandomInRange
meant only to give a uniform distribution, or can it give, say, a Gaussian, or ...?).
Hence, it seems I am missing something fundamentally important about what has been said on the C2 wiki - and if so, what is it?
Note 1: I suppose in C++'s C subset one can try to memcpy
and that at least produces a binary copy while being at least as "rude" as reflection, but it's even more dangerous than reflection due to the fact of raw memory access potentially inviting a security hazard, and last of all, you are pretty much stuck with a very opaque binary image only - no, say, writing to JSON, XML, any kind of queryable RDB like SQL, or other more convenient, transparent, and/or interesting or structured formats. Not to mention being completely and utterly non-portable [fire that stuff up after saving from x86 on an ARM processor, and it almost surely goes boom-boom].
Note 2: I suppose one could work around that in C++-like languages by writing one's own code generator or pre-processor (think Qt MOC) and adding it to the build chain, that would recognize non-standard extensions to annotate the persistence objects (say "#persists RandomCircleMaker
"), strip them and go over and insert the friend declarations before passing to the compiler so that at least in the directly human-touched code there is no "knowledge", but that seems like a heck of a lot of effort in the name of this "principle". And I've never heard of anyone doing that before, though of course that isn't saying much.