2015-03-22

Advancing Enterprise DDD - Rethinking the POJO

In the previous essay of the Advancing Enterprise DDD series, we looked at a sample POJO with a variety of un-encapsulated persistence concerns. Unfortunately, this exposure occurs right at the heart of our software system: the domain model. Because the domain classes are used throughout the service layer, and are sometimes mirrored through the application layer up to the user interface, our whole codebase is susceptible to misuse of these data. Furthermore, having these data in the domain classes themselves inevitably clouds our thinking when trying to work in terms of the domain and the ubiquitous language. 

So what can we do about this problem? One partial solution is to not expose these persistence-related properties with public getters and setters, leaving just the private fields:

public class Customer {

    private Long customerId;
    private String firstName;
    private User createdBy;
    private Date createdDate;
    private User lastModifiedBy;
    private Date lastModifiedDate;
    private Long version;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    } 
}

We can put the necessary JPA annotations on the fields themselves, and JPA is able to get and set these values using reflective techniques. I would highly recommend doing this much, but it may be all but impossible on established projects. And it does not completely alleviate the problem. For instance, we may be using a library to reflectively generate JSON for our entity classes, in which case, we need make sure that its actions are based on annotations, or reflecting on the getters and setters, and not on the fields themselves.

But there is a more important reason why this is only a partial solution: Although they are private, these fields are data sitting right at the heart of the domain model. They still can be improperly used within the entity classes themselves. Consider the following example. Suppose we have a user interface that brings up a list of customer accounts, and we allow for sorting the list by last name, account number, and similar fields. The product owner comes to tell us, “It’s great that we can sort this list in various ways, but the users really want some way to sort so that the records that are most interesting to them come to the top.” One of the developers has an idea: “We could sort the rows so that the most recently modified records come to the top.” The product owner likes this idea, and the developer implements this by providing a comparator in the customer class:

public class Customer {

    public static Comparator<Customer> recentInterestComparator =
        new Comparator<Customer>() {
            @Override
            public int compare(Customer c1, Customer c2) {
                Date date1 = c1.lastModifiedDate;
                Date date2 = c2.lastModifiedDate;
                return date2.compareTo(date1);
            }
        };

    // ...

}

Our private diagnostic data has just seeped out of the customer class and insidiously infected our domain model. The developer can hardly be blamed. He has not exposed any private data, and he simply made use of information sitting right there in the entity class.

Ideally, these persistence fields should be entirely hidden from the programmer outside the persistence layer. They should not be in our domain objects at all, private or not. We might try to separate these persistence concerns out into an entity container, such as this:

public class PersistentState[E] {

    private E entity;
    private Long id;
    private User createdBy;
    private Date createdDate;
    private User lastModifiedBy;
    private Date lastModifiedDate;
    private Long version;

    public E getEntity() {
        return entity;
    }

    // methods below are package private, and as such, are only
    // visible from within the repository classes

    Long getId() {
        return id;
    }

    void setUserId(id: Long) {
        this.id = id;
    }

    User getCreatedBy() {
        return createdBy;
    }

    void setCreatedBy(User createdBy) {
        this.createdBy = createdBy;
    }

    Date getCreatedDate() {
        return createdDate;
    }

    void setCreatedDate(Date createdDate) {
        this.createdDate = createdDate;
    }

    User getLastModifiedBy() {
        return lastModifiedBy;
    }

    void setLastModified(User lastModifiedBy) {
        this.lastModifiedBy = lastModifiedBy;
    }

    Date getLastModifiedDate() {
        return lastModifiedDate;
    }

    void setLastModifiedDate(Date lastModifiedDate) {
        this.lastModifiedDate = lastModifiedDate;
    }

    Long getVersion() {
        return version;
    }

    void setVersion(Long version) {
        this.version = version;
    }
}

This looks promising, but it would be hard to pull off with JPA, because JPA wasn’t designed to work this way. We would need to annotate our actual entity classes as @Embedded, and the PersistentState class as the @Entity. And we would need to subclass PersistentState for every kind of entity we have, since JPA determines the table name from the @Entity annotation, or from the name of the class that it annotates. It would also throw a monkey-wrench in our scheme if the primary key columns in the table we named differently, e.g., CUSTOMER_ID for the CUSTOMER table, ORDER_ID for the ORDER table, etc. We would not be able to provide JPA annotations that would map PeristentState.id correctly for the different tables.

None of these challenges are insurmountable, and yet it seems like more trouble than it's worth. But let's imagine we could pull it off. Then the repository classes would produce and consume PersistentState objects instead of the entities themselves. The service classes immediately adjacent to the repository layer would need to broker in PersistentState, but could keep it to themselves, passing the entities themselves in and out of auxiliary services and the application layer.

The PersistentState approach introduced here is exactly how longevity - a persistence framework for Scala and NoSQL - encapsulates this kind persistence information.

In the next essay in this series we will be discussing entity aggregates. After that, we will revisit this idea of a PersistentState from a different angle.

No comments:

Post a Comment