#1284 Implement Version Number pattern

This commit is contained in:
Pavel Manannikov
2020-10-13 20:17:25 +03:00
parent 1f4a412e70
commit 2332520d67
13 changed files with 733 additions and 0 deletions

View File

@ -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.
*
* <p>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.
*
* <p>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 {
long bookId = 1;
BookRepository bookRepository = new BookRepository();
Book 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 Book aliceBook = bookRepository.get(bookId);
final Book 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
}
}
}

View File

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

View File

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

View File

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

View File

@ -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<Long, Book> 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());
}
Book 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));
}
}

View File

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

View File

@ -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[]{}));
}
}

View File

@ -0,0 +1,72 @@
/*
* 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.*;
/**
* Tests for {@link BookRepository}
*/
class BookRepositoryTest {
@Test
void testBookRepository() throws BookDuplicateException, BookNotFoundException, VersionMismatchException {
final long bookId = 1;
BookRepository bookRepository = new BookRepository();
Book book = new Book();
book.setId(bookId);
bookRepository.add(book);
assertEquals(0, book.getVersion());
final Book aliceBook = bookRepository.get(bookId);
final Book bobBook = bookRepository.get(bookId);
assertEquals(aliceBook.getTitle(), bobBook.getTitle());
assertEquals(aliceBook.getAuthor(), bobBook.getAuthor());
assertEquals(aliceBook.getVersion(), bobBook.getVersion());
aliceBook.setTitle("Kama Sutra");
bookRepository.update(aliceBook);
assertEquals(1, aliceBook.getVersion());
assertEquals(0, bobBook.getVersion());
assertEquals(aliceBook.getVersion(), bookRepository.get(bookId).getVersion());
assertEquals(aliceBook.getTitle(), bookRepository.get(bookId).getTitle());
assertNotEquals(aliceBook.getTitle(), bobBook.getTitle());
bobBook.setAuthor("Vatsyayana Mallanaga");
try {
bookRepository.update(bobBook);
} catch (VersionMismatchException e) {
assertEquals(0, bobBook.getVersion());
assertEquals(1, bookRepository.get(bookId).getVersion());
assertEquals(aliceBook.getVersion(), bookRepository.get(bookId).getVersion());
assertEquals("", bobBook.getTitle());
assertNotEquals(aliceBook.getAuthor(), bobBook.getAuthor());
}
}
}