8

Revisions and Immutability

 2 years ago
source link: https://lorenzo-dee.blogspot.com/2018/11/revisions-and-immutability.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK