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.
1 2 3 4 5 6 7 8 9 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.
1 2 3 4 5 6 7 8 9 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.
1 2 3 4 5 6 7 8 9 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.