Статьи

Пересмотры и неизменность

Вот краткий пост. Я не уверен, как начать это. Это один из тех моментов «почему я не подумал об этом» при просмотре существующего кода. Из-за NDA я не могу поделиться реальным кодом. Это как-то связано с обработкой ревизий. Самое близкое, что я могу сказать, это то, как WordPress (WP) обрабатывает сообщения и ревизии в блоге.

В WP функция wp_insert_post вставляет или обновляет сообщение. Он проверяет поле идентификатора, чтобы определить, будет ли он выполнять INSERT или UPDATE . Если сообщение обновляется, оно проверяет, были ли внесены изменения. Если это так, ревизия сохраняется . Можно установить ограничение на количество сохраняемых ревизий. Если это так, самые старые из них удаляются.

Это звучит как нечто, что можно смоделировать как объект с богатым доменом. Вот первая попытка.

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?

С первой попытки методы установки создают проблему для обеспечения создания ревизии при обновлении публикации. Давайте попробуем еще раз. Вот наша вторая попытка.

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; }
}

Именно тогда у меня появился момент «почему я не подумал об этом»!

Обратите внимание, как мы инкапсулировали данные поста в его собственный тип — PostData . Это неизменно. Это позволяет гарантировать, что редакция создается при обновлении сообщения.

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

И вот как мы создаем ревизии.

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);
            }
        }
        ...
    }
    ...
}

Как я уже сказал, этот краткий пост. Дайте мне знать в комментариях ниже, если это то, что вы видели раньше, или, как и я, это дало вам момент «почему я не подумал об этом».

Смотрите оригинальную статью здесь: Изменения и неизменность

Мнения, высказанные участниками Java Code Geeks, являются их собственными.