Revisions and Immutability
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.
Published on Java Code Geeks with permission by Lorenzo Dee, partner at our JCG program. See the original article here: Revisions and Immutability Opinions expressed by Java Code Geeks contributors are their own. |