+ + BookRepository()
+ + add(book : Book)
+ + get(bookId : long) : Book
+ + update(book : Book)
+ }
+}
+@enduml
\ No newline at end of file
diff --git a/version-number/pom.xml b/version-number/pom.xml
new file mode 100644
index 000000000..b5748c93b
--- /dev/null
+++ b/version-number/pom.xml
@@ -0,0 +1,66 @@
+
+
+
+ 4.0.0
+
+ com.iluwatar
+ java-design-patterns
+ 1.24.0-SNAPSHOT
+
+ version-number
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+
+
+
+ com.iluwatar.versionnumber.App
+
+
+
+
+
+
+
+
+
diff --git a/version-number/src/main/java/com/iluwatar/versionnumber/App.java b/version-number/src/main/java/com/iluwatar/versionnumber/App.java
new file mode 100644
index 000000000..cffba50d4
--- /dev/null
+++ b/version-number/src/main/java/com/iluwatar/versionnumber/App.java
@@ -0,0 +1,84 @@
+/*
+ * The MIT License
+ * Copyright © 2014-2019 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.iluwatar.versionnumber;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The Version Number pattern helps to resolve concurrency conflicts in applications.
+ * Usually these conflicts arise in database operations, when multiple clients are trying
+ * to update the same record simultaneously.
+ * Resolving such conflicts requires determining whether an object has changed.
+ * For this reason we need a version number that is incremented with each change
+ * to the underlying data, e.g. database. The version number can be used by repositories
+ * to check for external changes and to report concurrency issues to the users.
+ *
+ * In this example we show how Alice and Bob will try to update the {@link Book}
+ * and save it simultaneously to {@link BookRepository}, which represents a typical database.
+ *
+ *
As in real databases, each client operates with copy of the data instead of original data
+ * passed by reference, that's why we are using {@link Book} copy-constructor here.
+ */
+public class App {
+ private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
+
+ /**
+ * Program entry point.
+ *
+ * @param args command line args
+ */
+ public static void main(String[] args) throws
+ BookDuplicateException,
+ BookNotFoundException,
+ VersionMismatchException {
+ var bookId = 1;
+
+ var bookRepository = new BookRepository();
+ var book = new Book();
+ book.setId(bookId);
+ bookRepository.add(book); // adding a book with empty title and author
+ LOGGER.info("An empty book with version {} was added to repository", book.getVersion());
+
+ // Alice and Bob took the book concurrently
+ final var aliceBook = bookRepository.get(bookId);
+ final var bobBook = bookRepository.get(bookId);
+
+ aliceBook.setTitle("Kama Sutra"); // Alice has updated book title
+ bookRepository.update(aliceBook); // and successfully saved book in database
+ LOGGER.info("Alice updates the book with new version {}", aliceBook.getVersion());
+
+ // now Bob has the stale version of the book with empty title and version = 0
+ // while actual book in database has filled title and version = 1
+ bobBook.setAuthor("Vatsyayana Mallanaga"); // Bob updates the author
+ try {
+ LOGGER.info("Bob tries to update the book with his version {}", bobBook.getVersion());
+ bookRepository.update(bobBook); // Bob tries to save his book to database
+ } catch (VersionMismatchException e) {
+ // Bob update fails, and book in repository remained untouchable
+ LOGGER.info("Exception: {}", e.getMessage());
+ // Now Bob should reread actual book from repository, do his changes again and save again
+ }
+ }
+}
diff --git a/version-number/src/main/java/com/iluwatar/versionnumber/Book.java b/version-number/src/main/java/com/iluwatar/versionnumber/Book.java
new file mode 100644
index 000000000..93b35880b
--- /dev/null
+++ b/version-number/src/main/java/com/iluwatar/versionnumber/Book.java
@@ -0,0 +1,78 @@
+/*
+ * The MIT License
+ * Copyright © 2014-2019 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.iluwatar.versionnumber;
+
+public class Book {
+ private long id;
+ private String title = "";
+ private String author = "";
+
+ private long version = 0; // version number
+
+ public Book() {
+
+ }
+
+ /**
+ * We need this copy constructor to copy book representation in {@link BookRepository}.
+ */
+ public Book(Book book) {
+ this.id = book.id;
+ this.title = book.title;
+ this.author = book.author;
+ this.version = book.version;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getAuthor() {
+ return author;
+ }
+
+ public void setAuthor(String author) {
+ this.author = author;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+
+ public void setVersion(long version) {
+ this.version = version;
+ }
+}
diff --git a/version-number/src/main/java/com/iluwatar/versionnumber/BookDuplicateException.java b/version-number/src/main/java/com/iluwatar/versionnumber/BookDuplicateException.java
new file mode 100644
index 000000000..cd993b147
--- /dev/null
+++ b/version-number/src/main/java/com/iluwatar/versionnumber/BookDuplicateException.java
@@ -0,0 +1,33 @@
+/*
+ * The MIT License
+ * Copyright © 2014-2019 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.iluwatar.versionnumber;
+
+/**
+ * When someone has tried to add a book which repository already have.
+ */
+public class BookDuplicateException extends Exception {
+ public BookDuplicateException(String message) {
+ super(message);
+ }
+}
diff --git a/version-number/src/main/java/com/iluwatar/versionnumber/BookNotFoundException.java b/version-number/src/main/java/com/iluwatar/versionnumber/BookNotFoundException.java
new file mode 100644
index 000000000..f832f350f
--- /dev/null
+++ b/version-number/src/main/java/com/iluwatar/versionnumber/BookNotFoundException.java
@@ -0,0 +1,33 @@
+/*
+ * The MIT License
+ * Copyright © 2014-2019 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.iluwatar.versionnumber;
+
+/**
+ * Client has tried to make an operation with book which repository does not have.
+ */
+public class BookNotFoundException extends Exception {
+ public BookNotFoundException(String message) {
+ super(message);
+ }
+}
diff --git a/version-number/src/main/java/com/iluwatar/versionnumber/BookRepository.java b/version-number/src/main/java/com/iluwatar/versionnumber/BookRepository.java
new file mode 100644
index 000000000..ef41e79ac
--- /dev/null
+++ b/version-number/src/main/java/com/iluwatar/versionnumber/BookRepository.java
@@ -0,0 +1,86 @@
+/*
+ * The MIT License
+ * Copyright © 2014-2019 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.iluwatar.versionnumber;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This repository represents simplified database.
+ * As a typical database do, repository operates with copies of object.
+ * So client and repo has different copies of book, which can lead to concurrency conflicts
+ * as much as in real databases.
+ */
+public class BookRepository {
+ private final Map collection = new HashMap<>();
+
+ /**
+ * Adds book to collection.
+ * Actually we are putting copy of book (saving a book by value, not by reference);
+ */
+ public void add(Book book) throws BookDuplicateException {
+ if (collection.containsKey(book.getId())) {
+ throw new BookDuplicateException("Duplicated book with id: " + book.getId());
+ }
+
+ // add copy of the book
+ collection.put(book.getId(), new Book(book));
+ }
+
+ /**
+ * Updates book in collection only if client has modified the latest version of the book.
+ */
+ public void update(Book book) throws BookNotFoundException, VersionMismatchException {
+ if (!collection.containsKey(book.getId())) {
+ throw new BookNotFoundException("Not found book with id: " + book.getId());
+ }
+
+ var latestBook = collection.get(book.getId());
+ if (book.getVersion() != latestBook.getVersion()) {
+ throw new VersionMismatchException(
+ "Tried to update stale version " + book.getVersion()
+ + " while actual version is " + latestBook.getVersion()
+ );
+ }
+
+ // update version, including client representation - modify by reference here
+ book.setVersion(book.getVersion() + 1);
+
+ // save book copy to repository
+ collection.put(book.getId(), new Book(book));
+ }
+
+ /**
+ * Returns book representation to the client.
+ * Representation means we are returning copy of the book.
+ */
+ public Book get(long bookId) throws BookNotFoundException {
+ if (!collection.containsKey(bookId)) {
+ throw new BookNotFoundException("Not found book with id: " + bookId);
+ }
+
+ // return copy of the book
+ return new Book(collection.get(bookId));
+ }
+}
diff --git a/version-number/src/main/java/com/iluwatar/versionnumber/VersionMismatchException.java b/version-number/src/main/java/com/iluwatar/versionnumber/VersionMismatchException.java
new file mode 100644
index 000000000..94ea0fd9e
--- /dev/null
+++ b/version-number/src/main/java/com/iluwatar/versionnumber/VersionMismatchException.java
@@ -0,0 +1,33 @@
+/*
+ * The MIT License
+ * Copyright © 2014-2019 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.iluwatar.versionnumber;
+
+/**
+ * Client has tried to update a stale version of the book.
+ */
+public class VersionMismatchException extends Exception {
+ public VersionMismatchException(String message) {
+ super(message);
+ }
+}
diff --git a/version-number/src/test/java/com/iluwatar/versionnumber/AppTest.java b/version-number/src/test/java/com/iluwatar/versionnumber/AppTest.java
new file mode 100644
index 000000000..7b4984901
--- /dev/null
+++ b/version-number/src/test/java/com/iluwatar/versionnumber/AppTest.java
@@ -0,0 +1,46 @@
+/*
+ * The MIT License
+ * Copyright © 2014-2019 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.iluwatar.versionnumber;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+/**
+ * Application test
+ */
+class AppTest {
+
+ /**
+ * Issue: Add at least one assertion to this test case.
+ *
+ * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])}
+ * throws an exception.
+ */
+
+ @Test
+ void shouldExecuteApplicationWithoutException() {
+ assertDoesNotThrow(() -> App.main(new String[]{}));
+ }
+}
diff --git a/version-number/src/test/java/com/iluwatar/versionnumber/BookRepositoryTest.java b/version-number/src/test/java/com/iluwatar/versionnumber/BookRepositoryTest.java
new file mode 100644
index 000000000..6b7b2b39a
--- /dev/null
+++ b/version-number/src/test/java/com/iluwatar/versionnumber/BookRepositoryTest.java
@@ -0,0 +1,87 @@
+/*
+ * The MIT License
+ * Copyright © 2014-2019 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.iluwatar.versionnumber;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Tests for {@link BookRepository}
+ */
+class BookRepositoryTest {
+ private final long bookId = 1;
+ private final BookRepository bookRepository = new BookRepository();
+
+ @BeforeEach
+ public void setUp() throws BookDuplicateException {
+ var book = new Book();
+ book.setId(bookId);
+ bookRepository.add(book);
+ }
+
+ @Test
+ void testDefaultVersionRemainsZeroAfterAdd() throws BookNotFoundException {
+ var book = bookRepository.get(bookId);
+ assertEquals(0, book.getVersion());
+ }
+
+ @Test
+ void testAliceAndBobHaveDifferentVersionsAfterAliceUpdate() throws BookNotFoundException, VersionMismatchException {
+ final var aliceBook = bookRepository.get(bookId);
+ final var bobBook = bookRepository.get(bookId);
+
+ aliceBook.setTitle("Kama Sutra");
+ bookRepository.update(aliceBook);
+
+ assertEquals(1, aliceBook.getVersion());
+ assertEquals(0, bobBook.getVersion());
+ var actualBook = bookRepository.get(bookId);
+ assertEquals(aliceBook.getVersion(), actualBook.getVersion());
+ assertEquals(aliceBook.getTitle(), actualBook.getTitle());
+ assertNotEquals(aliceBook.getTitle(), bobBook.getTitle());
+ }
+
+ @Test
+ void testShouldThrowVersionMismatchExceptionOnStaleUpdate() throws BookNotFoundException, VersionMismatchException {
+ final var aliceBook = bookRepository.get(bookId);
+ final var bobBook = bookRepository.get(bookId);
+
+ aliceBook.setTitle("Kama Sutra");
+ bookRepository.update(aliceBook);
+
+ bobBook.setAuthor("Vatsyayana Mallanaga");
+ try {
+ bookRepository.update(bobBook);
+ } catch (VersionMismatchException e) {
+ assertEquals(0, bobBook.getVersion());
+ var actualBook = bookRepository.get(bookId);
+ assertEquals(1, actualBook.getVersion());
+ assertEquals(aliceBook.getVersion(), actualBook.getVersion());
+ assertEquals("", bobBook.getTitle());
+ assertNotEquals(aliceBook.getAuthor(), bobBook.getAuthor());
+ }
+ }
+}