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 +