Here's a brief post. I'm not sure how to start it. It's one of those "why didn't I think of that" moments while reviewing some existing code. Due to NDAs, I cannot share the actual code. It has something to do with handling revisions. The closest thing I can relate to is how WordPress (WP) handles blog posts and revisions.
In WP, the wp_insert_post function inserts or updates a post. It checks the ID field to determine if it will carry out an INSERT or an UPDATE. If the post is being updated, it checks if changes were made. If so, a revision is saved. A limit for the number of revisions to keep can be set. If so, the oldest ones are deleted.
This sounds like something that can be modeled as a rich domain entity. Here's a first try.
@Entity
... class Post {
@Id @GeneratedValue ... id;
... name;
... title;
... content;
... excerpt;
... status; // e.g. 'draft', 'publish', 'inherit'
... type; // e.g. 'post', 'revision'
@OneToMany @JoinColumn(name="parent_post_id") ... List<Post> revisions;
...
// setters and getters
}
Post post = new Post();
post.setTitle("Lorem Ipsum");
post.setContent("...");
// save post
...
post = // retrieve existing post for updates
post.setContent("..."); // how can we ensure that revision is created?
In the first try, the setter methods pose a challenge to ensuring that a revision is created when the post is updated. Let's give it another try. Here's our second try.
// Immutable class
@Embeddable
... class PostData {
... title;
... content;
... excerpt;
// getters only
... getTitle() { return title; }
... getContent() { return content; }
... getExcerpt() { return excerpt; }
// equals() method to compare with another post data
// to see if there are changes
}
@Entity
... class Post {
@Id @GeneratedValue ... id;
... name; // for a revision, will contain parent ID and revision #
@Embedded ... PostData postData; // read-only
... status; // e.g. 'draft', 'published', 'inherit'
... type; // e.g. 'post', 'revision'
@OneToMany @JoinColumn(name="parent_post_id") ... List<Post> revisions;
...
... getTitle() { return this.postData.getTitle(); }
... getContent() { return this.postData.getContent(); }
... getExcerpt() { return this.postData.getExcerpt(); }
... getName() { return name; }
}
This is when I got my "why didn't I think of that" moment!
Note how we encapsulated the post data into its own type — PostData. It is immutable. This makes it possible to ensure that a revision is created when the post is updated.
PostData postData = new PostData("Lorem Ipsum", "...", "...");
Post post = new Post(postData);
// save post
...
post = // retrieve existing post for updates
// post.setContent("..."); // not possible
post.updateData(new PostData("...", "...", "...")); // ensure that revision is created
And here's how we create revisions.
@Entity
... class Post {
...
@Embedded ... PostData postData; // read-only
...
@OneToMany @JoinColumn(name="parent_post_id") ... List<Post> revisions;
...
public Post(PostData postData) {
this(postData, null);
}
/* package private */ Post(PostData postData, Post parent) {
if (postData == null) {
throw new IllegalArgumentException(...);
}
this.postData = postData;
if (parent == null) {
this.type = "post";
this.status = "draft";
this.name = null;
this.revisions = new ArrayList<>();
} else {
this.type = "revision";
this.status = "inherit";
this.name = "" + parent.getId() + "-revision" + (parent.getRevisionsCount() + 1);
this.revisions = null;
}
...
}
...
... void updateData(PostData newPostData) {
if (this.postData.equals(newPostData)) {
// no changes, no revisions added
return;
}
...
// creates a revision
PostData beforePostData = this.postData;
this.revisions.add(0, new Post(beforePostData, this));
// store latest changes
this.postData = newPostData;
// limit to number of revisions to keep
if (this.revisions.size() > ...) {
// delete the excess ones
for (...) {
this.revisions.remove(this.revisions.size() - 1);
}
}
...
}
...
}
Like I said, this one is a brief post. Let me know in the comments below if it's something you've seen before, or, just like me, it gave you a "why didn't I think of that" moment.
