#1284 Implement Version Number pattern
This commit is contained in:
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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[]{}));
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user