From 28f9e5af812a69385a34b86535566b5e8e2d49a4 Mon Sep 17 00:00:00 2001
From: Dean Chapman
+ * The consumer provides:
+ *
+ *
+ *
+ * Architecture: + * + *
+ * Events → Database → EventBus → VertxEventBusConsumer → Subscriber + * ↑ + * CatchupService (for missed events) + *+ * + * + *
+ * Example usage: + *
+ * + *{@code
+ * // Create configuration
+ * ConfigData config = new ConfigData("myapp", Set.of("orders"), ...);
+ *
+ * // Create consumer
+ * VertxEventBusConsumer consumer = new VertxEventBusConsumer(config, OpenTelemetry.noop(), 30);
+ *
+ * // Subscribe to events
+ * consumer.subscribe("orders", event -> {
+ * System.out.println("Received: " + event);
+ * });
+ *
+ * // Start consuming
+ * consumer.start(Set.of("orders"), dataSource);
+ * }
+ */
+public class VertxEventBusConsumer implements AutoCloseable {
+ private static final Logger logger = LoggerFactory.getLogger(VertxEventBusConsumer.class);
+
+ private final Vertx vertx;
+ private final EventBus eventBus;
+ private final ConfigData config;
+ private final OpenTelemetry openTelemetry;
+ private final int catchupIntervalSeconds;
+ private final Map+ * The service listens on specific EventBus addresses and delegates to the + * underlying CatchupServerInterface implementation. All requests and responses + * are JSON-encoded for simplicity and debugging. + *
+ * + *+ * EventBus addresses: + *
+ * Example usage: + *
+ * + *{@code
+ * CatchupServerInterface catchupServer = new CatchupServer(dataSource);
+ * EventBus eventBus = vertx.eventBus();
+ *
+ * EventBusCatchupService service = new EventBusCatchupService(catchupServer, eventBus);
+ * service.start();
+ *
+ * // Service is now listening for catchup requests on the EventBus
+ * }
+ */
+public class EventBusCatchupService {
+ private static final Logger logger = LoggerFactory.getLogger(EventBusCatchupService.class);
+
+ private static final String FETCH_EVENTS_ADDRESS = "catchup.fetchEvents";
+ private static final String GET_LATEST_MESSAGE_ID_ADDRESS = "catchup.getLatestMessageId";
+
+ private final CatchupServerInterface catchupServer;
+ private final EventBus eventBus;
+ private MessageConsumer{@code
+ * {
+ * "fromId": 100,
+ * "toId": 200,
+ * "limit": 50,
+ * "topic": "orders"
+ * }
+ * }
+ *
+ * @param message The EventBus message containing the request
+ */
+ private void handleFetchEvents(Message{@code
+ * {
+ * "topic": "orders"
+ * }
+ * }
+ *
+ * @param message The EventBus message containing the request
+ */
+ private void handleGetLatestMessageId(Message+ * This broker provides a dual-write pattern: + *
+ * Subscribers receive events from the EventBus, providing low-latency + * event delivery while maintaining persistence guarantees. + *
+ * + *+ * Example usage: + *
+ * + *{@code
+ * Vertx vertx = Vertx.vertx();
+ * DataSource dataSource = // configure datasource
+ * AsyncExecutor executor = new DefaultExecutor();
+ *
+ * EventBusMessageBroker broker = new EventBusMessageBroker(
+ * vertx, dataSource, executor, OpenTelemetry.noop(), "my-broker");
+ *
+ * // Subscribe to events
+ * broker.subscribe("orders", event -> {
+ * System.out.println("Received: " + event);
+ * });
+ *
+ * // Publish events (persisted + real-time)
+ * Event event = Event.create(...);
+ * broker.publish("orders", event);
+ * }
+ */
+public class EventBusMessageBroker extends EventMessageBroker {
+ private static final Logger logger = LoggerFactory.getLogger(EventBusMessageBroker.class);
+
+ private final Vertx vertx;
+ private final EventBus eventBus;
+ private final DataSource dataSource;
+ private final Map+ * This client provides a synchronous API that internally uses the + * asynchronous EventBus request-reply pattern. All operations have + * configurable timeouts to prevent indefinite blocking. + *
+ * + *+ * The client communicates with EventBusCatchupService using JSON-encoded + * messages over the EventBus, making it suitable for both local and + * distributed deployments. + *
+ * + *+ * Example usage: + *
+ * + *{@code
+ * EventBus eventBus = vertx.eventBus();
+ * EventBusCatchupClient client = new EventBusCatchupClient(eventBus);
+ *
+ * // Fetch events
+ * List events = client.fetchEvents(100L, 200L, 50, "orders");
+ *
+ * // Get latest message ID
+ * long latestId = client.getLatestMessageId("orders");
+ * }
+ */
+public class EventBusCatchupClient implements CatchupServerInterface {
+ private static final Logger logger = LoggerFactory.getLogger(EventBusCatchupClient.class);
+
+ private static final String FETCH_EVENTS_ADDRESS = "catchup.fetchEvents";
+ private static final String GET_LATEST_MESSAGE_ID_ADDRESS = "catchup.getLatestMessageId";
+ private static final long DEFAULT_TIMEOUT_SECONDS = 30;
+
+ private final EventBus eventBus;
+ private final long timeoutSeconds;
+
+ /**
+ * Creates a new EventBusCatchupClient with default timeout.
+ *
+ * @param eventBus The Vert.x EventBus to use for communication
+ */
+ public EventBusCatchupClient(EventBus eventBus) {
+ this(eventBus, DEFAULT_TIMEOUT_SECONDS);
+ }
+
+ /**
+ * Creates a new EventBusCatchupClient with custom timeout.
+ *
+ * @param eventBus The Vert.x EventBus to use for communication
+ * @param timeoutSeconds Timeout in seconds for EventBus requests
+ */
+ public EventBusCatchupClient(EventBus eventBus, long timeoutSeconds) {
+ this.eventBus = eventBus;
+ this.timeoutSeconds = timeoutSeconds;
+ }
+
+ /**
+ * Fetches events within the specified ID range for a topic.
+ * Sends a request to the EventBusCatchupService and waits for the response.
+ *
+ * @param fromId The starting event ID (inclusive)
+ * @param toId The ending event ID (inclusive)
+ * @param limit Maximum number of events to return
+ * @param topic The topic to fetch events from
+ * @return List of events within the specified range
+ * @throws Exception If the request fails or times out
+ */
+ @Override
+ public List+ * The codec uses JSON serialization for simplicity and debugging ease. + * Events are encoded as JSON strings with a length prefix for efficient parsing. + *
+ * + *+ * Wire format: + * [4 bytes: length][JSON string] + *
+ */ +public class EventCodec implements MessageCodec- * The consumer provides: - *
- * Architecture: - * - *
- * Events → Database → EventBus → VertxEventBusConsumer → Subscriber - * ↑ - * CatchupService (for missed events) - *- * - * - *
- * Example usage: - *
- * - *{@code
- * // Create configuration
- * ConfigData config = new ConfigData("myapp", Set.of("orders"), ...);
- *
- * // Create consumer
- * VertxEventBusConsumer consumer = new VertxEventBusConsumer(config, OpenTelemetry.noop(), 30);
- *
- * // Subscribe to events
- * consumer.subscribe("orders", event -> {
- * System.out.println("Received: " + event);
- * });
- *
- * // Start consuming
- * consumer.start(Set.of("orders"), dataSource);
- * }
- */
-public class VertxEventBusConsumer implements AutoCloseable {
- private static final Logger logger = LoggerFactory.getLogger(VertxEventBusConsumer.class);
-
- private final Vertx vertx;
- private final EventBus eventBus;
- private final ConfigData config;
- private final OpenTelemetry openTelemetry;
- private final int catchupIntervalSeconds;
- private final Map{@code
* {
* "fromId": 100,
@@ -137,43 +141,44 @@ public void stop() {
*/
private void handleFetchEvents(Message message) {
JsonObject request = message.body();
-
+
try {
long fromId = request.getLong("fromId");
long toId = request.getLong("toId");
int limit = request.getInteger("limit");
String topic = request.getString("topic");
-
+
logger.atDebug()
- .addArgument(fromId)
- .addArgument(toId)
- .addArgument(limit)
- .addArgument(topic)
- .log("Handling fetchEvents request: fromId={}, toId={}, limit={}, topic={}");
-
+ .addArgument(fromId)
+ .addArgument(toId)
+ .addArgument(limit)
+ .addArgument(topic)
+ .log("Handling fetchEvents request: fromId={}, toId={}, limit={}, topic={}");
+
List events = catchupServer.fetchEvents(fromId, toId, limit, topic);
-
+
// Serialize events to JSON and reply
String eventsJson = Json.encode(events);
message.reply(eventsJson);
-
+
logger.atDebug()
- .addArgument(events.size())
- .addArgument(topic)
- .log("Successfully fetched {} events for topic {}", events.size(), topic);
-
+ .addArgument(events.size())
+ .addArgument(topic)
+ .log("Successfully fetched {} events for topic {}", events.size(), topic);
+
} catch (Exception e) {
logger.atError()
- .setCause(e)
- .log("Error handling fetchEvents request");
+ .setCause(e)
+ .log("Error handling fetchEvents request");
message.fail(500, e.getMessage());
}
}
-
+
/**
* Handles getLatestMessageId requests from the EventBus.
*
* Expected request format:
+ *
* {@code
* {
* "topic": "orders"
@@ -184,29 +189,29 @@ private void handleFetchEvents(Message message) {
*/
private void handleGetLatestMessageId(Message message) {
JsonObject request = message.body();
-
+
try {
String topic = request.getString("topic");
-
+
logger.atDebug()
- .addArgument(topic)
- .log("Handling getLatestMessageId request for topic: {}");
-
+ .addArgument(topic)
+ .log("Handling getLatestMessageId request for topic: {}");
+
long latestId = catchupServer.getLatestMessageId(topic);
-
+
// Create response with latest ID
JsonObject response = new JsonObject().put("latestId", latestId);
message.reply(response);
-
+
logger.atDebug()
- .addArgument(latestId)
- .addArgument(topic)
- .log("Successfully retrieved latest message ID {} for topic {}", latestId, topic);
-
+ .addArgument(latestId)
+ .addArgument(topic)
+ .log("Successfully retrieved latest message ID {} for topic {}", latestId, topic);
+
} catch (Exception e) {
logger.atError()
- .setCause(e)
- .log("Error handling getLatestMessageId request");
+ .setCause(e)
+ .log("Error handling getLatestMessageId request");
message.fail(500, e.getMessage());
}
}
diff --git a/vertx/src/main/java/com/p14n/postevent/adapter/EventBusMessageBroker.java b/vertx/src/main/java/com/p14n/postevent/vertx/adapter/EventBusMessageBroker.java
similarity index 98%
rename from vertx/src/main/java/com/p14n/postevent/adapter/EventBusMessageBroker.java
rename to vertx/src/main/java/com/p14n/postevent/vertx/adapter/EventBusMessageBroker.java
index 8fe7437..780f70c 100644
--- a/vertx/src/main/java/com/p14n/postevent/adapter/EventBusMessageBroker.java
+++ b/vertx/src/main/java/com/p14n/postevent/vertx/adapter/EventBusMessageBroker.java
@@ -1,13 +1,12 @@
-package com.p14n.postevent.adapter;
+package com.p14n.postevent.vertx.adapter;
import com.p14n.postevent.Publisher;
import com.p14n.postevent.broker.AsyncExecutor;
import com.p14n.postevent.broker.EventMessageBroker;
import com.p14n.postevent.broker.MessageSubscriber;
-import com.p14n.postevent.codec.EventCodec;
+import com.p14n.postevent.vertx.codec.EventCodec;
import com.p14n.postevent.data.Event;
import io.opentelemetry.api.OpenTelemetry;
-import io.vertx.core.Vertx;
import io.vertx.core.eventbus.EventBus;
import io.vertx.core.eventbus.MessageConsumer;
diff --git a/vertx/src/main/java/com/p14n/postevent/client/EventBusCatchupClient.java b/vertx/src/main/java/com/p14n/postevent/vertx/client/EventBusCatchupClient.java
similarity index 94%
rename from vertx/src/main/java/com/p14n/postevent/client/EventBusCatchupClient.java
rename to vertx/src/main/java/com/p14n/postevent/vertx/client/EventBusCatchupClient.java
index 8e1839e..bcc96d7 100644
--- a/vertx/src/main/java/com/p14n/postevent/client/EventBusCatchupClient.java
+++ b/vertx/src/main/java/com/p14n/postevent/vertx/client/EventBusCatchupClient.java
@@ -1,4 +1,4 @@
-package com.p14n.postevent.client;
+package com.p14n.postevent.vertx.client;
import com.p14n.postevent.catchup.CatchupServerInterface;
import com.p14n.postevent.data.Event;
@@ -12,6 +12,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import static com.p14n.postevent.vertx.adapter.EventBusCatchupService.FETCH_EVENTS_ADDRESS;
+import static com.p14n.postevent.vertx.adapter.EventBusCatchupService.GET_LATEST_MESSAGE_ID_ADDRESS;
/**
* Client implementation of CatchupServerInterface that sends requests
@@ -47,8 +49,6 @@
public class EventBusCatchupClient implements CatchupServerInterface {
private static final Logger logger = LoggerFactory.getLogger(EventBusCatchupClient.class);
- private static final String FETCH_EVENTS_ADDRESS = "catchup.fetchEvents";
- private static final String GET_LATEST_MESSAGE_ID_ADDRESS = "catchup.getLatestMessageId";
private static final long DEFAULT_TIMEOUT_SECONDS = 30;
private final EventBus eventBus;
@@ -103,7 +103,7 @@ public List fetchEvents(long fromId, long toId, int limit, String topic)
try {
CompletableFuture future = new CompletableFuture<>();
- eventBus.request(FETCH_EVENTS_ADDRESS, request, reply -> {
+ eventBus.request(FETCH_EVENTS_ADDRESS+topic, request, reply -> {
if (reply.succeeded()) {
String eventsJson = (String) reply.result().body();
future.complete(eventsJson);
@@ -151,7 +151,7 @@ public long getLatestMessageId(String topic) {
try {
CompletableFuture future = new CompletableFuture<>();
- eventBus.request(GET_LATEST_MESSAGE_ID_ADDRESS, request, reply -> {
+ eventBus.request(GET_LATEST_MESSAGE_ID_ADDRESS+topic, request, reply -> {
if (reply.succeeded()) {
JsonObject response = (JsonObject) reply.result().body();
future.complete(response);
diff --git a/vertx/src/main/java/com/p14n/postevent/codec/EventCodec.java b/vertx/src/main/java/com/p14n/postevent/vertx/codec/EventCodec.java
similarity index 98%
rename from vertx/src/main/java/com/p14n/postevent/codec/EventCodec.java
rename to vertx/src/main/java/com/p14n/postevent/vertx/codec/EventCodec.java
index 2e7af58..4945481 100644
--- a/vertx/src/main/java/com/p14n/postevent/codec/EventCodec.java
+++ b/vertx/src/main/java/com/p14n/postevent/vertx/codec/EventCodec.java
@@ -1,4 +1,4 @@
-package com.p14n.postevent.codec;
+package com.p14n.postevent.vertx.codec;
import com.p14n.postevent.data.Event;
import io.vertx.core.buffer.Buffer;
diff --git a/vertx/src/main/java/com/p14n/postevent/example/VertxConsumerExample.java b/vertx/src/test/java/com/p14n/postevent/vertx/example/VertxConsumerExample.java
similarity index 52%
rename from vertx/src/main/java/com/p14n/postevent/example/VertxConsumerExample.java
rename to vertx/src/test/java/com/p14n/postevent/vertx/example/VertxConsumerExample.java
index d7f090c..bdfa5dc 100644
--- a/vertx/src/main/java/com/p14n/postevent/example/VertxConsumerExample.java
+++ b/vertx/src/test/java/com/p14n/postevent/vertx/example/VertxConsumerExample.java
@@ -1,22 +1,27 @@
-package com.p14n.postevent.example;
+package com.p14n.postevent.vertx.example;
-import com.p14n.postevent.VertxConsumerServer;
-import com.p14n.postevent.VertxPersistentConsumer;
-import com.p14n.postevent.adapter.EventBusMessageBroker;
+import com.p14n.postevent.vertx.VertxConsumerServer;
+import com.p14n.postevent.vertx.VertxPersistentConsumer;
+import com.p14n.postevent.vertx.adapter.EventBusMessageBroker;
import com.p14n.postevent.broker.DefaultExecutor;
import com.p14n.postevent.data.Event;
-import com.p14n.postevent.db.DatabaseSetup;
import io.opentelemetry.api.OpenTelemetry;
import io.vertx.core.Vertx;
import javax.sql.DataSource;
import java.io.IOException;
+import java.time.Instant;
import java.util.Set;
import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import io.zonky.test.db.postgres.embedded.EmbeddedPostgres;
public class VertxConsumerExample {
public static void start(DataSource ds) throws IOException, InterruptedException {
+
DefaultExecutor executor = new DefaultExecutor(2);
var ot = OpenTelemetry.noop();
var vertx = Vertx.vertx();
@@ -25,10 +30,22 @@ public static void start(DataSource ds) throws IOException, InterruptedException
var server = new VertxConsumerServer(ds,executor,ot);
server.start(vertx.eventBus(),mb,topics);
+ var latch = new CountDownLatch(2);
+
var client = new VertxPersistentConsumer(ot,executor,20);
client.start(topics,ds,vertx.eventBus(),mb);
+
+ mb.publish("order", Event.create(UUID.randomUUID().toString(),
+ "test",
+ "test",
+ "text",
+ null,
+ "test",
+ "hello".getBytes(), Instant.now(),1L ,"order",null));
+
client.subscribe("order", message -> {
System.out.println("Got message");
+ latch.countDown();
});
mb.publish("order", Event.create(UUID.randomUUID().toString(),
@@ -37,6 +54,17 @@ public static void start(DataSource ds) throws IOException, InterruptedException
"text",
null,
"test",
- "hello".getBytes(),null));
+ "hello".getBytes(), Instant.now(),2L ,"order",null));
+
+ latch.await(10, TimeUnit.SECONDS);
+ }
+
+ public static void main(String[] args){
+ try(var pg = EmbeddedPostgres.start()){
+ start(pg.getPostgresDatabase());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
}
}
From 295cc6298094aa62bc1f2d9be2fc5cd0c7145260 Mon Sep 17 00:00:00 2001
From: Dean Chapman
Date: Thu, 18 Sep 2025 21:46:52 +0100
Subject: [PATCH 4/9] Implement Autoclosable on new classes
---
vertx/README.md | 4 +-
.../postevent/vertx/VertxConsumerServer.java | 16 ++++-
.../vertx/VertxPersistentConsumer.java | 5 --
.../vertx/example/VertxConsumerExample.java | 67 ++++++++++---------
4 files changed, 52 insertions(+), 40 deletions(-)
diff --git a/vertx/README.md b/vertx/README.md
index ceb5ec8..dfab57a 100644
--- a/vertx/README.md
+++ b/vertx/README.md
@@ -8,7 +8,7 @@ VertxConsumerServer - sets up DDL for given topics and starts catchup for those
VertxPersistentConsumer - consumes events from vertx eventbus. Creates system event bus and catchup client, handles translation between vertx and postevent on the transactional consumer side.
Todo
-[] Implement autoclose on new classes
-[] Adapt classes to use vertx threading model
+ - [x] Implement autoclose on new classes
+ - [ ] Adapt classes to use vertx threading model
diff --git a/vertx/src/main/java/com/p14n/postevent/vertx/VertxConsumerServer.java b/vertx/src/main/java/com/p14n/postevent/vertx/VertxConsumerServer.java
index 5448155..c9d254b 100644
--- a/vertx/src/main/java/com/p14n/postevent/vertx/VertxConsumerServer.java
+++ b/vertx/src/main/java/com/p14n/postevent/vertx/VertxConsumerServer.java
@@ -15,7 +15,7 @@
import java.util.List;
import java.util.Set;
-public class VertxConsumerServer {
+public class VertxConsumerServer implements AutoCloseable {
private static final Logger logger = LoggerFactory.getLogger(VertxConsumerServer.class);
private DataSource ds;
@@ -40,8 +40,20 @@ public void start(EventBus eb, EventBusMessageBroker mb, Set topics) thr
closeables = List.of(catchupService, mb, asyncExecutor);
System.out.println("🌐 Vert.x EventBus server started");
- //System.out.println("🛑 Vert.x EventBus server stopped");
}
+ @Override
+ public void close() {
+ if(closeables != null){
+ for(var c : closeables){
+ try {
+ c.close();
+ } catch (Exception e){
+
+ }
+ }
+ }
+ System.out.println("🛑 Vert.x EventBus server stopped");
+ }
}
diff --git a/vertx/src/main/java/com/p14n/postevent/vertx/VertxPersistentConsumer.java b/vertx/src/main/java/com/p14n/postevent/vertx/VertxPersistentConsumer.java
index 4bd92e3..7130cef 100644
--- a/vertx/src/main/java/com/p14n/postevent/vertx/VertxPersistentConsumer.java
+++ b/vertx/src/main/java/com/p14n/postevent/vertx/VertxPersistentConsumer.java
@@ -51,11 +51,6 @@ public void start(Set topics, DataSource ds,EventBus eb, EventBusMessage
seb = new SystemEventBroker(asyncExecutor, ot);
tb = new TransactionalBroker(ds, asyncExecutor, ot, seb);
var pb = new PersistentBroker<>(tb, ds, seb);
- //var client = new MessageBrokerGrpcClient(asyncExecutor, ot, channel); // needs fixed threads
- //var catchupClient = new CatchupGrpcClient(channel);
-
- //Create a client that can listen to system events, register with seb
- //register the pb with the eb for all topics
var catchupClient = new EventBusCatchupClient(eb);
for (var topic : topics) {
diff --git a/vertx/src/test/java/com/p14n/postevent/vertx/example/VertxConsumerExample.java b/vertx/src/test/java/com/p14n/postevent/vertx/example/VertxConsumerExample.java
index bdfa5dc..830754b 100644
--- a/vertx/src/test/java/com/p14n/postevent/vertx/example/VertxConsumerExample.java
+++ b/vertx/src/test/java/com/p14n/postevent/vertx/example/VertxConsumerExample.java
@@ -26,37 +26,42 @@ public static void start(DataSource ds) throws IOException, InterruptedException
var ot = OpenTelemetry.noop();
var vertx = Vertx.vertx();
var topics = Set.of("order");
- var mb = new EventBusMessageBroker(vertx.eventBus(),ds,executor, ot, "consumer_server");
- var server = new VertxConsumerServer(ds,executor,ot);
- server.start(vertx.eventBus(),mb,topics);
-
- var latch = new CountDownLatch(2);
-
- var client = new VertxPersistentConsumer(ot,executor,20);
- client.start(topics,ds,vertx.eventBus(),mb);
-
- mb.publish("order", Event.create(UUID.randomUUID().toString(),
- "test",
- "test",
- "text",
- null,
- "test",
- "hello".getBytes(), Instant.now(),1L ,"order",null));
-
- client.subscribe("order", message -> {
- System.out.println("Got message");
- latch.countDown();
- });
-
- mb.publish("order", Event.create(UUID.randomUUID().toString(),
- "test",
- "test",
- "text",
- null,
- "test",
- "hello".getBytes(), Instant.now(),2L ,"order",null));
-
- latch.await(10, TimeUnit.SECONDS);
+
+ try(var mb = new EventBusMessageBroker(vertx.eventBus(),ds,executor, ot, "consumer_server");
+ var server = new VertxConsumerServer(ds,executor,ot);
+ var client = new VertxPersistentConsumer(ot,executor,20)){
+
+ server.start(vertx.eventBus(),mb,topics);
+
+ var latch = new CountDownLatch(2);
+
+ client.start(topics,ds,vertx.eventBus(),mb);
+
+ mb.publish("order", Event.create(UUID.randomUUID().toString(),
+ "test",
+ "test",
+ "text",
+ null,
+ "test",
+ "hello".getBytes(), Instant.now(),1L ,"order",null));
+
+ client.subscribe("order", message -> {
+ System.out.println("Got message");
+ latch.countDown();
+ });
+
+ mb.publish("order", Event.create(UUID.randomUUID().toString(),
+ "test",
+ "test",
+ "text",
+ null,
+ "test",
+ "hello".getBytes(), Instant.now(),2L ,"order",null));
+
+ latch.await(10, TimeUnit.SECONDS);
+
+ }
+ vertx.close();
}
public static void main(String[] args){
From 51ac0398c3bbc68a86da664cad461489d2aa8578 Mon Sep 17 00:00:00 2001
From: Dean Chapman
Date: Thu, 18 Sep 2025 22:14:20 +0100
Subject: [PATCH 5/9] Don't block the event loop
---
.../postevent/vertx/VertxConsumerServer.java | 2 +-
.../vertx/adapter/EventBusCatchupService.java | 37 ++++++++++----
.../vertx/adapter/EventBusMessageBroker.java | 50 ++++++++++++++-----
.../vertx/client/EventBusCatchupClient.java | 4 +-
4 files changed, 69 insertions(+), 24 deletions(-)
diff --git a/vertx/src/main/java/com/p14n/postevent/vertx/VertxConsumerServer.java b/vertx/src/main/java/com/p14n/postevent/vertx/VertxConsumerServer.java
index c9d254b..7740aac 100644
--- a/vertx/src/main/java/com/p14n/postevent/vertx/VertxConsumerServer.java
+++ b/vertx/src/main/java/com/p14n/postevent/vertx/VertxConsumerServer.java
@@ -36,7 +36,7 @@ public void start(EventBus eb, EventBusMessageBroker mb, Set topics) thr
var db = new DatabaseSetup(ds);
db.setupServer(topics);
var catchupServer = new CatchupServer(ds);
- var catchupService = new EventBusCatchupService(catchupServer,eb,topics);
+ var catchupService = new EventBusCatchupService(catchupServer,eb,topics,this.asyncExecutor);
closeables = List.of(catchupService, mb, asyncExecutor);
System.out.println("🌐 Vert.x EventBus server started");
diff --git a/vertx/src/main/java/com/p14n/postevent/vertx/adapter/EventBusCatchupService.java b/vertx/src/main/java/com/p14n/postevent/vertx/adapter/EventBusCatchupService.java
index 39eac9d..44a25ab 100644
--- a/vertx/src/main/java/com/p14n/postevent/vertx/adapter/EventBusCatchupService.java
+++ b/vertx/src/main/java/com/p14n/postevent/vertx/adapter/EventBusCatchupService.java
@@ -1,5 +1,6 @@
package com.p14n.postevent.vertx.adapter;
+import com.p14n.postevent.broker.AsyncExecutor;
import com.p14n.postevent.catchup.CatchupServerInterface;
import com.p14n.postevent.data.Event;
import io.vertx.core.eventbus.EventBus;
@@ -60,6 +61,7 @@ public class EventBusCatchupService implements AutoCloseable {
private List> fetchEventsConsumers;
private List> getLatestMessageIdConsumers;
private Set topics;
+ private AsyncExecutor executor;
/**
* Creates a new EventBusCatchupService.
@@ -67,10 +69,14 @@ public class EventBusCatchupService implements AutoCloseable {
* @param catchupServer The underlying catchup server implementation
* @param eventBus The Vert.x EventBus to use for messaging
*/
- public EventBusCatchupService(CatchupServerInterface catchupServer, EventBus eventBus, Set topics) {
+ public EventBusCatchupService(CatchupServerInterface catchupServer,
+ EventBus eventBus,
+ Set topics,
+ AsyncExecutor executor) {
this.catchupServer = catchupServer;
this.eventBus = eventBus;
this.topics = topics;
+ this.executor = executor;
}
/**
@@ -155,16 +161,29 @@ private void handleFetchEvents(Message message) {
.addArgument(topic)
.log("Handling fetchEvents request: fromId={}, toId={}, limit={}, topic={}");
- List events = catchupServer.fetchEvents(fromId, toId, limit, topic);
+ executor.submit(() -> {
- // Serialize events to JSON and reply
- String eventsJson = Json.encode(events);
- message.reply(eventsJson);
+ try{
+ List events = catchupServer.fetchEvents(fromId, toId, limit, topic);
- logger.atDebug()
- .addArgument(events.size())
- .addArgument(topic)
- .log("Successfully fetched {} events for topic {}", events.size(), topic);
+ // Serialize events to JSON and reply
+ String eventsJson = Json.encode(events);
+ message.reply(eventsJson);
+
+ logger.atDebug()
+ .addArgument(events.size())
+ .addArgument(topic)
+ .log("Successfully fetched {} events for topic {}", events.size(), topic);
+
+ } catch (Exception e){
+ logger.atError()
+ .setCause(e)
+ .log("Error handling fetchEvents request");
+ message.fail(500, e.getMessage());
+
+ }
+ return null;
+ });
} catch (Exception e) {
logger.atError()
diff --git a/vertx/src/main/java/com/p14n/postevent/vertx/adapter/EventBusMessageBroker.java b/vertx/src/main/java/com/p14n/postevent/vertx/adapter/EventBusMessageBroker.java
index 780f70c..5e585b3 100644
--- a/vertx/src/main/java/com/p14n/postevent/vertx/adapter/EventBusMessageBroker.java
+++ b/vertx/src/main/java/com/p14n/postevent/vertx/adapter/EventBusMessageBroker.java
@@ -63,6 +63,7 @@ public class EventBusMessageBroker extends EventMessageBroker {
private final EventBus eventBus;
private final DataSource dataSource;
private final Map> consumers = new ConcurrentHashMap<>();
+ private final AsyncExecutor executor;
/**
* Creates a new EventBusMessageBroker.
@@ -78,7 +79,7 @@ public EventBusMessageBroker(EventBus eventBus, DataSource dataSource, AsyncExec
super(executor, ot, name);
this.eventBus = eventBus;
this.dataSource = dataSource;
-
+ this.executor = executor;
// Register the Event codec for EventBus serialization
eventBus.registerDefaultCodec(Event.class, new EventCodec());
@@ -87,7 +88,6 @@ public EventBusMessageBroker(EventBus eventBus, DataSource dataSource, AsyncExec
.log("EventBusMessageBroker initialized: {}");
}
-
/**
* Publishes an event using the dual-write pattern.
* The event is first persisted to the database, then published to the EventBus.
@@ -103,17 +103,32 @@ public void publish(String topic, Event event) {
.log("Publishing event to topic {} with id {}");
try {
- // First, persist to database using existing Publisher
- Publisher.publish(event, dataSource, topic);
- // Then, publish to EventBus for real-time distribution
- String eventBusAddress = "events." + topic;
- eventBus.publish(eventBusAddress, event);
+ executor.submit(() -> {
+ try {
+ Publisher.publish(event, dataSource, topic);
- logger.atDebug()
- .addArgument(topic)
- .addArgument(event.id())
- .log("Successfully published event to topic {} with id {}");
+ // Then, publish to EventBus for real-time distribution
+ String eventBusAddress = "events." + topic;
+ eventBus.publish(eventBusAddress, event);
+
+ logger.atDebug()
+ .addArgument(topic)
+ .addArgument(event.id())
+ .log("Successfully published event to topic {} with id {}");
+
+ } catch (Exception e) {
+ logger.atError()
+ .addArgument(topic)
+ .addArgument(event.id())
+ .setCause(e)
+ .log("Failed to publish event to topic {} with id {}");
+
+ }
+ return null;
+ });
+
+ // First, persist to database using existing Publisher
} catch (Exception e) {
logger.atError()
@@ -148,7 +163,18 @@ public void subscribeToEventBus(String topic, MessageSubscriber subscribe
.log("Received event on topic {} with id {}");
try {
- subscriber.onMessage(event);
+ executor.submit(() -> {
+ try {
+ subscriber.onMessage(event);
+ } catch (Exception e) {
+ logger.atError()
+ .addArgument(topic)
+ .addArgument(event.id())
+ .setCause(e)
+ .log("Error processing event on topic {} with id {}");
+ }
+ return null;
+ });
} catch (Exception e) {
logger.atError()
.addArgument(topic)
diff --git a/vertx/src/main/java/com/p14n/postevent/vertx/client/EventBusCatchupClient.java b/vertx/src/main/java/com/p14n/postevent/vertx/client/EventBusCatchupClient.java
index bcc96d7..1670a53 100644
--- a/vertx/src/main/java/com/p14n/postevent/vertx/client/EventBusCatchupClient.java
+++ b/vertx/src/main/java/com/p14n/postevent/vertx/client/EventBusCatchupClient.java
@@ -103,7 +103,7 @@ public List fetchEvents(long fromId, long toId, int limit, String topic)
try {
CompletableFuture future = new CompletableFuture<>();
- eventBus.request(FETCH_EVENTS_ADDRESS+topic, request, reply -> {
+ eventBus.request(FETCH_EVENTS_ADDRESS + topic, request, reply -> {
if (reply.succeeded()) {
String eventsJson = (String) reply.result().body();
future.complete(eventsJson);
@@ -151,7 +151,7 @@ public long getLatestMessageId(String topic) {
try {
CompletableFuture future = new CompletableFuture<>();
- eventBus.request(GET_LATEST_MESSAGE_ID_ADDRESS+topic, request, reply -> {
+ eventBus.request(GET_LATEST_MESSAGE_ID_ADDRESS + topic, request, reply -> {
if (reply.succeeded()) {
JsonObject response = (JsonObject) reply.result().body();
future.complete(response);
From 9883ccd5d0e9a6fc59e1316b077a33e9186de97b Mon Sep 17 00:00:00 2001
From: Dean Chapman
Date: Fri, 19 Sep 2025 08:13:37 +0100
Subject: [PATCH 6/9] Refactor dependencies
---
app/src/main/java/com/p14n/postevent/App.java | 4 +--
core/build.gradle | 4 ---
.../postevent/broker/DefaultExecutor.java | 23 +++++++++++----
.../com/p14n/postevent/db/DatabaseSetup.java | 28 ++++++-------------
debezium/build.gradle | 3 ++
.../postevent/LocalPersistentConsumer.java | 3 +-
.../java/com/p14n/postevent/db/PoolSetup.java | 23 +++++++++++++++
.../com/p14n/postevent/ConsumerServer.java | 4 +--
vertx/build.gradle | 3 --
9 files changed, 58 insertions(+), 37 deletions(-)
create mode 100644 debezium/src/main/java/com/p14n/postevent/db/PoolSetup.java
diff --git a/app/src/main/java/com/p14n/postevent/App.java b/app/src/main/java/com/p14n/postevent/App.java
index 268ed92..3e5166d 100644
--- a/app/src/main/java/com/p14n/postevent/App.java
+++ b/app/src/main/java/com/p14n/postevent/App.java
@@ -21,7 +21,7 @@
import com.p14n.postevent.data.ConfigData;
import com.p14n.postevent.data.Event;
import com.p14n.postevent.db.DatabaseSetup;
-
+import com.p14n.postevent.db.PoolSetup;
import com.p14n.postevent.telemetry.OpenTelemetryFunctions;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Tracer;
@@ -119,7 +119,7 @@ private static void run(String affinity, String[] write, String[] read, String d
RemotePersistentConsumer cc = null;
var ot = Opentelemetry.create("postevent");
- var ds = JdbcTelemetry.create(ot).wrap(DatabaseSetup.createPool(cfg));
+ var ds = JdbcTelemetry.create(ot).wrap(PoolSetup.createPool(cfg));
try {
if (write.length > 0) {
diff --git a/core/build.gradle b/core/build.gradle
index 8c0d204..a02acbe 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -17,12 +17,8 @@ repositories {
dependencies {
// Core database and connection pooling
- implementation 'com.zaxxer:HikariCP:6.2.1'
implementation 'org.slf4j:slf4j-api:2.0.9'
- // Guava for utilities
- implementation 'com.google.guava:guava:32.0.0-jre'
-
// OpenTelemetry core dependencies
implementation 'io.opentelemetry:opentelemetry-api:1.32.0'
diff --git a/core/src/main/java/com/p14n/postevent/broker/DefaultExecutor.java b/core/src/main/java/com/p14n/postevent/broker/DefaultExecutor.java
index 3d954c3..f2bef1d 100644
--- a/core/src/main/java/com/p14n/postevent/broker/DefaultExecutor.java
+++ b/core/src/main/java/com/p14n/postevent/broker/DefaultExecutor.java
@@ -3,8 +3,9 @@
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicLong;
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import static java.lang.String.format;
/**
* Default implementation of {@link AsyncExecutor} that provides configurable
@@ -49,6 +50,19 @@ public DefaultExecutor(int scheduledSize, int fixedSize) {
this.es = createFixedExecutorService(fixedSize);
}
+ protected ThreadFactory createNamedFactory(String nameFormat,ThreadFactory backingFactory){
+ AtomicLong count = (nameFormat != null) ? new AtomicLong(0) : null;
+ return runnable -> {
+ Thread thread = backingFactory.newThread(runnable);
+ if (nameFormat != null) {
+ thread.setName(format(nameFormat, count.getAndIncrement()));
+ }
+ return thread;
+ };
+ }
+ protected ThreadFactory createNamedFactory(String nameFormat) {
+ return createNamedFactory(nameFormat,Executors.defaultThreadFactory());
+ }
/**
* Creates a fixed-size thread pool with named threads.
*
@@ -57,7 +71,7 @@ public DefaultExecutor(int scheduledSize, int fixedSize) {
*/
protected ExecutorService createFixedExecutorService(int size) {
return Executors.newFixedThreadPool(size,
- new ThreadFactoryBuilder().setNameFormat("post-event-fixed-%d").build());
+ createNamedFactory("post-event-fixed-%d"));
}
/**
@@ -67,8 +81,7 @@ protected ExecutorService createFixedExecutorService(int size) {
*/
protected ExecutorService createVirtualExecutorService() {
return Executors.newThreadPerTaskExecutor(
- new ThreadFactoryBuilder().setThreadFactory(Thread.ofVirtual().factory())
- .setNameFormat("post-event-virtual-%d").build());
+ createNamedFactory("post-event-virtual-%d",Thread.ofVirtual().factory()));
}
/**
@@ -79,7 +92,7 @@ protected ExecutorService createVirtualExecutorService() {
*/
protected ScheduledExecutorService createScheduledExecutorService(int size) {
return Executors.newScheduledThreadPool(size,
- new ThreadFactoryBuilder().setNameFormat("post-event-scheduled-%d").build());
+ createNamedFactory("post-event-scheduled-%d"));
}
@Override
diff --git a/core/src/main/java/com/p14n/postevent/db/DatabaseSetup.java b/core/src/main/java/com/p14n/postevent/db/DatabaseSetup.java
index 1ed10ff..362428b 100644
--- a/core/src/main/java/com/p14n/postevent/db/DatabaseSetup.java
+++ b/core/src/main/java/com/p14n/postevent/db/DatabaseSetup.java
@@ -1,7 +1,6 @@
package com.p14n.postevent.db;
import com.p14n.postevent.data.PostEventConfig;
-import com.zaxxer.hikari.HikariDataSource;
import javax.sql.DataSource;
@@ -35,16 +34,14 @@
* Example usage:
*
*
- * {@code
+ *
+ * {@code
* PostEventConfig config = // initialize configuration
* DatabaseSetup setup = new DatabaseSetup(config);
*
* // Setup all required tables for given topics
* setup.setupAll(Set.of("orders", "inventory"));
*
- * // Create connection pool
- * DataSource pool = DatabaseSetup.createPool(config);
- * }
*/
public class DatabaseSetup {
private static final Logger logger = LoggerFactory.getLogger(DatabaseSetup.class);
@@ -78,6 +75,7 @@ public DatabaseSetup(String jdbcUrl, String username, String password) {
this.password = password;
this.ds = null;
}
+
public DatabaseSetup(DataSource ds) {
this.jdbcUrl = null;
this.username = null;
@@ -99,15 +97,18 @@ public DatabaseSetup setupAll(Set topics) {
setupDebezium();
return this;
}
+
public DatabaseSetup setupDebezium() {
clearOldSlots();
return this;
}
+
public DatabaseSetup setupServer(Set topics) {
createSchemaIfNotExists();
topics.stream().forEach(this::createTableIfNotExists);
return this;
}
+
public DatabaseSetup setupClient() {
createSchemaIfNotExists();
createMessagesTableIfNotExists();
@@ -309,22 +310,9 @@ topic_name VARCHAR(255) PRIMARY KEY,
* @throws SQLException if connection fails
*/
private Connection getConnection() throws SQLException {
- if(ds!=null) return ds.getConnection();
+ if (ds != null)
+ return ds.getConnection();
return DriverManager.getConnection(jdbcUrl, username, password);
}
- /**
- * Creates and configures a connection pool using HikariCP.
- *
- * @param cfg Configuration containing database connection details
- * @return Configured DataSource
- */
- public static DataSource createPool(PostEventConfig cfg) {
- HikariDataSource ds = new HikariDataSource();
- ds.setJdbcUrl(cfg.jdbcUrl());
- ds.setUsername(cfg.dbUser());
- ds.setPassword(cfg.dbPassword());
- return ds;
- }
-
}
diff --git a/debezium/build.gradle b/debezium/build.gradle
index 64f6442..c6af107 100644
--- a/debezium/build.gradle
+++ b/debezium/build.gradle
@@ -42,6 +42,9 @@ dependencies {
constraints {
implementation 'com.google.guava:guava:32.0.0-jre'
}
+
+ // Database connection pooling
+ implementation 'com.zaxxer:HikariCP:5.0.1'
// Test dependencies
testImplementation platform('io.zonky.test.postgres:embedded-postgres-binaries-bom:16.2.0')
diff --git a/debezium/src/main/java/com/p14n/postevent/LocalPersistentConsumer.java b/debezium/src/main/java/com/p14n/postevent/LocalPersistentConsumer.java
index c394d2c..a3e89bd 100644
--- a/debezium/src/main/java/com/p14n/postevent/LocalPersistentConsumer.java
+++ b/debezium/src/main/java/com/p14n/postevent/LocalPersistentConsumer.java
@@ -8,6 +8,7 @@
import com.p14n.postevent.data.PostEventConfig;
import com.p14n.postevent.data.UnprocessedEventFinder;
import com.p14n.postevent.db.DatabaseSetup;
+import com.p14n.postevent.db.PoolSetup;
import io.opentelemetry.api.OpenTelemetry;
@@ -104,7 +105,7 @@ public LocalPersistentConsumer(DataSource ds, PostEventConfig cfg, OpenTelemetry
* @param batchSize Maximum number of events to process in a batch
*/
public LocalPersistentConsumer(PostEventConfig cfg, OpenTelemetry ot) {
- this(DatabaseSetup.createPool(cfg), cfg, new DefaultExecutor(2, 10), ot, 10);
+ this(PoolSetup.createPool(cfg), cfg, new DefaultExecutor(2, 10), ot, 10);
}
/**
diff --git a/debezium/src/main/java/com/p14n/postevent/db/PoolSetup.java b/debezium/src/main/java/com/p14n/postevent/db/PoolSetup.java
new file mode 100644
index 0000000..3157de1
--- /dev/null
+++ b/debezium/src/main/java/com/p14n/postevent/db/PoolSetup.java
@@ -0,0 +1,23 @@
+package com.p14n.postevent.db;
+
+import com.p14n.postevent.data.PostEventConfig;
+import com.zaxxer.hikari.HikariDataSource;
+
+import javax.sql.DataSource;
+
+public class PoolSetup {
+ /**
+ * Creates and configures a connection pool using HikariCP.
+ *
+ * @param cfg Configuration containing database connection details
+ * @return Configured DataSource
+ */
+ public static DataSource createPool(PostEventConfig cfg) {
+ HikariDataSource ds = new HikariDataSource();
+ ds.setJdbcUrl(cfg.jdbcUrl());
+ ds.setUsername(cfg.dbUser());
+ ds.setPassword(cfg.dbPassword());
+ return ds;
+ }
+
+}
diff --git a/grpc/src/main/java/com/p14n/postevent/ConsumerServer.java b/grpc/src/main/java/com/p14n/postevent/ConsumerServer.java
index 672a099..c3d7fb4 100644
--- a/grpc/src/main/java/com/p14n/postevent/ConsumerServer.java
+++ b/grpc/src/main/java/com/p14n/postevent/ConsumerServer.java
@@ -13,7 +13,7 @@
import com.p14n.postevent.catchup.CatchupServer;
import com.p14n.postevent.catchup.remote.CatchupGrpcServer;
import com.p14n.postevent.data.ConfigData;
-import com.p14n.postevent.db.DatabaseSetup;
+import com.p14n.postevent.db.PoolSetup;
import io.grpc.Server;
import io.grpc.ServerBuilder;
@@ -72,7 +72,7 @@ public class ConsumerServer implements AutoCloseable {
* @param ot The OpenTelemetry instance for monitoring and tracing
*/
public ConsumerServer(ConfigData cfg, OpenTelemetry ot) {
- this(DatabaseSetup.createPool(cfg), cfg, new DefaultExecutor(2), ot);
+ this(PoolSetup.createPool(cfg), cfg, new DefaultExecutor(2), ot);
}
/**
diff --git a/vertx/build.gradle b/vertx/build.gradle
index f513ea6..c8d3bef 100644
--- a/vertx/build.gradle
+++ b/vertx/build.gradle
@@ -25,9 +25,6 @@ dependencies {
// OpenTelemetry (from core module)
implementation 'io.opentelemetry:opentelemetry-api:1.32.0'
- // Database connection pooling (for examples)
- implementation 'com.zaxxer:HikariCP:5.0.1'
-
// Logging
implementation 'org.slf4j:slf4j-api:2.0.9'
From 1075f3125f08a2922318058a930a977450d95290 Mon Sep 17 00:00:00 2001
From: Dean Chapman
Date: Fri, 19 Sep 2025 08:23:04 +0100
Subject: [PATCH 7/9] Upgrade to vertx 5
---
vertx/build.gradle | 2 +-
.../p14n/postevent/vertx/client/EventBusCatchupClient.java | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/vertx/build.gradle b/vertx/build.gradle
index c8d3bef..8c0917a 100644
--- a/vertx/build.gradle
+++ b/vertx/build.gradle
@@ -20,7 +20,7 @@ dependencies {
implementation project(':core')
// Vert.x dependencies
- implementation 'io.vertx:vertx-core:4.5.1'
+ implementation 'io.vertx:vertx-core:5.0.4'
// OpenTelemetry (from core module)
implementation 'io.opentelemetry:opentelemetry-api:1.32.0'
diff --git a/vertx/src/main/java/com/p14n/postevent/vertx/client/EventBusCatchupClient.java b/vertx/src/main/java/com/p14n/postevent/vertx/client/EventBusCatchupClient.java
index 1670a53..cd3c289 100644
--- a/vertx/src/main/java/com/p14n/postevent/vertx/client/EventBusCatchupClient.java
+++ b/vertx/src/main/java/com/p14n/postevent/vertx/client/EventBusCatchupClient.java
@@ -103,7 +103,7 @@ public List fetchEvents(long fromId, long toId, int limit, String topic)
try {
CompletableFuture future = new CompletableFuture<>();
- eventBus.request(FETCH_EVENTS_ADDRESS + topic, request, reply -> {
+ eventBus.request(FETCH_EVENTS_ADDRESS + topic, request).andThen(reply -> {
if (reply.succeeded()) {
String eventsJson = (String) reply.result().body();
future.complete(eventsJson);
@@ -151,7 +151,7 @@ public long getLatestMessageId(String topic) {
try {
CompletableFuture future = new CompletableFuture<>();
- eventBus.request(GET_LATEST_MESSAGE_ID_ADDRESS + topic, request, reply -> {
+ eventBus.request(GET_LATEST_MESSAGE_ID_ADDRESS + topic, request).andThen(reply -> {
if (reply.succeeded()) {
JsonObject response = (JsonObject) reply.result().body();
future.complete(response);
From c97c9201f949e1fa400c7f37b7e39e1a699a18fc Mon Sep 17 00:00:00 2001
From: Dean Chapman
Date: Tue, 23 Sep 2025 19:58:39 +0100
Subject: [PATCH 8/9] Update
core/src/main/java/com/p14n/postevent/db/DatabaseSetup.java
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
---
core/src/main/java/com/p14n/postevent/db/DatabaseSetup.java | 3 +++
1 file changed, 3 insertions(+)
diff --git a/core/src/main/java/com/p14n/postevent/db/DatabaseSetup.java b/core/src/main/java/com/p14n/postevent/db/DatabaseSetup.java
index 362428b..c7bdc30 100644
--- a/core/src/main/java/com/p14n/postevent/db/DatabaseSetup.java
+++ b/core/src/main/java/com/p14n/postevent/db/DatabaseSetup.java
@@ -77,6 +77,9 @@ public DatabaseSetup(String jdbcUrl, String username, String password) {
}
public DatabaseSetup(DataSource ds) {
+ if (ds == null) {
+ throw new IllegalArgumentException("DataSource must not be null");
+ }
this.jdbcUrl = null;
this.username = null;
this.password = null;
From ed6189a77575f38d29031666498872f7f0740a2f Mon Sep 17 00:00:00 2001
From: Dean Chapman
Date: Tue, 23 Sep 2025 20:35:21 +0100
Subject: [PATCH 9/9] Review feedback
---
.../vertx/VertxPersistentConsumer.java | 2 +-
.../vertx/adapter/EventBusCatchupService.java | 41 +++---
.../vertx/adapter/EventBusMessageBroker.java | 24 +++-
.../vertx/client/EventBusCatchupClient.java | 132 ++++++------------
.../postevent/vertx/codec/EventCodec.java | 6 +-
5 files changed, 86 insertions(+), 119 deletions(-)
diff --git a/vertx/src/main/java/com/p14n/postevent/vertx/VertxPersistentConsumer.java b/vertx/src/main/java/com/p14n/postevent/vertx/VertxPersistentConsumer.java
index 7130cef..c228766 100644
--- a/vertx/src/main/java/com/p14n/postevent/vertx/VertxPersistentConsumer.java
+++ b/vertx/src/main/java/com/p14n/postevent/vertx/VertxPersistentConsumer.java
@@ -92,7 +92,7 @@ public void close() {
for (AutoCloseable c : closeables) {
try {
- c.close();
+ if(c != null) c.close();
} catch (Exception e) {
logger.atWarn()
.setCause(e)
diff --git a/vertx/src/main/java/com/p14n/postevent/vertx/adapter/EventBusCatchupService.java b/vertx/src/main/java/com/p14n/postevent/vertx/adapter/EventBusCatchupService.java
index 44a25ab..aa06861 100644
--- a/vertx/src/main/java/com/p14n/postevent/vertx/adapter/EventBusCatchupService.java
+++ b/vertx/src/main/java/com/p14n/postevent/vertx/adapter/EventBusCatchupService.java
@@ -85,24 +85,29 @@ public EventBusCatchupService(CatchupServerInterface catchupServer,
* requests.
*/
public void start() {
- logger.atInfo().log("Starting EventBusCatchupService");
-
- // Register consumer for fetchEvents requests
- fetchEventsConsumers = topics.stream().map(topic -> {
- logger.atInfo()
- .addArgument(FETCH_EVENTS_ADDRESS + topic)
- .log("EventBusCatchupService started, listening on address: {}");
-
- return eventBus.consumer(FETCH_EVENTS_ADDRESS + topic, this::handleFetchEvents);
- }).toList();
-
- // Register consumer for getLatestMessageId requests
- getLatestMessageIdConsumers = topics.stream().map(topic -> {
- logger.atInfo()
- .addArgument(GET_LATEST_MESSAGE_ID_ADDRESS + topic)
- .log("EventBusCatchupService started, listening on address: {}");
- return eventBus.consumer(GET_LATEST_MESSAGE_ID_ADDRESS + topic, this::handleGetLatestMessageId);
- }).toList();
+ if(fetchEventsConsumers == null) {
+
+ logger.atInfo().log("Starting EventBusCatchupService");
+
+ // Register consumer for fetchEvents requests
+ fetchEventsConsumers = topics.stream().map(topic -> {
+ logger.atInfo()
+ .addArgument(FETCH_EVENTS_ADDRESS + topic)
+ .log("EventBusCatchupService started, listening on address: {}");
+
+ return eventBus.consumer(FETCH_EVENTS_ADDRESS + topic, this::handleFetchEvents);
+ }).toList();
+
+ // Register consumer for getLatestMessageId requests
+ getLatestMessageIdConsumers = topics.stream().map(topic -> {
+ logger.atInfo()
+ .addArgument(GET_LATEST_MESSAGE_ID_ADDRESS + topic)
+ .log("EventBusCatchupService started, listening on address: {}");
+ return eventBus.consumer(GET_LATEST_MESSAGE_ID_ADDRESS + topic, this::handleGetLatestMessageId);
+ }).toList();
+ } else {
+ logger.atInfo().log("EventBusCatchupService already started");
+ }
}
diff --git a/vertx/src/main/java/com/p14n/postevent/vertx/adapter/EventBusMessageBroker.java b/vertx/src/main/java/com/p14n/postevent/vertx/adapter/EventBusMessageBroker.java
index 5e585b3..f96e068 100644
--- a/vertx/src/main/java/com/p14n/postevent/vertx/adapter/EventBusMessageBroker.java
+++ b/vertx/src/main/java/com/p14n/postevent/vertx/adapter/EventBusMessageBroker.java
@@ -11,6 +11,8 @@
import io.vertx.core.eventbus.MessageConsumer;
import javax.sql.DataSource;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -62,7 +64,7 @@ public class EventBusMessageBroker extends EventMessageBroker {
private static final Logger logger = LoggerFactory.getLogger(EventBusMessageBroker.class);
private final EventBus eventBus;
private final DataSource dataSource;
- private final Map> consumers = new ConcurrentHashMap<>();
+ private final Map>> consumers = new ConcurrentHashMap<>();
private final AsyncExecutor executor;
/**
@@ -185,7 +187,13 @@ public void subscribeToEventBus(String topic, MessageSubscriber subscribe
});
// Store consumer for potential cleanup
- consumers.put(topic, consumer);
+ consumers.compute(topic, (k,l) -> {
+ if(l == null){
+ l = new ArrayList<>();
+ }
+ l.add(consumer);
+ return l;
+ });
logger.atInfo()
.addArgument(topic)
@@ -199,9 +207,11 @@ public void subscribeToEventBus(String topic, MessageSubscriber subscribe
* @param topic The topic to unsubscribe from
*/
public void unsubscribe(String topic) {
- MessageConsumer consumer = consumers.remove(topic);
- if (consumer != null) {
- consumer.unregister();
+ List> consumerList = consumers.remove(topic);
+ if (consumerList != null) {
+ for(var consumer: consumerList){
+ consumer.unregister();
+ }
logger.atInfo()
.addArgument(topic)
.log("Unsubscribed from topic: {}");
@@ -216,7 +226,9 @@ public void close() {
logger.atInfo().log("Closing EventBusMessageBroker");
// Unregister all consumers
- consumers.values().forEach(MessageConsumer::unregister);
+ consumers.values().forEach( l -> {
+ l.forEach(MessageConsumer::unregister);
+ });
consumers.clear();
super.close();
diff --git a/vertx/src/main/java/com/p14n/postevent/vertx/client/EventBusCatchupClient.java b/vertx/src/main/java/com/p14n/postevent/vertx/client/EventBusCatchupClient.java
index cd3c289..8b1b1b7 100644
--- a/vertx/src/main/java/com/p14n/postevent/vertx/client/EventBusCatchupClient.java
+++ b/vertx/src/main/java/com/p14n/postevent/vertx/client/EventBusCatchupClient.java
@@ -1,5 +1,6 @@
package com.p14n.postevent.vertx.client;
+import com.fasterxml.jackson.core.type.TypeReference;
import com.p14n.postevent.catchup.CatchupServerInterface;
import com.p14n.postevent.data.Event;
import io.vertx.core.eventbus.EventBus;
@@ -9,6 +10,7 @@
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -74,109 +76,55 @@ public EventBusCatchupClient(EventBus eventBus, long timeoutSeconds) {
this.timeoutSeconds = timeoutSeconds;
}
- /**
- * Fetches events within the specified ID range for a topic.
- * Sends a request to the EventBusCatchupService and waits for the response.
- *
- * @param fromId The starting event ID (inclusive)
- * @param toId The ending event ID (inclusive)
- * @param limit Maximum number of events to return
- * @param topic The topic to fetch events from
- * @return List of events within the specified range
- * @throws Exception If the request fails or times out
- */
- @Override
- public List fetchEvents(long fromId, long toId, int limit, String topic) {
- logger.atDebug()
- .addArgument(fromId)
- .addArgument(toId)
- .addArgument(limit)
- .addArgument(topic)
- .log("Fetching events: fromId={}, toId={}, limit={}, topic={}");
-
- JsonObject request = new JsonObject()
- .put("fromId", fromId)
- .put("toId", toId)
- .put("limit", limit)
- .put("topic", topic);
+ private R requestAndDecode(
+ String address,
+ JsonObject payload,
+ Function