mirror of
https://github.com/tiennm99/java-design-patterns.git
synced 2026-08-20 14:24:05 +00:00
deps: Refactor dependencies (#3224)
* remove spring dep move junit, logging, mockito under dep mgmt * upgrade anti-corruption-layer deps * async method invocation * balking, bloc * bridge to bytecode * caching * callback - cqrs * component - health check * hexagonal - metadata mapping * rest of the patterns * remove checkstyle, take spotless into use
This commit is contained in:
@@ -47,45 +47,35 @@ import java.util.List;
|
||||
* <p><i>PROBLEM</i> <br>
|
||||
* Server applications in a distributed system must handle multiple clients that send them service
|
||||
* requests. Following forces need to be resolved:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Availability</li>
|
||||
* <li>Efficiency</li>
|
||||
* <li>Programming Simplicity</li>
|
||||
* <li>Adaptability</li>
|
||||
* <li>Availability
|
||||
* <li>Efficiency
|
||||
* <li>Programming Simplicity
|
||||
* <li>Adaptability
|
||||
* </ul>
|
||||
*
|
||||
* <p><i>PARTICIPANTS</i> <br>
|
||||
*
|
||||
* <ul>
|
||||
* <li>Synchronous Event De-multiplexer
|
||||
* <p>
|
||||
* {@link NioReactor} plays the role of synchronous event de-multiplexer.
|
||||
* It waits for events on multiple channels registered to it in an event loop.
|
||||
* </p>
|
||||
* </li>
|
||||
* <li>Initiation Dispatcher
|
||||
* <p>
|
||||
* {@link NioReactor} plays this role as the application specific {@link ChannelHandler}s
|
||||
* are registered to the reactor.
|
||||
* </p>
|
||||
* </li>
|
||||
* <li>Handle
|
||||
* <p>
|
||||
* {@link AbstractNioChannel} acts as a handle that is registered to the reactor.
|
||||
* When any events occur on a handle, reactor calls the appropriate handler.
|
||||
* </p>
|
||||
* </li>
|
||||
* <li>Event Handler
|
||||
* <p>
|
||||
* {@link ChannelHandler} acts as an event handler, which is bound to a
|
||||
* channel and is called back when any event occurs on any of its associated handles. Application
|
||||
* logic resides in event handlers.
|
||||
* </p>
|
||||
* </li>
|
||||
* <li>Synchronous Event De-multiplexer
|
||||
* <p>{@link NioReactor} plays the role of synchronous event de-multiplexer. It waits for
|
||||
* events on multiple channels registered to it in an event loop.
|
||||
* <li>Initiation Dispatcher
|
||||
* <p>{@link NioReactor} plays this role as the application specific {@link ChannelHandler}s
|
||||
* are registered to the reactor.
|
||||
* <li>Handle
|
||||
* <p>{@link AbstractNioChannel} acts as a handle that is registered to the reactor. When any
|
||||
* events occur on a handle, reactor calls the appropriate handler.
|
||||
* <li>Event Handler
|
||||
* <p>{@link ChannelHandler} acts as an event handler, which is bound to a channel and is
|
||||
* called back when any event occurs on any of its associated handles. Application logic
|
||||
* resides in event handlers.
|
||||
* </ul>
|
||||
*
|
||||
* The application utilizes single thread to listen for requests on all ports. It does not create a
|
||||
* separate thread for each client, which provides better scalability under load (number of clients
|
||||
* increase).
|
||||
* The example uses Java NIO framework to implement the Reactor.
|
||||
* increase). The example uses Java NIO framework to implement the Reactor.
|
||||
*/
|
||||
public class App {
|
||||
|
||||
@@ -103,9 +93,7 @@ public class App {
|
||||
this.dispatcher = dispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* App entry.
|
||||
*/
|
||||
/** App entry. */
|
||||
public static void main(String[] args) throws IOException {
|
||||
new App(new ThreadPoolDispatcher(2)).start();
|
||||
}
|
||||
@@ -143,7 +131,7 @@ public class App {
|
||||
* Stops the NIO reactor. This is a blocking call.
|
||||
*
|
||||
* @throws InterruptedException if interrupted while stopping the reactor.
|
||||
* @throws IOException if any I/O error occurs
|
||||
* @throws IOException if any I/O error occurs
|
||||
*/
|
||||
public void stop() throws InterruptedException, IOException {
|
||||
reactor.stop();
|
||||
|
||||
@@ -70,9 +70,7 @@ public class AppClient {
|
||||
service.execute(new UdpLoggingClient("Client 4", 16669));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops logging clients. This is a blocking call.
|
||||
*/
|
||||
/** Stops logging clients. This is a blocking call. */
|
||||
public void stop() {
|
||||
service.shutdown();
|
||||
if (!service.isTerminated()) {
|
||||
@@ -94,9 +92,7 @@ public class AppClient {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A logging client that sends requests to Reactor on TCP socket.
|
||||
*/
|
||||
/** A logging client that sends requests to Reactor on TCP socket. */
|
||||
static class TcpLoggingClient implements Runnable {
|
||||
|
||||
private final int serverPort;
|
||||
@@ -141,12 +137,9 @@ public class AppClient {
|
||||
artificialDelayOf(100);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A logging client that sends requests to Reactor on UDP socket.
|
||||
*/
|
||||
/** A logging client that sends requests to Reactor on UDP socket. */
|
||||
static class UdpLoggingClient implements Runnable {
|
||||
private final String clientName;
|
||||
private final InetSocketAddress remoteAddress;
|
||||
@@ -155,7 +148,7 @@ public class AppClient {
|
||||
* Creates a new UDP logging client.
|
||||
*
|
||||
* @param clientName the name of the client to be sent in logging requests.
|
||||
* @param port the port on which client will send logging requests.
|
||||
* @param port the port on which client will send logging requests.
|
||||
* @throws UnknownHostException if localhost is unknown
|
||||
*/
|
||||
public UdpLoggingClient(String clientName, int port) throws UnknownHostException {
|
||||
|
||||
@@ -40,9 +40,7 @@ public class LoggingHandler implements ChannelHandler {
|
||||
|
||||
private static final byte[] ACK = "Data logged successfully".getBytes();
|
||||
|
||||
/**
|
||||
* Decodes the received data and logs it on standard console.
|
||||
*/
|
||||
/** Decodes the received data and logs it on standard console. */
|
||||
@Override
|
||||
public void handleChannelRead(AbstractNioChannel channel, Object readObject, SelectionKey key) {
|
||||
/*
|
||||
@@ -61,10 +59,7 @@ public class LoggingHandler implements ChannelHandler {
|
||||
}
|
||||
|
||||
private static void sendReply(
|
||||
AbstractNioChannel channel,
|
||||
DatagramPacket incomingPacket,
|
||||
SelectionKey key
|
||||
) {
|
||||
AbstractNioChannel channel, DatagramPacket incomingPacket, SelectionKey key) {
|
||||
/*
|
||||
* Create a reply acknowledgement datagram packet setting the receiver to the sender of incoming
|
||||
* message.
|
||||
|
||||
@@ -47,8 +47,7 @@ import lombok.Getter;
|
||||
public abstract class AbstractNioChannel {
|
||||
|
||||
private final SelectableChannel channel;
|
||||
@Getter
|
||||
private final ChannelHandler handler;
|
||||
@Getter private final ChannelHandler handler;
|
||||
private final Map<SelectableChannel, Queue<Object>> channelToPendingWrites;
|
||||
private NioReactor reactor;
|
||||
|
||||
@@ -64,9 +63,7 @@ public abstract class AbstractNioChannel {
|
||||
this.channelToPendingWrites = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects the reactor in this channel.
|
||||
*/
|
||||
/** Injects the reactor in this channel. */
|
||||
void setReactor(NioReactor reactor) {
|
||||
this.reactor = reactor;
|
||||
}
|
||||
@@ -125,7 +122,7 @@ public abstract class AbstractNioChannel {
|
||||
* Writes the data to the channel.
|
||||
*
|
||||
* @param pendingWrite the data to be written on channel.
|
||||
* @param key the key which is writable.
|
||||
* @param key the key which is writable.
|
||||
* @throws IOException if any I/O error occurs.
|
||||
*/
|
||||
protected abstract void doWrite(Object pendingWrite, SelectionKey key) throws IOException;
|
||||
@@ -149,13 +146,15 @@ public abstract class AbstractNioChannel {
|
||||
* </pre>
|
||||
*
|
||||
* @param data the data to be written on underlying channel.
|
||||
* @param key the key which is writable.
|
||||
* @param key the key which is writable.
|
||||
*/
|
||||
public void write(Object data, SelectionKey key) {
|
||||
var pendingWrites = this.channelToPendingWrites.get(key.channel());
|
||||
if (pendingWrites == null) {
|
||||
synchronized (this.channelToPendingWrites) {
|
||||
pendingWrites = this.channelToPendingWrites.computeIfAbsent(key.channel(), k -> new ConcurrentLinkedQueue<>());
|
||||
pendingWrites =
|
||||
this.channelToPendingWrites.computeIfAbsent(
|
||||
key.channel(), k -> new ConcurrentLinkedQueue<>());
|
||||
}
|
||||
}
|
||||
pendingWrites.add(data);
|
||||
|
||||
@@ -31,17 +31,16 @@ import java.nio.channels.SelectionKey;
|
||||
* to it by the {@link Dispatcher}. This is where the application logic resides.
|
||||
*
|
||||
* <p>A {@link ChannelHandler} can be associated with one or many {@link AbstractNioChannel}s, and
|
||||
* whenever an event occurs on any of the associated channels, the handler is notified of the
|
||||
* event.
|
||||
* whenever an event occurs on any of the associated channels, the handler is notified of the event.
|
||||
*/
|
||||
public interface ChannelHandler {
|
||||
|
||||
/**
|
||||
* Called when the {@code channel} receives some data from remote peer.
|
||||
*
|
||||
* @param channel the channel from which the data was received.
|
||||
* @param channel the channel from which the data was received.
|
||||
* @param readObject the data read.
|
||||
* @param key the key on which read event occurred.
|
||||
* @param key the key on which read event occurred.
|
||||
*/
|
||||
void handleChannelRead(AbstractNioChannel channel, Object readObject, SelectionKey key);
|
||||
}
|
||||
|
||||
@@ -30,8 +30,9 @@ import java.nio.channels.SelectionKey;
|
||||
* Represents the event dispatching strategy. When {@link NioReactor} senses any event on the
|
||||
* registered {@link AbstractNioChannel}s then it de-multiplexes the event type, read or write or
|
||||
* connect, and then calls the {@link Dispatcher} to dispatch the read events. This decouples the
|
||||
* I/O processing from application specific processing. <br> Dispatcher should call the {@link
|
||||
* ChannelHandler} associated with the channel on which event occurred.
|
||||
* I/O processing from application specific processing. <br>
|
||||
* Dispatcher should call the {@link ChannelHandler} associated with the channel on which event
|
||||
* occurred.
|
||||
*
|
||||
* <p>The application can customize the way in which event is dispatched such as using the reactor
|
||||
* thread to dispatch event to channels or use a worker pool to do the non I/O processing.
|
||||
@@ -47,9 +48,9 @@ public interface Dispatcher {
|
||||
*
|
||||
* <p>The type of <code>readObject</code> depends on the channel on which data was received.
|
||||
*
|
||||
* @param channel on which read event occurred
|
||||
* @param channel on which read event occurred
|
||||
* @param readObject object read by channel
|
||||
* @param key on which event occurred
|
||||
* @param key on which event occurred
|
||||
*/
|
||||
void onChannelReadEvent(AbstractNioChannel channel, Object readObject, SelectionKey key);
|
||||
|
||||
|
||||
@@ -35,9 +35,7 @@ import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* A wrapper over {@link DatagramChannel} which can read and write data on a DatagramChannel.
|
||||
*/
|
||||
/** A wrapper over {@link DatagramChannel} which can read and write data on a DatagramChannel. */
|
||||
@Slf4j
|
||||
public class NioDatagramChannel extends AbstractNioChannel {
|
||||
|
||||
@@ -50,7 +48,7 @@ public class NioDatagramChannel extends AbstractNioChannel {
|
||||
* <p>Note the constructor does not bind the socket, {@link #bind()} method should be called for
|
||||
* binding the socket.
|
||||
*
|
||||
* @param port the port to be bound to listen for incoming datagram requests.
|
||||
* @param port the port to be bound to listen for incoming datagram requests.
|
||||
* @param handler the handler to be used for handling incoming requests on this channel.
|
||||
* @throws IOException if any I/O error occurs.
|
||||
*/
|
||||
@@ -130,16 +128,12 @@ public class NioDatagramChannel extends AbstractNioChannel {
|
||||
super.write(data, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Container of data used for {@link NioDatagramChannel} to communicate with remote peer.
|
||||
*/
|
||||
/** Container of data used for {@link NioDatagramChannel} to communicate with remote peer. */
|
||||
@Getter
|
||||
public static class DatagramPacket {
|
||||
private final ByteBuffer data;
|
||||
@Setter
|
||||
private SocketAddress sender;
|
||||
@Setter
|
||||
private SocketAddress receiver;
|
||||
@Setter private SocketAddress sender;
|
||||
@Setter private SocketAddress receiver;
|
||||
|
||||
/**
|
||||
* Creates a container with underlying data.
|
||||
|
||||
@@ -42,9 +42,8 @@ import lombok.extern.slf4j.Slf4j;
|
||||
* synchronously de-multiplexes the event which can be any of read, write or accept, and dispatches
|
||||
* the event to the appropriate {@link ChannelHandler} using the {@link Dispatcher}.
|
||||
*
|
||||
* <p>Implementation: A NIO reactor runs in its own thread when it is started using {@link
|
||||
* #start()} method. {@link NioReactor} uses {@link Selector} for realizing Synchronous Event
|
||||
* De-multiplexing.
|
||||
* <p>Implementation: A NIO reactor runs in its own thread when it is started using {@link #start()}
|
||||
* method. {@link NioReactor} uses {@link Selector} for realizing Synchronous Event De-multiplexing.
|
||||
*
|
||||
* <p>NOTE: This is one of the ways to implement NIO reactor, and it does not take care of all
|
||||
* possible edge cases which are required in a real application. This implementation is meant to
|
||||
@@ -55,6 +54,7 @@ public class NioReactor {
|
||||
|
||||
private final Selector selector;
|
||||
private final Dispatcher dispatcher;
|
||||
|
||||
/**
|
||||
* All the work of altering the SelectionKey operations and Selector operations are performed in
|
||||
* the context of main event loop of reactor. So when any channel needs to change its readability
|
||||
@@ -62,6 +62,7 @@ public class NioReactor {
|
||||
* the command and executes it in next iteration.
|
||||
*/
|
||||
private final Queue<Runnable> pendingCommands = new ConcurrentLinkedQueue<>();
|
||||
|
||||
private final ExecutorService reactorMain = Executors.newSingleThreadExecutor();
|
||||
|
||||
/**
|
||||
@@ -76,25 +77,24 @@ public class NioReactor {
|
||||
this.selector = Selector.open();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the reactor event loop in a new thread.
|
||||
*/
|
||||
/** Starts the reactor event loop in a new thread. */
|
||||
public void start() {
|
||||
reactorMain.execute(() -> {
|
||||
try {
|
||||
LOGGER.info("Reactor started, waiting for events...");
|
||||
eventLoop();
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("exception in event loop", e);
|
||||
}
|
||||
});
|
||||
reactorMain.execute(
|
||||
() -> {
|
||||
try {
|
||||
LOGGER.info("Reactor started, waiting for events...");
|
||||
eventLoop();
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("exception in event loop", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the reactor and related resources such as dispatcher.
|
||||
*
|
||||
* @throws InterruptedException if interrupted while stopping the reactor.
|
||||
* @throws IOException if any I/O error occurs.
|
||||
* @throws IOException if any I/O error occurs.
|
||||
*/
|
||||
public void stop() throws InterruptedException, IOException {
|
||||
reactorMain.shutdown();
|
||||
@@ -112,7 +112,7 @@ public class NioReactor {
|
||||
* AbstractNioChannel#getInterestedOps()} to know about the interested operation of this channel.
|
||||
*
|
||||
* @param channel a new channel on which reactor will wait for events. The channel must be bound
|
||||
* prior to being registered.
|
||||
* prior to being registered.
|
||||
* @return this
|
||||
* @throws IOException if any I/O error occurs.
|
||||
*/
|
||||
@@ -217,7 +217,7 @@ public class NioReactor {
|
||||
* <p>This is a non-blocking method and does not guarantee that the operations have changed when
|
||||
* this method returns.
|
||||
*
|
||||
* @param key the key for which operations have to be changed.
|
||||
* @param key the key for which operations have to be changed.
|
||||
* @param interestedOps the new interest operations.
|
||||
*/
|
||||
public void changeOps(SelectionKey key, int interestedOps) {
|
||||
@@ -225,9 +225,7 @@ public class NioReactor {
|
||||
selector.wakeup();
|
||||
}
|
||||
|
||||
/**
|
||||
* A command that changes the interested operations of the key provided.
|
||||
*/
|
||||
/** A command that changes the interested operations of the key provided. */
|
||||
static class ChangeKeyOpsCommand implements Runnable {
|
||||
private final SelectionKey key;
|
||||
private final int interestedOps;
|
||||
|
||||
@@ -43,13 +43,13 @@ public class NioServerSocketChannel extends AbstractNioChannel {
|
||||
private final int port;
|
||||
|
||||
/**
|
||||
* Creates a {@link ServerSocketChannel} which will bind at provided port and use
|
||||
* <code>handler</code> to handle incoming events on this channel.
|
||||
* Creates a {@link ServerSocketChannel} which will bind at provided port and use <code>handler
|
||||
* </code> to handle incoming events on this channel.
|
||||
*
|
||||
* <p>Note the constructor does not bind the socket, {@link #bind()} method should be called for
|
||||
* binding the socket.
|
||||
*
|
||||
* @param port the port on which channel will be bound to accept incoming connection requests.
|
||||
* @param port the port on which channel will be bound to accept incoming connection requests.
|
||||
* @param handler the handler that will handle incoming requests on this channel.
|
||||
* @throws IOException if any I/O error occurs.
|
||||
*/
|
||||
@@ -58,7 +58,6 @@ public class NioServerSocketChannel extends AbstractNioChannel {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getInterestedOps() {
|
||||
// being a server socket channel it is interested in accepting connection from remote peers.
|
||||
|
||||
@@ -38,8 +38,9 @@ import java.nio.channels.SelectionKey;
|
||||
public class SameThreadDispatcher implements Dispatcher {
|
||||
|
||||
/**
|
||||
* Dispatches the read event in the context of caller thread. <br> Note this is a blocking call.
|
||||
* It returns only after the associated handler has handled the read event.
|
||||
* Dispatches the read event in the context of caller thread. <br>
|
||||
* Note this is a blocking call. It returns only after the associated handler has handled the read
|
||||
* event.
|
||||
*/
|
||||
@Override
|
||||
public void onChannelReadEvent(AbstractNioChannel channel, Object readObject, SelectionKey key) {
|
||||
@@ -50,9 +51,7 @@ public class SameThreadDispatcher implements Dispatcher {
|
||||
channel.getHandler().handleChannelRead(channel, readObject, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* No resources to free.
|
||||
*/
|
||||
/** No resources to free. */
|
||||
@Override
|
||||
public void stop() {
|
||||
// no-op
|
||||
|
||||
@@ -49,8 +49,9 @@ public class ThreadPoolDispatcher implements Dispatcher {
|
||||
|
||||
/**
|
||||
* Submits the work of dispatching the read event to worker pool, where it gets picked up by
|
||||
* worker threads. <br> Note that this is a non-blocking call and returns immediately. It is not
|
||||
* guaranteed that the event has been handled by associated handler.
|
||||
* worker threads. <br>
|
||||
* Note that this is a non-blocking call and returns immediately. It is not guaranteed that the
|
||||
* event has been handled by associated handler.
|
||||
*/
|
||||
@Override
|
||||
public void onChannelReadEvent(AbstractNioChannel channel, Object readObject, SelectionKey key) {
|
||||
|
||||
@@ -42,7 +42,7 @@ class ReactorTest {
|
||||
/**
|
||||
* Test the application using pooled thread dispatcher.
|
||||
*
|
||||
* @throws IOException if any I/O error occurs.
|
||||
* @throws IOException if any I/O error occurs.
|
||||
* @throws InterruptedException if interrupted while stopping the application.
|
||||
*/
|
||||
@Test
|
||||
@@ -74,7 +74,7 @@ class ReactorTest {
|
||||
/**
|
||||
* Test the application using same thread dispatcher.
|
||||
*
|
||||
* @throws IOException if any I/O error occurs.
|
||||
* @throws IOException if any I/O error occurs.
|
||||
* @throws InterruptedException if interrupted while stopping the application.
|
||||
*/
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user