Skip to content

Commit 0c44b53

Browse files
authored
Merge pull request iluwatar#1563 from manannikov/Issue#1284
iluwatar#1284 Implement Version Number pattern
2 parents 43ed090 + 9ead3ad commit 0c44b53

File tree

13 files changed

+748
-0
lines changed

13 files changed

+748
-0
lines changed

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
<module>state</module>
9090
<module>strategy</module>
9191
<module>template-method</module>
92+
<module>version-number</module>
9293
<module>visitor</module>
9394
<module>double-checked-locking</module>
9495
<module>servant</module>

version-number/README.md

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
---
2+
layout: pattern
3+
title: Version Number
4+
folder: versionnumber
5+
permalink: /patterns/versionnumber/
6+
description: Entity versioning with version number
7+
8+
categories:
9+
- Concurrency
10+
11+
tags:
12+
- Data access
13+
- Microservices
14+
---
15+
16+
## Name / classification
17+
18+
Version Number.
19+
20+
## Also known as
21+
22+
Entity Versioning, Optimistic Locking.
23+
24+
## Intent
25+
26+
Resolve concurrency conflicts when multiple clients are trying to update same entity simultaneously.
27+
28+
## Explanation
29+
30+
Real world example
31+
32+
> Alice and Bob are working on the book, which stored in the database. Our heroes are making
33+
> changes simultaneously, and we need some mechanism to prevent them from overwriting each other.
34+
35+
In plain words
36+
37+
> Version Number pattern grants protection against concurrent updates to same entity.
38+
39+
Wikipedia says
40+
41+
> Optimistic concurrency control assumes that multiple transactions can frequently complete
42+
> without interfering with each other. While running, transactions use data resources without
43+
> acquiring locks on those resources. Before committing, each transaction verifies that no other
44+
> transaction has modified the data it has read. If the check reveals conflicting modifications,
45+
> the committing transaction rolls back and can be restarted.
46+
47+
**Programmatic Example**
48+
49+
We have a `Book` entity, which is versioned, and has a copy-constructor:
50+
51+
```java
52+
public class Book {
53+
private long id;
54+
private String title = "";
55+
private String author = "";
56+
57+
private long version = 0; // version number
58+
59+
public Book(Book book) {
60+
this.id = book.id;
61+
this.title = book.title;
62+
this.author = book.author;
63+
this.version = book.version;
64+
}
65+
66+
// getters and setters are omitted here
67+
}
68+
```
69+
70+
We also have `BookRepository`, which implements concurrency control:
71+
72+
```java
73+
public class BookRepository {
74+
private final Map<Long, Book> collection = new HashMap<>();
75+
76+
public void update(Book book) throws BookNotFoundException, VersionMismatchException {
77+
if (!collection.containsKey(book.getId())) {
78+
throw new BookNotFoundException("Not found book with id: " + book.getId());
79+
}
80+
81+
var latestBook = collection.get(book.getId());
82+
if (book.getVersion() != latestBook.getVersion()) {
83+
throw new VersionMismatchException(
84+
"Tried to update stale version " + book.getVersion()
85+
+ " while actual version is " + latestBook.getVersion()
86+
);
87+
}
88+
89+
// update version, including client representation - modify by reference here
90+
book.setVersion(book.getVersion() + 1);
91+
92+
// save book copy to repository
93+
collection.put(book.getId(), new Book(book));
94+
}
95+
96+
public Book get(long bookId) throws BookNotFoundException {
97+
if (!collection.containsKey(bookId)) {
98+
throw new BookNotFoundException("Not found book with id: " + bookId);
99+
}
100+
101+
// return copy of the book
102+
return new Book(collection.get(bookId));
103+
}
104+
}
105+
```
106+
107+
Here's the concurrency control in action:
108+
109+
```java
110+
var bookId = 1;
111+
// Alice and Bob took the book concurrently
112+
final var aliceBook = bookRepository.get(bookId);
113+
final var bobBook = bookRepository.get(bookId);
114+
115+
aliceBook.setTitle("Kama Sutra"); // Alice has updated book title
116+
bookRepository.update(aliceBook); // and successfully saved book in database
117+
LOGGER.info("Alice updates the book with new version {}", aliceBook.getVersion());
118+
119+
// now Bob has the stale version of the book with empty title and version = 0
120+
// while actual book in database has filled title and version = 1
121+
bobBook.setAuthor("Vatsyayana Mallanaga"); // Bob updates the author
122+
try {
123+
LOGGER.info("Bob tries to update the book with his version {}", bobBook.getVersion());
124+
bookRepository.update(bobBook); // Bob tries to save his book to database
125+
} catch (VersionMismatchException e) {
126+
// Bob update fails, and book in repository remained untouchable
127+
LOGGER.info("Exception: {}", e.getMessage());
128+
// Now Bob should reread actual book from repository, do his changes again and save again
129+
}
130+
```
131+
132+
Program output:
133+
134+
```java
135+
Alice updates the book with new version 1
136+
Bob tries to update the book with his version 0
137+
Exception: Tried to update stale version 0 while actual version is 1
138+
```
139+
140+
## Class diagram
141+
142+
![alt text](etc/version-number.urm.png "Version Number pattern class diagram")
143+
144+
## Applicability
145+
146+
Use Version Number for:
147+
148+
* resolving concurrent write-access to the data
149+
* strong data consistency
150+
151+
## Tutorials
152+
* [Version Number Pattern Tutorial](http://www.java2s.com/Tutorial/Java/0355__JPA/VersioningEntity.htm)
153+
154+
## Known uses
155+
* [Hibernate](https://vladmihalcea.com/jpa-entity-version-property-hibernate/)
156+
* [Elasticsearch](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html#index-versioning)
157+
* [Apache Solr](https://lucene.apache.org/solr/guide/6_6/updating-parts-of-documents.html)
158+
159+
## Consequences
160+
Version Number pattern allows to implement a concurrency control, which is usually done
161+
via Optimistic Offline Lock pattern.
162+
163+
## Related patterns
164+
* [Optimistic Offline Lock](https://martinfowler.com/eaaCatalog/optimisticOfflineLock.html)
165+
166+
## Credits
167+
* [Optimistic Locking in JPA](https://www.baeldung.com/jpa-optimistic-locking)
168+
* [JPA entity versioning](https://www.byteslounge.com/tutorials/jpa-entity-versioning-version-and-optimistic-locking)
169+
* [J2EE Design Patterns](http://ommolketab.ir/aaf-lib/axkwht7wxrhvgs2aqkxse8hihyu9zv.pdf)
21.7 KB
Loading
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
@startuml
2+
package com.iluwatar.versionnumber {
3+
class App {
4+
- LOGGER : Logger {static}
5+
+ App()
6+
+ main(args : String[]) {static}
7+
}
8+
class Book {
9+
- author : String
10+
- id : long
11+
- title : String
12+
- version : long
13+
+ Book()
14+
+ Book(book : Book)
15+
+ getAuthor() : String
16+
+ getId() : long
17+
+ getTitle() : String
18+
+ getVersion() : long
19+
+ setAuthor(author : String)
20+
+ setId(id : long)
21+
+ setTitle(title : String)
22+
+ setVersion(version : long)
23+
}
24+
class BookRepository {
25+
- collection : Map<Long, Book>
26+
+ BookRepository()
27+
+ add(book : Book)
28+
+ get(bookId : long) : Book
29+
+ update(book : Book)
30+
}
31+
}
32+
@enduml

version-number/pom.xml

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?xml version="1.0"?>
2+
<!--
3+
4+
The MIT License
5+
Copyright © 2014-2019 Ilkka Seppälä
6+
7+
Permission is hereby granted, free of charge, to any person obtaining a copy
8+
of this software and associated documentation files (the "Software"), to deal
9+
in the Software without restriction, including without limitation the rights
10+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
copies of the Software, and to permit persons to whom the Software is
12+
furnished to do so, subject to the following conditions:
13+
14+
The above copyright notice and this permission notice shall be included in
15+
all copies or substantial portions of the Software.
16+
17+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
THE SOFTWARE.
24+
25+
-->
26+
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
27+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
28+
<modelVersion>4.0.0</modelVersion>
29+
<parent>
30+
<groupId>com.iluwatar</groupId>
31+
<artifactId>java-design-patterns</artifactId>
32+
<version>1.24.0-SNAPSHOT</version>
33+
</parent>
34+
<artifactId>version-number</artifactId>
35+
<dependencies>
36+
<dependency>
37+
<groupId>org.junit.jupiter</groupId>
38+
<artifactId>junit-jupiter-engine</artifactId>
39+
<scope>test</scope>
40+
</dependency>
41+
<dependency>
42+
<groupId>org.mockito</groupId>
43+
<artifactId>mockito-core</artifactId>
44+
<scope>test</scope>
45+
</dependency>
46+
</dependencies>
47+
<build>
48+
<plugins>
49+
<plugin>
50+
<groupId>org.apache.maven.plugins</groupId>
51+
<artifactId>maven-assembly-plugin</artifactId>
52+
<executions>
53+
<execution>
54+
<configuration>
55+
<archive>
56+
<manifest>
57+
<mainClass>com.iluwatar.versionnumber.App</mainClass>
58+
</manifest>
59+
</archive>
60+
</configuration>
61+
</execution>
62+
</executions>
63+
</plugin>
64+
</plugins>
65+
</build>
66+
</project>
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* The MIT License
3+
* Copyright © 2014-2019 Ilkka Seppälä
4+
*
5+
* Permission is hereby granted, free of charge, to any person obtaining a copy
6+
* of this software and associated documentation files (the "Software"), to deal
7+
* in the Software without restriction, including without limitation the rights
8+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
* copies of the Software, and to permit persons to whom the Software is
10+
* furnished to do so, subject to the following conditions:
11+
*
12+
* The above copyright notice and this permission notice shall be included in
13+
* all copies or substantial portions of the Software.
14+
*
15+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
* THE SOFTWARE.
22+
*/
23+
24+
package com.iluwatar.versionnumber;
25+
26+
import org.slf4j.Logger;
27+
import org.slf4j.LoggerFactory;
28+
29+
/**
30+
* The Version Number pattern helps to resolve concurrency conflicts in applications.
31+
* Usually these conflicts arise in database operations, when multiple clients are trying
32+
* to update the same record simultaneously.
33+
* Resolving such conflicts requires determining whether an object has changed.
34+
* For this reason we need a version number that is incremented with each change
35+
* to the underlying data, e.g. database. The version number can be used by repositories
36+
* to check for external changes and to report concurrency issues to the users.
37+
*
38+
* <p>In this example we show how Alice and Bob will try to update the {@link Book}
39+
* and save it simultaneously to {@link BookRepository}, which represents a typical database.
40+
*
41+
* <p>As in real databases, each client operates with copy of the data instead of original data
42+
* passed by reference, that's why we are using {@link Book} copy-constructor here.
43+
*/
44+
public class App {
45+
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
46+
47+
/**
48+
* Program entry point.
49+
*
50+
* @param args command line args
51+
*/
52+
public static void main(String[] args) throws
53+
BookDuplicateException,
54+
BookNotFoundException,
55+
VersionMismatchException {
56+
var bookId = 1;
57+
58+
var bookRepository = new BookRepository();
59+
var book = new Book();
60+
book.setId(bookId);
61+
bookRepository.add(book); // adding a book with empty title and author
62+
LOGGER.info("An empty book with version {} was added to repository", book.getVersion());
63+
64+
// Alice and Bob took the book concurrently
65+
final var aliceBook = bookRepository.get(bookId);
66+
final var bobBook = bookRepository.get(bookId);
67+
68+
aliceBook.setTitle("Kama Sutra"); // Alice has updated book title
69+
bookRepository.update(aliceBook); // and successfully saved book in database
70+
LOGGER.info("Alice updates the book with new version {}", aliceBook.getVersion());
71+
72+
// now Bob has the stale version of the book with empty title and version = 0
73+
// while actual book in database has filled title and version = 1
74+
bobBook.setAuthor("Vatsyayana Mallanaga"); // Bob updates the author
75+
try {
76+
LOGGER.info("Bob tries to update the book with his version {}", bobBook.getVersion());
77+
bookRepository.update(bobBook); // Bob tries to save his book to database
78+
} catch (VersionMismatchException e) {
79+
// Bob update fails, and book in repository remained untouchable
80+
LOGGER.info("Exception: {}", e.getMessage());
81+
// Now Bob should reread actual book from repository, do his changes again and save again
82+
}
83+
}
84+
}

0 commit comments

Comments
 (0)