diff --git a/patterns/design-patterns-architectural/README.md b/patterns/design-patterns-architectural/README.md index fbe4221752..7f3eea27af 100644 --- a/patterns/design-patterns-architectural/README.md +++ b/patterns/design-patterns-architectural/README.md @@ -1,3 +1,4 @@ ### Relevant Articles: - [Service Locator Pattern](https://www.baeldung.com/java-service-locator-pattern) - [The DAO Pattern in Java](https://www.baeldung.com/java-dao-pattern) +- [A Practical Example of Hexagonal Architecture in Java](https://www.baeldung.com/java-hexagonal-pattern) diff --git a/patterns/design-patterns-architectural/src/main/java/com/baeldung/hexagonal/ChatApplication.java b/patterns/design-patterns-architectural/src/main/java/com/baeldung/hexagonal/ChatApplication.java new file mode 100644 index 0000000000..4e8cc457f7 --- /dev/null +++ b/patterns/design-patterns-architectural/src/main/java/com/baeldung/hexagonal/ChatApplication.java @@ -0,0 +1,45 @@ +package com.baeldung.hexagonal; + +import com.baeldung.hexagonal.adapters.DisplayInConsoleAdapter; +import com.baeldung.hexagonal.adapters.InMemoryMessageStore; +import com.baeldung.hexagonal.application.ChatManager; +import com.baeldung.hexagonal.domain.ChatUser; +import com.baeldung.hexagonal.domain.IDisplayMessages; + +import java.util.ArrayDeque; +import java.util.Scanner; + +public class ChatApplication { + + public static void main(String[] args) { + Scanner console = new Scanner(System.in); + + System.out.print("Enter username of user 1: "); + ChatUser user1 = new ChatUser(console.nextLine()); + + System.out.printf("Enter username of user 2: "); + ChatUser user2 = new ChatUser(console.nextLine()); + + System.out.println("Chat will end when any user uses the word bye in a message."); + + InMemoryMessageStore messageStore = new InMemoryMessageStore(new ArrayDeque<>(10)); + IDisplayMessages messageDisplayer = new DisplayInConsoleAdapter(messageStore); + ChatManager chatManager = new ChatManager(messageStore, messageDisplayer); + + + while (true) { + System.out.printf("From %s to %s : ", user1, user2); + String message = console.nextLine(); + if (message.toLowerCase().contains("bye")) { + chatManager.sendMessage(user1, user2, message); + System.out.println("Chat recap:"); + messageDisplayer.displayMessages(); + System.exit(0); + } + chatManager.sendMessage(user1, user2, message); + ChatUser temp = user1; + user1 = user2; + user2 = temp; + } + } +} diff --git a/patterns/design-patterns-architectural/src/main/java/com/baeldung/hexagonal/adapters/DisplayInConsoleAdapter.java b/patterns/design-patterns-architectural/src/main/java/com/baeldung/hexagonal/adapters/DisplayInConsoleAdapter.java new file mode 100644 index 0000000000..ea4982182b --- /dev/null +++ b/patterns/design-patterns-architectural/src/main/java/com/baeldung/hexagonal/adapters/DisplayInConsoleAdapter.java @@ -0,0 +1,36 @@ +package com.baeldung.hexagonal.adapters; + +import com.baeldung.hexagonal.domain.ChatMessage; +import com.baeldung.hexagonal.domain.IDisplayMessages; +import com.baeldung.hexagonal.domain.IStoreMessages; + +/** + * A Utility adapter to display the chat messages in the console + */ +public class DisplayInConsoleAdapter implements IDisplayMessages { + + IStoreMessages messageStore; + + public DisplayInConsoleAdapter(IStoreMessages messageStore) { + this.messageStore = messageStore; + } + + public static void clearScreen() { + System.out.print("\033[H\033[2J"); + System.out.flush(); + } + + @Override + public void displayMessages() { + clearScreen(); + for (ChatMessage message: messageStore.getMessages(10)) { + System.out.printf( + "%tF %tT [%s to %s]: %s %n", + message.getTimeSent(), + message.getTimeSent(), + message.getFrom(), + message.getTo(), + message.getContents()); + } + } +} diff --git a/patterns/design-patterns-architectural/src/main/java/com/baeldung/hexagonal/adapters/InMemoryMessageStore.java b/patterns/design-patterns-architectural/src/main/java/com/baeldung/hexagonal/adapters/InMemoryMessageStore.java new file mode 100644 index 0000000000..6c4c12eb6f --- /dev/null +++ b/patterns/design-patterns-architectural/src/main/java/com/baeldung/hexagonal/adapters/InMemoryMessageStore.java @@ -0,0 +1,37 @@ +package com.baeldung.hexagonal.adapters; + +import com.baeldung.hexagonal.domain.ChatMessage; +import com.baeldung.hexagonal.domain.IStoreMessages; + +import java.util.Collection; +import java.util.Comparator; +import java.util.Queue; +import java.util.stream.Collectors; + +/** + * We're storing the messages in memory. We could later opt to switch to an implementation that stores messages in a + * database. + */ +public class InMemoryMessageStore implements IStoreMessages { + + private Queue messages; + + public InMemoryMessageStore(Queue messages) { + this.messages = messages; + } + + @Override + public void storeMessage(ChatMessage message) { + this.messages.add(message); + } + + @Override + public Collection getMessages(long maxNbMessages) {// @formatter:off + return messages.stream() + .sorted((m1, m2) -> m2.getTimeSent().compareTo(m1.getTimeSent())) + .limit(maxNbMessages) + .sorted(Comparator.comparing(ChatMessage::getTimeSent)) + .collect(Collectors.toList()); + // @formatter:on + } +} diff --git a/patterns/design-patterns-architectural/src/main/java/com/baeldung/hexagonal/application/ChatManager.java b/patterns/design-patterns-architectural/src/main/java/com/baeldung/hexagonal/application/ChatManager.java new file mode 100644 index 0000000000..3ec54f1354 --- /dev/null +++ b/patterns/design-patterns-architectural/src/main/java/com/baeldung/hexagonal/application/ChatManager.java @@ -0,0 +1,27 @@ +package com.baeldung.hexagonal.application; + +import com.baeldung.hexagonal.domain.*; + +import java.time.LocalDateTime; + +public class ChatManager implements ISendMessage { + + // The domain doesn't need to know where messages will be stored + private IStoreMessages messageStore; + + // The domain doesn't need to know how messages will be displayed + private IDisplayMessages messageDisplayer; + + public ChatManager(IStoreMessages messageStore, IDisplayMessages displayMessages) { + this.messageStore = messageStore; + this.messageDisplayer = displayMessages; + } + + @Override + public void sendMessage(ChatUser from, ChatUser to, String message) { + ChatMessage chatMessage = new ChatMessage(LocalDateTime.now(), from, to, message); + this.messageStore.storeMessage(chatMessage); + this.messageDisplayer.displayMessages(); + } + +} diff --git a/patterns/design-patterns-architectural/src/main/java/com/baeldung/hexagonal/domain/ChatMessage.java b/patterns/design-patterns-architectural/src/main/java/com/baeldung/hexagonal/domain/ChatMessage.java new file mode 100644 index 0000000000..27db77a887 --- /dev/null +++ b/patterns/design-patterns-architectural/src/main/java/com/baeldung/hexagonal/domain/ChatMessage.java @@ -0,0 +1,34 @@ +package com.baeldung.hexagonal.domain; + +import java.time.LocalDateTime; + +public class ChatMessage { + private LocalDateTime timeSent; + private ChatUser from; + private ChatUser to; + private String contents; + + public LocalDateTime getTimeSent() { + return timeSent; + } + + public ChatUser getFrom() { + return from; + } + + public ChatUser getTo() { + return to; + } + + public String getContents() { + return contents; + } + + public ChatMessage(LocalDateTime timeSent, ChatUser from, ChatUser to, String contents) { + this.timeSent = timeSent; + this.from = from; + this.to = to; + this.contents = contents; + } + +} diff --git a/patterns/design-patterns-architectural/src/main/java/com/baeldung/hexagonal/domain/ChatUser.java b/patterns/design-patterns-architectural/src/main/java/com/baeldung/hexagonal/domain/ChatUser.java new file mode 100644 index 0000000000..e66e0c79e3 --- /dev/null +++ b/patterns/design-patterns-architectural/src/main/java/com/baeldung/hexagonal/domain/ChatUser.java @@ -0,0 +1,14 @@ +package com.baeldung.hexagonal.domain; + +public class ChatUser { + private String name; + + public ChatUser(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } +} diff --git a/patterns/design-patterns-architectural/src/main/java/com/baeldung/hexagonal/domain/IDisplayMessages.java b/patterns/design-patterns-architectural/src/main/java/com/baeldung/hexagonal/domain/IDisplayMessages.java new file mode 100644 index 0000000000..beb7e71cff --- /dev/null +++ b/patterns/design-patterns-architectural/src/main/java/com/baeldung/hexagonal/domain/IDisplayMessages.java @@ -0,0 +1,8 @@ +package com.baeldung.hexagonal.domain; + +/** + * An external system displays the messages sent by users. + */ +public interface IDisplayMessages { + void displayMessages(); +} diff --git a/patterns/design-patterns-architectural/src/main/java/com/baeldung/hexagonal/domain/ISendMessage.java b/patterns/design-patterns-architectural/src/main/java/com/baeldung/hexagonal/domain/ISendMessage.java new file mode 100644 index 0000000000..dcec7e033a --- /dev/null +++ b/patterns/design-patterns-architectural/src/main/java/com/baeldung/hexagonal/domain/ISendMessage.java @@ -0,0 +1,8 @@ +package com.baeldung.hexagonal.domain; + +/** + * A user sends a message (application's use case) + */ +public interface ISendMessage { + void sendMessage(ChatUser from, ChatUser to, String message); +} diff --git a/patterns/design-patterns-architectural/src/main/java/com/baeldung/hexagonal/domain/IStoreMessages.java b/patterns/design-patterns-architectural/src/main/java/com/baeldung/hexagonal/domain/IStoreMessages.java new file mode 100644 index 0000000000..fe6195bb67 --- /dev/null +++ b/patterns/design-patterns-architectural/src/main/java/com/baeldung/hexagonal/domain/IStoreMessages.java @@ -0,0 +1,14 @@ +package com.baeldung.hexagonal.domain; + +import java.util.Collection; + +/** + * An external system (infrastructure) stores a message + */ +public interface IStoreMessages { + + void storeMessage(ChatMessage message); + + Collection getMessages(long maxNbMessages); + +}