Before I start continuing the previous post, let's just see a quick reminder about our example! We have a Domain Object and for persistence we've decided to create a DTO and a DO<->DTO mapper class in the ACL to gain some flexibility and make our Domain more independent from the underlying data structure and persistence solution. Persistence Independence is a good thing, anyway.
//the domain object
public class Bonus {
private DateTime collectedAt;
// type can be "EXTRA_CHIPS" or "JAZZ_NIGHT"
private final String type;
private final long recurrencyIntervalInMilliseconds;
public RecurrentBonus(String type, long recurrencyIntervalInMilliseconds) {
this.type = type;
this.recurrencyIntervalInMilliseconds = recurrencyIntervalInMilliseconds;
}
public boolean isCollectable() {
return DateTime.now()
.isAfter(collectedAt.plusMilliseconds(recurrencyIntervalInMilliseconds));
}
public void collect() {
if (!isCollectable()) { throw new BonusPrematureCollectionAttemptException() }
DomainEventDispatcher.dispatch(new BonusCollectedEvent(this));
}
}
//the persistent object (DTO) in the ACL (insfrastructure layer)
@JsonIgnoreProperties(ignoreUnknown=true)
public class PersistentBonus implements Serializable {
private String type;
private long recurrencyIntervalInMilliseconds;
private DateTime collectedAt;
//getters-setters for each field
}
//the mapper between the the DO and DTO in the ACL
public class BonusPersistenceAssembler {
PersistentBonus toPersistence(Bonus bonus) { ... }
Bonus toDomain(PersistentBonus bonus) { ... }
}
Earlier we have seen that in some specific cases, like rolling release, this separation between our DOs and persistence DTOs comes very handy. Having said that, let's see how the mapping really works.
The trick - encapsulation gets in the way?
The mapping between PersistentBonus and Bonus takes place in the anti-corruption layer. The trick is, since the Bonus, as a well-behaving rich Domain Object, doesn't expose its internal structure, how can we do it at all? No other class knows about its private fields, let alone accessing them. In the followings I explore some possible ways, from trivial and not-so-ideal solutions to some more sophisticated ones.
Solution 1 - Add Getters-Setters to the DO and get over with it
Plainly exposing the internal structure of an object is a major no-no in DDD and OO in general. It violates loose coupling, encapsulation, extendibility, bla-bla. And we've taken pains to hide the internals of
Bonus, we shouldn't nullify the effort by making the internals accessible. Forget it if possible.
Solution 2 - Separating interface and implementation of the Domain Object
Using an interface is usually a good idea regardless of our current problem. Let's do that and add an additional Factory class.
public interface Bonus {
boolean isCollectable();
void collect();
}
public class BonusImpl implements Bonus{
//all the stuff from before
// getters-setters
}
//the only class in the domain directly seeing BonusImpl
public class BonusFactory {
Bonus createRecurrentBonus(String type, long recurrencyIntervalInMilliseconds) { ... }
}
// the mapper class in the anti-corruption layer
public class BonusPersistenceAssembler {
PersistentBonus toPersistence(Bonus bonus) { ... }
Bonus toDomain(PersistentBonus bonus) { ... }
}
Now we can arrange our code in a way that the other part of the Domain (everything but the
BonusFactory) accesses the object strictly through the interface, and the
BonusImpl will be only used by the
BonusFactory and the BonusPersistenceAssembler. Although the
BonusImpl has getters and setters, they are still shielded quite well from the rest of the Domain. I have two minor problems with this solution. One is the necessity of explicit casting from
Bonus to
BonusImpl in
BonusPersistenceAssembler.toPersistence, the other is that at the end of the day refraining from the direct use of the
BonusImpl in the Domain is entirely up to the discipline of the developers. In some cases explicit casting of
Bonus to
BonusImpl might seem the easy way to achieve something and a lazy fellow may fail to resist the temptation. Let's see what can we do about it.
Solution 3 - "Normal" interface + View interface + Factory + package protected implementation class
The first idea is to put the
BonusImpl with the
BonusFactory in a new package, and change the visibility of the
BonusImpl to package protected. Now it's hidden from even the rest of the Domain, but alas, from the BonusPersistenceAssembler, too. Then comes the next idea. Create a new interface,
BonusView, which captures only the view of the "data" in
Bonus, and let's pass a
BonusView to the
BonusPersistenceAssembler. Only this class should use the
BonusView. In code
public interface Bonus {
boolean isCollectable();
void collect();
}
public interface BonusView {
long getRecurrencyIntervalInMilliseconds();
String getType();
DateTime collectedAt();
}
//package protected
class BonusImpl implements Bonus, BonusView {
//all the stuff from before
// getters-setters
}
//the only class in the domain directly seeing BonusImpl.They are in the same package
public class BonusFactory {
Bonus createRecurrentBonus(String type, recurrencyIntervalInMilliseconds) { ... }
}
// the mapper class in the anti-corruption layer
public class BonusPersistenceAssembler {
PersistentBonus toPersistence(BonusView bonusView) { ... }
//uses the BonusFactory
Bonus toDomain(PersistentBonus bonus) { ... }
}
Better. We still have to cast from
Bonus to
BonusView before we pass the reference to the
BonusPersistenceAssembler, but the ugly getters-setters are nicely confined to a small nook of the code.
Solution 3.1 What about the invariants? - static factory method
However there is still one possible problem to address. If the
Bonus has some invariants (not in our simple example, but we are discussing the general idea), having individual setters for each field might be undesirable, since this approach leaves place for putting the object in a state that violates its invariant. In this case in the
BonusFactory we can use a
static factory method, passing all the fields together (or maybe only the ones that have to be set together to preserve an invariant) to it.
class BonusImpl {
public static Bonus reinstantiate(String type, long recurrencyIntervalInMilliseconds, DateTime collectedAt) { ... }
// other stuff
}
Finally we got rid of the setters. The method is called
reinstantiate, suggesting to the developer, that this method is to
recreate the DO from something. Conceptually it's very different from simply providing the means to set its fields individually. The drawback is that the factory method will be bloated if the class has more than a couple of fields.
Moral of the story: Setters-getters are troublemakers
It looks like we can tweak the problem in all kinds of smart ways, the result is always a compromise, and we wouldn't need to do anything at all if not for those setters-getters. Let's see how far we have come to mitigate the problem. We started with a simple class and ended up with two additional interfaces, a factory class, a static factory method and package protection. I start to doubt whether it's worth the effort. Let's see a different approach inspired by the static factory method.
Solution 4 - Memento
Instead of passing all the fields to the
reinstantiate method, we can create a new class to wrap them up. Let's call it
BonusMemento, because it's like a footprint of the
Bonus object. It doesn't have any logic, only the data, like the
PersistentBonus, just without JSON annotations,
Serializable interface and the other technology-related stuff. Unlike the
PersistentBonus, the
BonusMemento is part of the Domain, even if it's only used to make persistence easier. You can think of it as the skeleton of the
Bonus. Just the bones, no brain.
//the original Bonus class, not the interface
public class Bonus {
public static Bonus reinstantiate(BonusMemento bonusMemento) { ... }
// other stuff
}
public class BonusMemento {
private String type;
private long recurrencyIntervalInMilliseconds;
private DateTime collectedAt;
//getters-setters for each field
}
No more bloated factory method. No setters either, only getters. But wait! Why can't we use the BonusMemento to replace the getters, too? Instead of exposing its fields one by one through getters, the domain object can be responsible to creating its own Memento.
public class Bonus {
public static Bonus reinstantiate(BonusMemento bonusMemento) { ... }
public BonusMemento toMemento() { ... }
//all the stuff from before
}
// the mapper class in the anti-corruption layer
public class BonusPersistenceAssembler {
PersistentBonus toPersistence(BonusMemento bonusMemento) { ... }
Bonus toDomain(PersistentBonus bonus) {
BonusMemento bonusMemento = convertFrom(bonus);
return Bonus.reinstantiate(bonusMemento);
}
}
Not bad. We are back to the original
Bonus class + a static factory method +
Memento class. Encapsulation preserved, no bothering compromises. Well, maybe one. The
BonusMemento and the
PersistentBonus are almost the same, which is a bit of a code duplication. We cannot use
PersistentBonus directly in the
Bonus class, because the former belongs to the infrastructure layer. But the
PersistentBonus can extend
BonusMemento, inheriting its content and keeping only the infrastructure-specific part of its former self.
public class BonusMemento {
private String type;
private long recurrencyIntervalInMilliseconds;
private DateTime collectedAt;
//getters-setters for each field
}
@JsonIgnoreProperties(ignoreUnknown=true)
public class PersistentBonus extends BonusMemento implements Serializable {}
//so what is inside the mapper
public class BonusPersistenceAssembler {
PersistentBonus toPersistence(BonusMemento Memento) {
PersistentBonus ps = new PersistentBonus();
ps.setType(Memento.getType());
//set values for the other fields
return ps;
}
Bonus toDomain(PersistentBonus persistentBonus ) {
return Bonus.reinstantiate(persistentBonus );
}
}
The
toPersistence method of
BonusPersistenceAssembler simply copies the content of the Memento to the
PersistentBonus field by field. The
toDomain is even more simple, since
PersistentBonus is a subclass of
BonusMemento, we can simply pass it into
Bonus.reinstantiate. Awesome.
Solution 5 - Visitor
I've been playing with the idea of using the Visitor pattern to retrieve the values of the fields from the Domain Object.
public class Bonus {
public void buildView(BonusVisitor visitor) {
visitor.setType(this.type);
visitor.setRecurrencyIntervalInMilliseconds(this.recurrencyIntervalInMilliseconds);
visitor.setCollectedAt(this.collectedAt);
}
//all the stuff from before
}
//in the Domain
public interface BonusVisitor {
//setters
}
//in the infrastructure
public PersistenceBonusVisitor implements BonusVisitor {
private final PersistentBonus persistentBonus;
public PersistenceBonusVisitor(PersistentBonus persistentBonus) { this.persistentBonus = persistentBonus }
// in the setters call the setters of PersistentBonus
}
The visitor pattern is suitable for this kind of situations when we want an object to have control over what it shares about its internals. But at the end of the day we already have it with the Mementos and in a simpler form, so I've decided to stick to that.
Solution 6 - Using reflection
To be frank, I have never tried using reflection. There are some tools available, like Dozer, which promise a painless mapping betweens DOs and DTOs. A colleague of mine has told me about their experiences with Dozer. They could spare themselves the time of writing mapper classes, but they still needed to create the DTOs, the DOs had to have getters-setters, they still wanted to test that the mapping is correct, so couldn't spare writing the tests for the mappers. And performance-wise reflection-based solutions are always heavy. Having said this, I still want to explore Dozer (or something alike) on day.
Summary
In the post we've started from a simple example and explored a couple of solutions for the problem. In the end I would stick to the Memento-one. Basically it's derived from the idea of using a static factory method instead of setters. Since we don't like long parameter-list, we've introduced a new class, the Memento, and then realized, we can use it instead of getters, too. As an additional bonus (no pun intended), we can even derive the persistence class from it. That's it.