Sunday, 17 November 2013

Persisting the Domain Objects - 1

This post is about the not-so-trivial, but omnipresent chore of persistency in a DDD project. Let's just plunge into the middle through an example. We have to develop a submodule in our imaginary in New York-residing on-line Casino application to reward players with bonuses. The Product Owner's wish is that they can collect tickets to Jazz-concerts in the local Royal Albert Hall once a fortnight (first bonus type) and 50 extra chips once a day (second bonus type). In the architecture the client and the server has an agreement, that the client can check the "collectability" of a bonus, and can collect it. After a bit of pondering we come up with the following class to represent the bonus.

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));
    }
}

Although the code could be improved (for example for the sake of brevity I've completely skipped the part how a Bonus is tied to a Player, as deemed irrelevant for us now), we are quite satisfied with it. It's very object-orientated. It hides completely what the conditions of the collectability are. The PO can come up with totally different kind of collectable bonuses (ones that depend on some previous achievement from the player, or one which applies to only Irish players, whatever), and the interface of the class won't change. So, next step. How to persist it?

Do we need a PersistentBonus class?

Let's assume we use Mongo, which stores its data in JSON format. In this case the object to be persisted has to have getters-setters for each field and has to implement the Serializable interface (it's a usual requirement for a JSON-serializing library, like Jackson). It might even need some library-specific annotations. Definitely the kinds of things we don't want to press into our Domain Object. It would mean letting Infrastructure details creeping into the Domain.The solution is that we can create Data Transfer Objects for our Domain Objects to capture all the needs of the chosen persistence solution, and we can transform the DO to DTO when persisting it, and the other way around when it's reinstantiated from the DB. The DTO and the mapper in the anti-corruption layer would look something like

@JsonIgnoreProperties(ignoreUnknown=true)
public class PersistentBonus implements Serializable {
    private String type;
    private long recurrencyIntervalInMilliseconds;
    private DateTime collectedAt;
    //getters-setters for each field
}
public class BonusPersistenceAssembler {
    PersistentBonus toPersistence(Bonus bonus) { ... }
    Bonus toDomain(PersistentBonus bonus) { ... }  
}

If we use an ORM solution, like Hibernate, we might not even need the mandatory getters-setters and implementing Serializable. A couple of annotations or XML configuration can do the trick. It's tempting just not to bother with DTOs at all. But separating the persistence code from the domain has one big advantage.

Can you do a rolling release if there are data structure changes in DB?

It makes possible data structure changes in the DB without the need of downtime. Imagine we've chosen to go without DTOs and our application has gone live a while ago in New York and we already have thousands of entries for Bonus in our DB. Then, as the company grows and gains territory, it decides to launch in other states and the PO decides that the Extra Chips and the Jazz Night could mean different things in different states. From now on, the type and state together should characterize the Bonus. After a bit of thinking we figure out that the slightly changed Domain would be served better by a slightly changed Bonus class

public class Bonus {
    //instead of String type;
    private final BonusCategory category;
    //old stuff
}
public class BonusCategory {
    private final String type;
    private final State state;
    // constructor and getters
}

Very neat, but it's not compatible anymore with the entries in the DB. We can execute a DB patch to convert the data to a new format, changing type to bonusCategory and use NEW_YORK as the state for all, then deploy the new version of the Casino, but we can't do it without downtime. If the patch is executed first the new data would break the old Casino before we deploy the new one (unknown field bonusCategory and the expected type is missing), if the app is deployed first, it would break immediately because of the old format of the data (unknown field type and the expected bonusCategory is missing). The management want a rolling release. No downtime. If we have a separate class for persistence and a mapper in the ACL, it's possible, even easy. We do some minor changes in the DTO and the mapper to accomodate both data format.

@JsonIgnoreProperties(ignoreUnknown=true)
public class PersistentBonus implements Serializable {
    private String type;
    //new field
    private String state;
    private long recurrencyIntervalInMilliseconds;
    private DateTime collectedAt;
    //getters-setters for each field
}
public class BonusPersistenceAssembler {
    PersistentBonus toPersistence(Bonus bonus) { ... }
    Bonus toDomain(PersistentBonus persistentBonus) { 
       BonusCategory bonusCategory = getBonusCategory(persistentBonus);
       // inject bonusCategory into the Bonus and the other stuff        
    } 
    private static BonusCategory getBonusCategory(PersistentBonus persistentBonus) {
       if (persistentBonus.getState() == null) {
          //old data, belongs to New York
          return new BonusCategory("NEW_YORK",persistentBonus.getType());  
       } else {
          return new BonusCategory(persistentBonus.getState(),persistentBonus.getType()); 
       }  
   } 
}

Done. Now our app can handle both the new and the old format of data. We can deploy it, then execute the DB patch. In the next release we can remove the "if persistentBonus.getState() == null" check from the code entirely. It's called intermediate code.

Pros and contras

Hopefully I've managed to make a point why separating persistence DTOs and DOs can be very useful even it requires a bit more code. I know using the Domain POJOs directly in persistence (like Hibernate offers) and in messages between the server and the client is very tempting, and yields a clean and lean codebase with relatively smaller number of classes. Writing an ACL with all the DTOs and mappers is usually a tedious monkey-work. As almost everything in software development the decision is about the trade-off. Does your application needs this level of independence of the Domain from the data so much, that you are willing to go the extra mile?

What's next

If the answer is yes, then you still need to spend some time on contemplating how to implement the idea. Unfortunately the DO<->DTO mapping is not as trivial as it seems at first. In the next post I'll explore what difficulties we need to face with this approach and how can we overcome them. Stay tuned.

No comments :

Post a Comment