diff --git a/build.gradle.kts b/build.gradle.kts index fc5abdc..ccfe977 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -19,6 +19,11 @@ repositories { dependencies { implementation("org.springframework.boot:spring-boot-starter") + implementation("org.springframework.boot:spring-boot-starter-actuator") + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.boot:spring-boot-starter-aop") + compileOnly("org.projectlombok:lombok") + annotationProcessor("org.projectlombok:lombok") testImplementation("org.springframework.boot:spring-boot-starter-test") testRuntimeOnly("org.junit.platform:junit-platform-launcher") } diff --git a/src/main/java/org/javaspringcourse/actuator/TimeActuator.java b/src/main/java/org/javaspringcourse/actuator/TimeActuator.java new file mode 100644 index 0000000..add964a --- /dev/null +++ b/src/main/java/org/javaspringcourse/actuator/TimeActuator.java @@ -0,0 +1,20 @@ +package org.javaspringcourse.actuator; + +import lombok.extern.log4j.Log4j2; +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; + +@Log4j2 +@Component +@Endpoint(id = "time") +public class TimeActuator { + + @ReadOperation + public String time() { + log.info("Current time: {}", LocalDateTime.now()); + return LocalDateTime.now().toString(); + } +} diff --git a/src/main/java/org/javaspringcourse/controller/ChocolateShopController.java b/src/main/java/org/javaspringcourse/controller/ChocolateShopController.java new file mode 100644 index 0000000..a90b630 --- /dev/null +++ b/src/main/java/org/javaspringcourse/controller/ChocolateShopController.java @@ -0,0 +1,24 @@ +package org.javaspringcourse.controller; + +import lombok.RequiredArgsConstructor; +import org.javaspringcourse.dto.OrderIn; +import org.javaspringcourse.dto.OrderOut; +import org.javaspringcourse.metric.counter.CounterMetric; +import org.javaspringcourse.metric.timer.TimerMetric; +import org.javaspringcourse.service.ChocolateShopService; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class ChocolateShopController { + private final ChocolateShopService chocolateShopService; + + @PostMapping("/order") + @CounterMetric(name = "createOrder") + @TimerMetric(name = "createOrder") + public OrderOut createOrder(@RequestBody OrderIn order) { + return chocolateShopService.createOrder(order); + } +} diff --git a/src/main/java/org/javaspringcourse/dto/OrderIn.java b/src/main/java/org/javaspringcourse/dto/OrderIn.java new file mode 100644 index 0000000..a71525c --- /dev/null +++ b/src/main/java/org/javaspringcourse/dto/OrderIn.java @@ -0,0 +1,6 @@ +package org.javaspringcourse.dto; + +import org.javaspringcourse.model.Chocolate; + +public record OrderIn(Chocolate chocolate, int count) { +} diff --git a/src/main/java/org/javaspringcourse/dto/OrderOut.java b/src/main/java/org/javaspringcourse/dto/OrderOut.java new file mode 100644 index 0000000..3b41f83 --- /dev/null +++ b/src/main/java/org/javaspringcourse/dto/OrderOut.java @@ -0,0 +1,6 @@ +package org.javaspringcourse.dto; + +import org.javaspringcourse.model.Chocolate; + +public record OrderOut(int orderId, Chocolate chocolate, int count) { +} diff --git a/src/main/java/org/javaspringcourse/metric/counter/CounterAspect.java b/src/main/java/org/javaspringcourse/metric/counter/CounterAspect.java new file mode 100644 index 0000000..0546a04 --- /dev/null +++ b/src/main/java/org/javaspringcourse/metric/counter/CounterAspect.java @@ -0,0 +1,20 @@ +package org.javaspringcourse.metric.counter; + +import io.micrometer.core.instrument.MeterRegistry; +import lombok.RequiredArgsConstructor; +import org.aspectj.lang.annotation.After; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; + +@Aspect +@Component +@RequiredArgsConstructor +public class CounterAspect { + private final MeterRegistry meterRegistry; + + @After("@annotation(counter)") + public void counterMetric(CounterMetric counter) { + meterRegistry.counter("shop.api-request.counter.all").increment(); + meterRegistry.counter("shop.api-request.counter." + counter.name()).increment(); + } +} diff --git a/src/main/java/org/javaspringcourse/metric/counter/CounterMetric.java b/src/main/java/org/javaspringcourse/metric/counter/CounterMetric.java new file mode 100644 index 0000000..79b08cb --- /dev/null +++ b/src/main/java/org/javaspringcourse/metric/counter/CounterMetric.java @@ -0,0 +1,12 @@ +package org.javaspringcourse.metric.counter; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface CounterMetric { + String name() default ""; +} diff --git a/src/main/java/org/javaspringcourse/metric/timer/TimerAspect.java b/src/main/java/org/javaspringcourse/metric/timer/TimerAspect.java new file mode 100644 index 0000000..d0fb293 --- /dev/null +++ b/src/main/java/org/javaspringcourse/metric/timer/TimerAspect.java @@ -0,0 +1,27 @@ +package org.javaspringcourse.metric.timer; + +import io.micrometer.core.instrument.MeterRegistry; +import lombok.RequiredArgsConstructor; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; + +@Aspect +@Component +@RequiredArgsConstructor +public class TimerAspect { + private final MeterRegistry meterRegistry; + + @Around("@annotation(timer)") + public Object timer(ProceedingJoinPoint jp, TimerMetric timer) { + return meterRegistry.timer("shop.api-request.timer." + timer.name()).record(() -> + { + try { + return jp.proceed(); + } catch (Throwable e) { + throw new RuntimeException(e); + } + }); + } +} diff --git a/src/main/java/org/javaspringcourse/metric/timer/TimerMetric.java b/src/main/java/org/javaspringcourse/metric/timer/TimerMetric.java new file mode 100644 index 0000000..5bbf550 --- /dev/null +++ b/src/main/java/org/javaspringcourse/metric/timer/TimerMetric.java @@ -0,0 +1,12 @@ +package org.javaspringcourse.metric.timer; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface TimerMetric { + String name() default ""; +} diff --git a/src/main/java/org/javaspringcourse/model/Chocolate.java b/src/main/java/org/javaspringcourse/model/Chocolate.java new file mode 100644 index 0000000..2f8f0cc --- /dev/null +++ b/src/main/java/org/javaspringcourse/model/Chocolate.java @@ -0,0 +1,7 @@ +package org.javaspringcourse.model; + +public enum Chocolate { + KitKat, + Snickers, + Twix +} diff --git a/src/main/java/org/javaspringcourse/model/OrderEntity.java b/src/main/java/org/javaspringcourse/model/OrderEntity.java new file mode 100644 index 0000000..f1d0f91 --- /dev/null +++ b/src/main/java/org/javaspringcourse/model/OrderEntity.java @@ -0,0 +1,12 @@ +package org.javaspringcourse.model; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class OrderEntity { + private int orderId; + private Chocolate chocolateType; + private int count; +} diff --git a/src/main/java/org/javaspringcourse/service/ChocolateShopService.java b/src/main/java/org/javaspringcourse/service/ChocolateShopService.java new file mode 100644 index 0000000..eb70fb2 --- /dev/null +++ b/src/main/java/org/javaspringcourse/service/ChocolateShopService.java @@ -0,0 +1,35 @@ +package org.javaspringcourse.service; + +import io.micrometer.core.instrument.MeterRegistry; +import lombok.RequiredArgsConstructor; +import org.javaspringcourse.dto.OrderIn; +import org.javaspringcourse.dto.OrderOut; +import org.javaspringcourse.model.OrderEntity; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class ChocolateShopService { + private final List orders = new ArrayList(); + private final MeterRegistry meterRegistry; + + public OrderOut createOrder(OrderIn orderIn) { + var order = OrderEntity.builder() + .orderId(orders.size()) + .chocolateType(orderIn.chocolate()) + .count(orderIn.count()) + .build(); + orders.add(order); + + meterRegistry.counter("shop.order.counter." + orderIn.chocolate()).increment(orderIn.count()); // TODO стоит выделить в ивент + + return toDto(order); + } + + private OrderOut toDto(OrderEntity order) { + return new OrderOut(order.getOrderId(), order.getChocolateType(), order.getCount()); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 61bae71..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -spring.application.name=java-spring-course diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml new file mode 100644 index 0000000..d866c2c --- /dev/null +++ b/src/main/resources/application.yaml @@ -0,0 +1,5 @@ +management: + endpoints: + web: + exposure: + include: '*' \ No newline at end of file