diff --git a/NotificationsSystemDocker/compose.yaml b/NotificationsSystemDocker/compose.yaml
index cf1038c..9820d46 100644
--- a/NotificationsSystemDocker/compose.yaml
+++ b/NotificationsSystemDocker/compose.yaml
@@ -28,6 +28,21 @@ services:
- "1025:1025"
networks:
- notifications-network
+ zipkin:
+ image: openzipkin/zipkin:latest
+ container_name: zipkin-server
+ ports:
+ - "9411:9411"
+ networks:
+ - notifications-network
+ prometheus:
+ image: prom/prometheus:latest
+ ports:
+ - "9090:9090"
+ networks:
+ - notifications-network
+ volumes:
+ - ./prometheus.yml:/etc/prometheus/prometheus.yml
networks:
notifications-network:
diff --git a/NotificationsSystemDocker/prometheus.yml b/NotificationsSystemDocker/prometheus.yml
new file mode 100644
index 0000000..4e2ec8c
--- /dev/null
+++ b/NotificationsSystemDocker/prometheus.yml
@@ -0,0 +1,8 @@
+global:
+ scrape_interval: 15s
+
+scrape_configs:
+ - job_name: "notifications-system"
+ metrics_path: "/actuator/prometheus"
+ static_configs:
+ - targets: ["backend:8080"]
\ No newline at end of file
diff --git a/NotificationsSystemInfrastructure/pom.xml b/NotificationsSystemInfrastructure/pom.xml
index a897ee0..2cff964 100644
--- a/NotificationsSystemInfrastructure/pom.xml
+++ b/NotificationsSystemInfrastructure/pom.xml
@@ -53,6 +53,30 @@
org.springframework.boot
spring-boot-starter-mail
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.springframework.boot
+ spring-boot-micrometer-tracing-brave
+
+
+ io.micrometer
+ micrometer-tracing-bridge-brave
+
+
+ org.springframework.boot
+ spring-boot-starter-zipkin
+
+
+ net.ttddyy.observation
+ datasource-micrometer-spring-boot
+
+
+ io.micrometer
+ micrometer-registry-prometheus
+
org.projectlombok
diff --git a/NotificationsSystemInfrastructure/src/main/java/pl/marcinsobanski/notificationssystem/infrastructure/adapters/cqrs/CQCommandHandlerImpl.java b/NotificationsSystemInfrastructure/src/main/java/pl/marcinsobanski/notificationssystem/infrastructure/adapters/cqrs/CQCommandHandlerImpl.java
index afc68a1..317f1d1 100644
--- a/NotificationsSystemInfrastructure/src/main/java/pl/marcinsobanski/notificationssystem/infrastructure/adapters/cqrs/CQCommandHandlerImpl.java
+++ b/NotificationsSystemInfrastructure/src/main/java/pl/marcinsobanski/notificationssystem/infrastructure/adapters/cqrs/CQCommandHandlerImpl.java
@@ -1,87 +1,61 @@
package pl.marcinsobanski.notificationssystem.infrastructure.adapters.cqrs;
+import io.micrometer.observation.Observation;
+import io.micrometer.observation.ObservationRegistry;
+import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.reflect.TypeUtils;
import org.springframework.stereotype.Service;
import pl.marcinsobanski.notificationssystem.api.cqrs.common.cqrs.Command;
-import pl.marcinsobanski.notificationssystem.api.cqrs.common.cqrs.CommandHandler;
import pl.marcinsobanski.notificationssystem.api.cqrs.common.cqrs.Query;
-import pl.marcinsobanski.notificationssystem.api.cqrs.common.cqrs.QueryHandler;
import pl.marcinsobanski.notificationssystem.application.cqrs.CQCommandHandler;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
@Service
@Slf4j
-// TODO remove duplication
+@RequiredArgsConstructor
public class CQCommandHandlerImpl implements CQCommandHandler {
- private final Map>, CommandHandler, ?>> commandHandlerMap;
- private final Map>, QueryHandler, ?>> queryHandlerMap;
-
- public CQCommandHandlerImpl(
- List> commandHandlerBeans,
- List> queryHandlerBeans
- ) {
- final var commandHandlers = new HashMap>, CommandHandler, ?>>();
- for (CommandHandler, ?> commandHandlerBean : commandHandlerBeans) {
- final var typeArguments = TypeUtils.getTypeArguments(commandHandlerBean.getClass(), CommandHandler.class);
- final var commandClazz = (Class) typeArguments.entrySet().stream()
- .filter(typeVariableTypeEntry -> "COMMAND".equals(typeVariableTypeEntry.getKey().getName()))
- .map(Map.Entry::getValue)
- .findFirst()
- .orElseThrow();
- commandHandlers.put(commandClazz, commandHandlerBean);
- }
- commandHandlerMap = Collections.unmodifiableMap(commandHandlers);
-
- final var queryHandlers = new HashMap>, QueryHandler, ?>>();
- for (QueryHandler, ?> queryHandlerBean : queryHandlerBeans) {
- final var typeArguments = TypeUtils.getTypeArguments(queryHandlerBean.getClass(), QueryHandler.class);
- final var queryClazz = (Class) typeArguments.entrySet().stream()
- .filter(typeVariableTypeEntry -> "QUERY".equals(typeVariableTypeEntry.getKey().getName()))
- .map(Map.Entry::getValue)
- .findFirst()
- .orElseThrow();
- queryHandlers.put(queryClazz, queryHandlerBean);
- }
- queryHandlerMap = Collections.unmodifiableMap(queryHandlers);
- }
+ private final CQHandlersProvider cqHandlersProvider;
+ private final ObservationRegistry observationRegistry;
@Override
public RESULT executeCommand(Command command) {
- final var commandExecutor = (CommandHandler>) commandHandlerMap.get(command.getClass());
+ final var commandExecutor = cqHandlersProvider.getCommandHandler(command);
if (commandExecutor == null) {
throw new IllegalArgumentException("Command handler for class " + command.getClass() + " not exist");
}
- log.info("Execute command {}", command);
- try {
- final var result = commandExecutor.handle(command);
- log.info("Command {} executed: {}", command, result);
- return result;
- } catch (Exception e) {
- log.error("Command {} execution failed", command, e);
- throw e;
- }
+ return Observation.createNotStarted("execute command", observationRegistry)
+ .lowCardinalityKeyValue("command.class", command.getClass().getSimpleName())
+ .observe(() -> {
+ log.info("Execute command {}", command);
+ try {
+ final var result = commandExecutor.handle(command);
+ log.info("Command {} executed: {}", command, result);
+ return result;
+ } catch (Exception e) {
+ log.error("Command {} execution failed", command, e);
+ throw e;
+ }
+ });
}
@Override
public RESULT executeQuery(Query query) {
- final var queryExecutor = (QueryHandler>) queryHandlerMap.get(query.getClass());
+ final var queryExecutor = cqHandlersProvider.getQueryHandler(query);
if (queryExecutor == null) {
throw new IllegalArgumentException("Query handler for class " + query.getClass() + " not exist");
}
- log.info("Execute query {}", query);
- try {
- final var result = queryExecutor.handle(query);
- log.info("Query {} executed: {}", query, result);
- return result;
- } catch (Exception e) {
- log.error("Query {} execution failed", query, e);
- throw e;
- }
+ return Observation.createNotStarted("execute query", observationRegistry)
+ .lowCardinalityKeyValue("query.class", query.getClass().getName())
+ .observe(() -> {
+ log.info("Execute query {}", query);
+ try {
+ final var result = queryExecutor.handle(query);
+ log.info("Query {} executed: {}", query, result);
+ return result;
+ } catch (Exception e) {
+ log.error("Query {} execution failed", query, e);
+ throw e;
+ }
+ });
}
}
diff --git a/NotificationsSystemInfrastructure/src/main/java/pl/marcinsobanski/notificationssystem/infrastructure/adapters/cqrs/CQHandlersProvider.java b/NotificationsSystemInfrastructure/src/main/java/pl/marcinsobanski/notificationssystem/infrastructure/adapters/cqrs/CQHandlersProvider.java
new file mode 100644
index 0000000..e60d441
--- /dev/null
+++ b/NotificationsSystemInfrastructure/src/main/java/pl/marcinsobanski/notificationssystem/infrastructure/adapters/cqrs/CQHandlersProvider.java
@@ -0,0 +1,51 @@
+package pl.marcinsobanski.notificationssystem.infrastructure.adapters.cqrs;
+
+import org.apache.commons.lang3.reflect.TypeUtils;
+import org.springframework.stereotype.Service;
+import pl.marcinsobanski.notificationssystem.api.cqrs.common.cqrs.Command;
+import pl.marcinsobanski.notificationssystem.api.cqrs.common.cqrs.CommandHandler;
+import pl.marcinsobanski.notificationssystem.api.cqrs.common.cqrs.Query;
+import pl.marcinsobanski.notificationssystem.api.cqrs.common.cqrs.QueryHandler;
+
+import java.util.*;
+
+@Service
+public class CQHandlersProvider {
+
+ private final Map>, CommandHandler, ?>> commandHandlerMap;
+ private final Map>, QueryHandler, ?>> queryHandlerMap;
+
+ public CQHandlersProvider(
+ List> commandHandlerBeans,
+ List> queryHandlerBeans
+ ) {
+ final var commandHandlers = new HashMap>, CommandHandler, ?>>();
+ commandHandlerBeans.forEach(commandHandlerBean -> commandHandlers.put((Class>) extract(commandHandlerBean, CommandHandler.class, "COMMAND"), commandHandlerBean));
+ commandHandlerMap = Collections.unmodifiableMap(commandHandlers);
+
+
+ final var queryHandlers = new HashMap>, QueryHandler, ?>>();
+ queryHandlerBeans.forEach(queryHandlerBean -> queryHandlers.put((Class>) extract(queryHandlerBean, QueryHandler.class, "QUERY"), queryHandlerBean));
+ queryHandlerMap = Collections.unmodifiableMap(queryHandlers);
+ }
+
+ private Class> extract(Object cqBean, Class> cqHandlerClazz, String argument) {
+ final var typeArguments = TypeUtils.getTypeArguments(cqBean.getClass(), cqHandlerClazz);
+ return (Class) typeArguments.entrySet().stream()
+ .filter(typeVariableTypeEntry -> argument.equals(typeVariableTypeEntry.getKey().getName()))
+ .map(Map.Entry::getValue)
+ .findFirst()
+ .orElseThrow();
+ }
+
+ public > CommandHandler getCommandHandler(C command) {
+ final var clazz = command.getClass();
+ return (CommandHandler) commandHandlerMap.get(clazz);
+ }
+
+ public > QueryHandler getQueryHandler(Q query) {
+ final var clazz = query.getClass();
+ return (QueryHandler) queryHandlerMap.get(clazz);
+ }
+
+}
diff --git a/NotificationsSystemInfrastructure/src/main/resources/application-docker.yaml b/NotificationsSystemInfrastructure/src/main/resources/application-docker.yaml
index 3b40355..1c14ad5 100644
--- a/NotificationsSystemInfrastructure/src/main/resources/application-docker.yaml
+++ b/NotificationsSystemInfrastructure/src/main/resources/application-docker.yaml
@@ -6,4 +6,15 @@ spring:
ddl-auto: update
mail:
host: mailpit
- port: 1025
\ No newline at end of file
+ port: 1025
+management:
+ endpoints:
+ web:
+ exposure:
+ include: health, prometheus
+ tracing:
+ sampling:
+ probability: 1.0
+ export:
+ zipkin:
+ endpoint: http://zipkin:9411/api/v2/spans
\ No newline at end of file
diff --git a/README.md b/README.md
index 2c0761c..abeb86d 100644
--- a/README.md
+++ b/README.md
@@ -40,6 +40,8 @@ Url:
* h2-console http://localhost:8080/h2-console
* JDBC URL `jdbc:h2:file:/data/h2db`
* user `admin`
+* zipkin http://localhost:9411
+* prometheus http://localhost:9090
Important:
diff --git a/pom.xml b/pom.xml
index f0a2901..cd62c3e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -60,6 +60,7 @@
springdoc-openapi-starter-webmvc-ui
3.0.1
+
org.springframework.boot
spring-boot-dependencies
@@ -67,6 +68,13 @@
pom
import
+
+ net.ttddyy.observation
+ datasource-micrometer-bom
+ 2.1.1
+ pom
+ import
+