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.
01 02 03 04 05 06 07 08 09 10 11 12 13 | @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 } |
1 2 3 4 5 6 7 | 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.
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | // 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.
1 2 3 4 5 6 7 | 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.
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | @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. |