From 626c56730c7dac32eb33bd414ad4a9436736a097 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilkka=20Sepp=C3=A4l=C3=A4?= Date: Sun, 11 Sep 2016 20:02:34 +0300 Subject: [PATCH] Hexagonal pattern: Added Mongo based ticket repository and set production configuration to use that --- .../administration/ConsoleAdministration.java | 2 +- .../database/MongoTicketRepository.java | 194 ++++++++++++++++++ .../hexagonal/domain/LotteryNumbers.java | 18 ++ .../hexagonal/domain/LotteryTicket.java | 24 ++- .../hexagonal/module/LotteryModule.java | 4 +- .../hexagonal/sampledata/SampleData.java | 4 +- .../hexagonal/service/ConsoleLottery.java | 2 +- ...java => InMemoryTicketRepositoryTest.java} | 2 +- .../database/MongoTicketRepositoryTest.java | 94 +++++++++ .../hexagonal/domain/LotteryTicketTest.java | 6 +- .../hexagonal/test/LotteryTestUtils.java | 3 +- 11 files changed, 339 insertions(+), 14 deletions(-) create mode 100644 hexagonal/src/main/java/com/iluwatar/hexagonal/database/MongoTicketRepository.java rename hexagonal/src/test/java/com/iluwatar/hexagonal/database/{LotteryTicketRepositoryTest.java => InMemoryTicketRepositoryTest.java} (98%) create mode 100644 hexagonal/src/test/java/com/iluwatar/hexagonal/database/MongoTicketRepositoryTest.java diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministration.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministration.java index 4301c07e3..6a846280c 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministration.java +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministration.java @@ -54,7 +54,7 @@ public class ConsoleAdministration { administartion.getAllSubmittedTickets().forEach((k,v)->System.out.println("Key: " + k + " Value: " + v)); } else if (cmd.equals("2")) { LotteryNumbers numbers = administartion.performLottery(); - System.out.println("The winning numbers: " + numbers); + System.out.println("The winning numbers: " + numbers.getNumbersAsString()); System.out.println("Time to reset the database for next round, eh?"); } else if (cmd.equals("3")) { administartion.resetLottery(); diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/database/MongoTicketRepository.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/database/MongoTicketRepository.java new file mode 100644 index 000000000..ff0439af8 --- /dev/null +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/database/MongoTicketRepository.java @@ -0,0 +1,194 @@ +/** + * The MIT License + * Copyright (c) 2014 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.hexagonal.database; + +import com.iluwatar.hexagonal.domain.LotteryNumbers; +import com.iluwatar.hexagonal.domain.LotteryTicket; +import com.iluwatar.hexagonal.domain.LotteryTicketId; +import com.iluwatar.hexagonal.domain.PlayerDetails; +import com.mongodb.MongoClient; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import org.bson.Document; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; + +/** + * Mongo lottery ticket database + */ +public class MongoTicketRepository implements LotteryTicketRepository { + + private static final String DEFAULT_HOST = "localhost"; + private static final int DEFAULT_PORT = 27017; + private static final String DEFAULT_DB = "lotteryDB"; + private static final String DEFAULT_TICKETS_COLLECTION = "lotteryTickets"; + private static final String DEFAULT_COUNTERS_COLLECTION = "counters"; + + private MongoClient mongoClient; + private MongoDatabase database; + private MongoCollection ticketsCollection; + private MongoCollection countersCollection; + + /** + * Constructor + */ + public MongoTicketRepository() { + connect(); + } + + /** + * Constructor accepting parameters + */ + public MongoTicketRepository(String host, int port, String dbName, String ticketsCollectionName, + String countersCollectionName) { + connect(host, port, dbName, ticketsCollectionName, countersCollectionName); + } + + /** + * Connect to database with default parameters + */ + public void connect() { + connect(DEFAULT_HOST, DEFAULT_PORT, DEFAULT_DB, DEFAULT_TICKETS_COLLECTION, DEFAULT_COUNTERS_COLLECTION); + } + + /** + * Connect to database with given parameters + */ + public void connect(String host, int port, String dbName, String ticketsCollectionName, + String countersCollectionName) { + if (mongoClient != null) { + mongoClient.close(); + } + mongoClient = new MongoClient(host , port); + database = mongoClient.getDatabase(dbName); + ticketsCollection = database.getCollection(ticketsCollectionName); + countersCollection = database.getCollection(countersCollectionName); + if (countersCollection.count() <= 0) { + initCounters(); + } + } + + private void initCounters() { + Document doc = new Document("_id", "ticketId").append("seq", 1); + countersCollection.insertOne(doc); + } + + /** + * @return next ticket id + */ + public int getNextId() { + Document find = new Document("_id", "ticketId"); + Document increase = new Document("seq", 1); + Document update = new Document("$inc", increase); + Document result = countersCollection.findOneAndUpdate(find, update); + return result.getInteger("seq"); + } + + /** + * @return mongo client + */ + public MongoClient getMongoClient() { + return mongoClient; + } + + /** + * + * @return mongo database + */ + public MongoDatabase getMongoDatabase() { + return database; + } + + /** + * + * @return tickets collection + */ + public MongoCollection getTicketsCollection() { + return ticketsCollection; + } + + /** + * + * @return counters collection + */ + public MongoCollection getCountersCollection() { + return countersCollection; + } + + @Override + public Optional findById(LotteryTicketId id) { + Document find = new Document("ticketId", id.getId()); + ArrayList results = ticketsCollection.find(find).limit(1).into(new ArrayList()); + if (results.size() > 0) { + LotteryTicket lotteryTicket = docToTicket(results.get(0)); + return Optional.of(lotteryTicket); + } else { + return Optional.empty(); + } + } + + @Override + public Optional save(LotteryTicket ticket) { + int ticketId = getNextId(); + Document doc = new Document("ticketId", ticketId); + doc.put("email", ticket.getPlayerDetails().getEmail()); + doc.put("bank", ticket.getPlayerDetails().getBankAccount()); + doc.put("phone", ticket.getPlayerDetails().getPhoneNumber()); + doc.put("numbers", ticket.getNumbers().getNumbersAsString()); + ticketsCollection.insertOne(doc); + return Optional.of(new LotteryTicketId(ticketId)); + } + + @Override + public Map findAll() { + Map map = new HashMap<>(); + ArrayList docs = ticketsCollection.find(new Document()).into(new ArrayList()); + for (Document doc: docs) { + LotteryTicket lotteryTicket = docToTicket(doc); + map.put(lotteryTicket.getId(), lotteryTicket); + } + return map; + } + + @Override + public void deleteAll() { + ticketsCollection.deleteMany(new Document()); + } + + private LotteryTicket docToTicket(Document doc) { + PlayerDetails playerDetails = PlayerDetails.create(doc.getString("email"), doc.getString("bank"), + doc.getString("phone")); + int[] numArray = Arrays.asList(doc.getString("numbers").split(",")).stream().mapToInt(Integer::parseInt).toArray(); + HashSet numbers = new HashSet<>(); + for (int num: numArray) { + numbers.add(num); + } + LotteryNumbers lotteryNumbers = LotteryNumbers.create(numbers); + return LotteryTicket.create(new LotteryTicketId(doc.getInteger("ticketId")), playerDetails, lotteryNumbers); + } +} diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryNumbers.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryNumbers.java index 9ce8d61bd..930c919da 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryNumbers.java +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryNumbers.java @@ -22,8 +22,10 @@ */ package com.iluwatar.hexagonal.domain; +import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.PrimitiveIterator; import java.util.Random; import java.util.Set; @@ -78,6 +80,22 @@ public class LotteryNumbers { public Set getNumbers() { return Collections.unmodifiableSet(numbers); } + + /** + * @return numbers as comma separated string + */ + public String getNumbersAsString() { + List list = new ArrayList<>(); + list.addAll(numbers); + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < NUM_NUMBERS; i++) { + builder.append(list.get(i)); + if (i < NUM_NUMBERS - 1) { + builder.append(","); + } + } + return builder.toString(); + } /** * Generates 4 unique random numbers between 1-20 into numbers set. diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicket.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicket.java index 08064f46c..3e9ebdae4 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicket.java +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicket.java @@ -29,13 +29,15 @@ package com.iluwatar.hexagonal.domain; */ public class LotteryTicket { + private LotteryTicketId id; private final PlayerDetails playerDetails; private final LotteryNumbers lotteryNumbers; - + /** * Constructor. */ - private LotteryTicket(PlayerDetails details, LotteryNumbers numbers) { + private LotteryTicket(LotteryTicketId id, PlayerDetails details, LotteryNumbers numbers) { + this.id = id; playerDetails = details; lotteryNumbers = numbers; } @@ -43,8 +45,8 @@ public class LotteryTicket { /** * Factory for creating lottery tickets; */ - public static LotteryTicket create(PlayerDetails details, LotteryNumbers numbers) { - return new LotteryTicket(details, numbers); + public static LotteryTicket create(LotteryTicketId id, PlayerDetails details, LotteryNumbers numbers) { + return new LotteryTicket(id, details, numbers); } /** @@ -61,6 +63,20 @@ public class LotteryTicket { return lotteryNumbers; } + /** + * @return id + */ + public LotteryTicketId getId() { + return id; + } + + /** + * set id + */ + public void setId(LotteryTicketId id) { + this.id = id; + } + @Override public String toString() { return playerDetails.toString() + " " + lotteryNumbers.toString(); diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/module/LotteryModule.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/module/LotteryModule.java index c62dbfa54..0a0177f25 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/module/LotteryModule.java +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/module/LotteryModule.java @@ -25,8 +25,8 @@ package com.iluwatar.hexagonal.module; import com.google.inject.AbstractModule; import com.iluwatar.hexagonal.banking.InMemoryBank; import com.iluwatar.hexagonal.banking.WireTransfers; -import com.iluwatar.hexagonal.database.InMemoryTicketRepository; import com.iluwatar.hexagonal.database.LotteryTicketRepository; +import com.iluwatar.hexagonal.database.MongoTicketRepository; import com.iluwatar.hexagonal.notifications.LotteryNotifications; import com.iluwatar.hexagonal.notifications.StdOutNotifications; @@ -36,7 +36,7 @@ import com.iluwatar.hexagonal.notifications.StdOutNotifications; public class LotteryModule extends AbstractModule { @Override protected void configure() { - bind(LotteryTicketRepository.class).to(InMemoryTicketRepository.class); + bind(LotteryTicketRepository.class).to(MongoTicketRepository.class); bind(LotteryNotifications.class).to(StdOutNotifications.class); bind(WireTransfers.class).to(InMemoryBank.class); } diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/sampledata/SampleData.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/sampledata/SampleData.java index 1af18118d..b9c779c34 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/sampledata/SampleData.java +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/sampledata/SampleData.java @@ -27,6 +27,7 @@ import com.iluwatar.hexagonal.domain.LotteryConstants; import com.iluwatar.hexagonal.domain.LotteryNumbers; import com.iluwatar.hexagonal.domain.LotteryService; import com.iluwatar.hexagonal.domain.LotteryTicket; +import com.iluwatar.hexagonal.domain.LotteryTicketId; import com.iluwatar.hexagonal.domain.PlayerDetails; import java.util.ArrayList; @@ -94,7 +95,8 @@ public class SampleData { */ public static void submitTickets(LotteryService lotteryService, int numTickets) { for (int i = 0; i < numTickets; i++) { - LotteryTicket ticket = LotteryTicket.create(getRandomPlayerDetails(), LotteryNumbers.createRandom()); + LotteryTicket ticket = LotteryTicket.create(new LotteryTicketId(), + getRandomPlayerDetails(), LotteryNumbers.createRandom()); lotteryService.submitTicket(ticket); } } diff --git a/hexagonal/src/main/java/com/iluwatar/hexagonal/service/ConsoleLottery.java b/hexagonal/src/main/java/com/iluwatar/hexagonal/service/ConsoleLottery.java index 5463eca7c..afacc35cc 100644 --- a/hexagonal/src/main/java/com/iluwatar/hexagonal/service/ConsoleLottery.java +++ b/hexagonal/src/main/java/com/iluwatar/hexagonal/service/ConsoleLottery.java @@ -83,7 +83,7 @@ public class ConsoleLottery { chosen.add(Integer.parseInt(parts[i])); } LotteryNumbers lotteryNumbers = LotteryNumbers.create(chosen); - LotteryTicket lotteryTicket = LotteryTicket.create(details, lotteryNumbers); + LotteryTicket lotteryTicket = LotteryTicket.create(new LotteryTicketId(), details, lotteryNumbers); Optional id = service.submitTicket(lotteryTicket); if (id.isPresent()) { System.out.println("Submitted lottery ticket with id: " + id.get()); diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/database/LotteryTicketRepositoryTest.java b/hexagonal/src/test/java/com/iluwatar/hexagonal/database/InMemoryTicketRepositoryTest.java similarity index 98% rename from hexagonal/src/test/java/com/iluwatar/hexagonal/database/LotteryTicketRepositoryTest.java rename to hexagonal/src/test/java/com/iluwatar/hexagonal/database/InMemoryTicketRepositoryTest.java index 31cb8f5f0..d32e594a8 100644 --- a/hexagonal/src/test/java/com/iluwatar/hexagonal/database/LotteryTicketRepositoryTest.java +++ b/hexagonal/src/test/java/com/iluwatar/hexagonal/database/InMemoryTicketRepositoryTest.java @@ -39,7 +39,7 @@ import com.iluwatar.hexagonal.test.LotteryTestUtils; * Tests for {@link LotteryTicketRepository} * */ -public class LotteryTicketRepositoryTest { +public class InMemoryTicketRepositoryTest { private final LotteryTicketRepository repository = new InMemoryTicketRepository(); diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/database/MongoTicketRepositoryTest.java b/hexagonal/src/test/java/com/iluwatar/hexagonal/database/MongoTicketRepositoryTest.java new file mode 100644 index 000000000..dc0d5c7fd --- /dev/null +++ b/hexagonal/src/test/java/com/iluwatar/hexagonal/database/MongoTicketRepositoryTest.java @@ -0,0 +1,94 @@ +/** + * The MIT License + * Copyright (c) 2014 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.hexagonal.database; + +import com.iluwatar.hexagonal.domain.LotteryNumbers; +import com.iluwatar.hexagonal.domain.LotteryTicket; +import com.iluwatar.hexagonal.domain.LotteryTicketId; +import com.iluwatar.hexagonal.domain.PlayerDetails; +import com.mongodb.MongoClient; +import org.junit.Before; +import org.junit.Test; + +import java.util.Optional; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Tests for Mongo based ticket repository + */ +public class MongoTicketRepositoryTest { + + private static final String TEST_HOST = "localhost"; + private static final int TEST_PORT = 27017; + private static final String TEST_DB = "lotteryDB"; + private static final String TEST_TICKETS_COLLECTION = "lotteryTickets"; + private static final String TEST_COUNTERS_COLLECTION = "counters"; + + private MongoTicketRepository repository; + + @Before + public void init() { + MongoClient mongoClient = new MongoClient(TEST_HOST, TEST_PORT); + mongoClient.dropDatabase(TEST_DB); + mongoClient.close(); + repository = new MongoTicketRepository(TEST_HOST, TEST_PORT, TEST_DB, TEST_TICKETS_COLLECTION, + TEST_COUNTERS_COLLECTION); + } + + @Test + public void testSetup() { + assertTrue(repository.getCountersCollection().count() == 1); + assertTrue(repository.getTicketsCollection().count() == 0); + } + + @Test + public void testNextId() { + assertEquals(1, repository.getNextId()); + assertEquals(2, repository.getNextId()); + assertEquals(3, repository.getNextId()); + } + + @Test + public void testCrudOperations() { + // create new lottery ticket and save it + PlayerDetails details = PlayerDetails.create("foo@bar.com", "123-123", "07001234"); + LotteryNumbers random = LotteryNumbers.createRandom(); + LotteryTicket original = LotteryTicket.create(new LotteryTicketId(), details, random); + Optional saved = repository.save(original); + assertEquals(1, repository.getTicketsCollection().count()); + assertTrue(saved.isPresent()); + // fetch the saved lottery ticket from database and check its contents + Optional found = repository.findById(saved.get()); + assertTrue(found.isPresent()); + LotteryTicket ticket = found.get(); + assertEquals("foo@bar.com", ticket.getPlayerDetails().getEmail()); + assertEquals("123-123", ticket.getPlayerDetails().getBankAccount()); + assertEquals("07001234", ticket.getPlayerDetails().getPhoneNumber()); + assertEquals(original.getNumbers(), ticket.getNumbers()); + // clear the collection + repository.deleteAll(); + assertEquals(0, repository.getTicketsCollection().count()); + } +} diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketTest.java b/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketTest.java index e1918686a..4840dc897 100644 --- a/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketTest.java +++ b/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketTest.java @@ -36,14 +36,14 @@ public class LotteryTicketTest { public void testEquals() { PlayerDetails details1 = PlayerDetails.create("bob@foo.bar", "1212-121212", "+34332322"); LotteryNumbers numbers1 = LotteryNumbers.create(new HashSet(Arrays.asList(1, 2, 3, 4))); - LotteryTicket ticket1 = LotteryTicket.create(details1, numbers1); + LotteryTicket ticket1 = LotteryTicket.create(new LotteryTicketId(), details1, numbers1); PlayerDetails details2 = PlayerDetails.create("bob@foo.bar", "1212-121212", "+34332322"); LotteryNumbers numbers2 = LotteryNumbers.create(new HashSet(Arrays.asList(1, 2, 3, 4))); - LotteryTicket ticket2 = LotteryTicket.create(details2, numbers2); + LotteryTicket ticket2 = LotteryTicket.create(new LotteryTicketId(), details2, numbers2); assertEquals(ticket1, ticket2); PlayerDetails details3 = PlayerDetails.create("elsa@foo.bar", "1223-121212", "+49332322"); LotteryNumbers numbers3 = LotteryNumbers.create(new HashSet(Arrays.asList(1, 2, 3, 8))); - LotteryTicket ticket3 = LotteryTicket.create(details3, numbers3); + LotteryTicket ticket3 = LotteryTicket.create(new LotteryTicketId(), details3, numbers3); assertFalse(ticket1.equals(ticket3)); } } diff --git a/hexagonal/src/test/java/com/iluwatar/hexagonal/test/LotteryTestUtils.java b/hexagonal/src/test/java/com/iluwatar/hexagonal/test/LotteryTestUtils.java index 883c8127f..02304296f 100644 --- a/hexagonal/src/test/java/com/iluwatar/hexagonal/test/LotteryTestUtils.java +++ b/hexagonal/src/test/java/com/iluwatar/hexagonal/test/LotteryTestUtils.java @@ -28,6 +28,7 @@ import java.util.Set; import com.iluwatar.hexagonal.domain.LotteryNumbers; import com.iluwatar.hexagonal.domain.LotteryTicket; +import com.iluwatar.hexagonal.domain.LotteryTicketId; import com.iluwatar.hexagonal.domain.PlayerDetails; /** @@ -51,6 +52,6 @@ public class LotteryTestUtils { Set givenNumbers) { PlayerDetails details = PlayerDetails.create(email, account, phone); LotteryNumbers numbers = LotteryNumbers.create(givenNumbers); - return LotteryTicket.create(details, numbers); + return LotteryTicket.create(new LotteryTicketId(), details, numbers); } }