Revisions and Immutability
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.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK