From fd536d2d8d2a5165df6bd1f1a0f7dfe6ab26337a Mon Sep 17 00:00:00 2001 From: Vladimir Ivanov Date: Tue, 24 Mar 2026 19:35:34 +0300 Subject: [PATCH] 2025-11 spring-30-endpoints-flow-components --- .../.gitignore | 24 +++++ .../pom.xml | 48 ++++++++++ .../java/ru/otus/spring/integration/App.java | 12 +++ .../spring/integration/config/AppRunner.java | 19 ++++ .../integration/config/IntegrationConfig.java | 32 +++++++ .../otus/spring/integration/domain/Food.java | 6 ++ .../spring/integration/domain/OrderItem.java | 5 + .../integration/services/CafeGateway.java | 12 +++ .../integration/services/KitchenService.java | 9 ++ .../services/KitchenServiceImpl.java | 28 ++++++ .../integration/services/OrderService.java | 5 + .../services/OrderServiceImpl.java | 43 +++++++++ .../src/main/resources/application.yml | 5 + .../pom.xml | 72 +++++++++++++++ .../java/ru/otus/spring/integration/App.java | 14 +++ .../spring/integration/config/AppRunner.java | 19 ++++ .../integration/config/IntegrationConfig.java | 38 ++++++++ .../otus/spring/integration/domain/Food.java | 6 ++ .../spring/integration/domain/OrderItem.java | 5 + .../integration/services/CafeGateway.java | 16 ++++ .../integration/services/KitchenService.java | 9 ++ .../services/KitchenServiceImpl.java | 27 ++++++ .../integration/services/OrderService.java | 5 + .../services/OrderServiceImpl.java | 65 +++++++++++++ .../ru/otus/spring/test/bridge/BridgeApp.java | 58 ++++++++++++ .../otus/spring/test/gateway/GatewayApp.java | 76 +++++++++++++++ .../otus/spring/test/polling/PollingApp.java | 92 +++++++++++++++++++ .../spring/test/transform/TransformApp.java | 91 ++++++++++++++++++ .../src/main/resources/application.yml | 5 + .../pom.xml | 34 +++++++ 30 files changed, 880 insertions(+) create mode 100644 2025-11/spring-30-endpoints-flow-components/.gitignore create mode 100644 2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/pom.xml create mode 100644 2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/App.java create mode 100644 2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/config/AppRunner.java create mode 100644 2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/config/IntegrationConfig.java create mode 100644 2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/domain/Food.java create mode 100644 2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/domain/OrderItem.java create mode 100644 2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/services/CafeGateway.java create mode 100644 2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/services/KitchenService.java create mode 100644 2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/services/KitchenServiceImpl.java create mode 100644 2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/services/OrderService.java create mode 100644 2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/services/OrderServiceImpl.java create mode 100644 2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/resources/application.yml create mode 100644 2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/pom.xml create mode 100644 2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/App.java create mode 100644 2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/config/AppRunner.java create mode 100644 2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/config/IntegrationConfig.java create mode 100644 2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/domain/Food.java create mode 100644 2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/domain/OrderItem.java create mode 100644 2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/services/CafeGateway.java create mode 100644 2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/services/KitchenService.java create mode 100644 2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/services/KitchenServiceImpl.java create mode 100644 2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/services/OrderService.java create mode 100644 2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/services/OrderServiceImpl.java create mode 100644 2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/test/bridge/BridgeApp.java create mode 100644 2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/test/gateway/GatewayApp.java create mode 100644 2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/test/polling/PollingApp.java create mode 100644 2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/test/transform/TransformApp.java create mode 100644 2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/resources/application.yml create mode 100644 2025-11/spring-30-endpoints-flow-components/pom.xml diff --git a/2025-11/spring-30-endpoints-flow-components/.gitignore b/2025-11/spring-30-endpoints-flow-components/.gitignore new file mode 100644 index 00000000..4ea52072 --- /dev/null +++ b/2025-11/spring-30-endpoints-flow-components/.gitignore @@ -0,0 +1,24 @@ +target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/build/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ diff --git a/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/pom.xml b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/pom.xml new file mode 100644 index 00000000..6c20857a --- /dev/null +++ b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + ru.otus + endpoints-flow-components-exercise + 1.0-SNAPSHOT + + + ru.otus + endpoints-flow-components + 1.0 + + + + + org.springframework.boot + spring-boot-starter-integration + + + + org.apache.commons + commons-lang3 + + + + + org.projectlombok + lombok + provided + + + com.fasterxml.jackson.core + jackson-databind + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/App.java b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/App.java new file mode 100644 index 00000000..f4f51ce9 --- /dev/null +++ b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/App.java @@ -0,0 +1,12 @@ +package ru.otus.spring.integration; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + + +@SpringBootApplication +public class App { + public static void main(String[] args) { + SpringApplication.run(App.class, args); + } +} diff --git a/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/config/AppRunner.java b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/config/AppRunner.java new file mode 100644 index 00000000..f431ff12 --- /dev/null +++ b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/config/AppRunner.java @@ -0,0 +1,19 @@ +package ru.otus.spring.integration.config; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; +import ru.otus.spring.integration.services.OrderService; + +@Slf4j +@Component +@RequiredArgsConstructor +public class AppRunner implements CommandLineRunner { + final OrderService orderService; + + @Override + public void run(String... args) { + orderService.startGenerateOrdersLoop(); + } +} diff --git a/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/config/IntegrationConfig.java b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/config/IntegrationConfig.java new file mode 100644 index 00000000..3abcf8de --- /dev/null +++ b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/config/IntegrationConfig.java @@ -0,0 +1,32 @@ +package ru.otus.spring.integration.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.dsl.IntegrationFlow; +import org.springframework.integration.dsl.MessageChannelSpec; +import org.springframework.integration.dsl.MessageChannels; + +@SuppressWarnings("unused") +@Configuration +public class IntegrationConfig { + @Bean + public MessageChannelSpec itemsChannel() { + return MessageChannels.queue(10); + } + + @Bean + public MessageChannelSpec foodChannel() { + return MessageChannels.publishSubscribe(); + } + + // TODO: create default poller + + @Bean + public IntegrationFlow cafeFlow() { + return IntegrationFlow.from(itemsChannel()) + // TODO: cook OrderItem in the kitchen + // TODO*: add splitter and aggregator + // TODO: forward it to the publish subscriber channel + .get(); + } +} diff --git a/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/domain/Food.java b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/domain/Food.java new file mode 100644 index 00000000..f27d5047 --- /dev/null +++ b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/domain/Food.java @@ -0,0 +1,6 @@ +package ru.otus.spring.integration.domain; + + +public record Food(String name) { + +} diff --git a/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/domain/OrderItem.java b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/domain/OrderItem.java new file mode 100644 index 00000000..69d9da98 --- /dev/null +++ b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/domain/OrderItem.java @@ -0,0 +1,5 @@ +package ru.otus.spring.integration.domain; + +public record OrderItem(String itemName) { + +} diff --git a/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/services/CafeGateway.java b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/services/CafeGateway.java new file mode 100644 index 00000000..58c24077 --- /dev/null +++ b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/services/CafeGateway.java @@ -0,0 +1,12 @@ +package ru.otus.spring.integration.services; + + +import ru.otus.spring.integration.domain.Food; +import ru.otus.spring.integration.domain.OrderItem; + +// TODO: add messaging gateway annotation +public interface CafeGateway { + + // TODO: add gateway annotation with required channels + Food process(OrderItem orderItem); +} diff --git a/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/services/KitchenService.java b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/services/KitchenService.java new file mode 100644 index 00000000..2ae92af2 --- /dev/null +++ b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/services/KitchenService.java @@ -0,0 +1,9 @@ +package ru.otus.spring.integration.services; + +import ru.otus.spring.integration.domain.Food; +import ru.otus.spring.integration.domain.OrderItem; + +public interface KitchenService { + + Food cook(OrderItem orderItem); +} diff --git a/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/services/KitchenServiceImpl.java b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/services/KitchenServiceImpl.java new file mode 100644 index 00000000..08d3ddc2 --- /dev/null +++ b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/services/KitchenServiceImpl.java @@ -0,0 +1,28 @@ +package ru.otus.spring.integration.services; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import ru.otus.spring.integration.domain.Food; +import ru.otus.spring.integration.domain.OrderItem; + +@Service +@Slf4j +public class KitchenServiceImpl implements KitchenService { + + @Override + public Food cook(OrderItem orderItem) { + log.info("Cooking {}", orderItem.itemName()); + delay(); + log.info("Cooking {} done", orderItem.itemName()); + + return new Food(orderItem.itemName()); + } + + private static void delay() { + try { + Thread.sleep(3000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } +} diff --git a/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/services/OrderService.java b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/services/OrderService.java new file mode 100644 index 00000000..c5da0ac9 --- /dev/null +++ b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/services/OrderService.java @@ -0,0 +1,5 @@ +package ru.otus.spring.integration.services; + +public interface OrderService { + void startGenerateOrdersLoop(); +} diff --git a/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/services/OrderServiceImpl.java b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/services/OrderServiceImpl.java new file mode 100644 index 00000000..dae05628 --- /dev/null +++ b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/java/ru/otus/spring/integration/services/OrderServiceImpl.java @@ -0,0 +1,43 @@ +package ru.otus.spring.integration.services; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.RandomUtils; +import org.springframework.stereotype.Service; +import ru.otus.spring.integration.domain.Food; +import ru.otus.spring.integration.domain.OrderItem; + +@Service +@Slf4j +public class OrderServiceImpl implements OrderService { + private static final String[] MENU = {"coffee", "tea", "smoothie", "whiskey", "beer", "cola", "water"}; + + private final CafeGateway cafe; + + public OrderServiceImpl(CafeGateway cafe) { + this.cafe = cafe; + } + + @Override + public void startGenerateOrdersLoop() { + for (int i = 0; i < 10; i++) { + OrderItem orderItem = generateOrderItem(); + int num = i + 1; + log.info("{}, New orderItem: {}", num, orderItem.itemName()); + Food food = cafe.process(orderItem); + log.info("{}, Ready food: {}", num, food.name()); + delay(); + } + } + + private static OrderItem generateOrderItem() { + return new OrderItem(MENU[RandomUtils.nextInt(0, MENU.length)]); + } + + private void delay() { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } +} diff --git a/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/resources/application.yml b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/resources/application.yml new file mode 100644 index 00000000..1a9aeb48 --- /dev/null +++ b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-exercise/src/main/resources/application.yml @@ -0,0 +1,5 @@ +logging: + level: + root: info + + org.springframework.integration: debug \ No newline at end of file diff --git a/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/pom.xml b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/pom.xml new file mode 100644 index 00000000..fd184690 --- /dev/null +++ b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + ru.otus + endpoints-flow-components-solution + 1.0-SNAPSHOT + + + ru.otus + endpoints-flow-components + 1.0 + + + + + org.springframework.boot + spring-boot-starter-integration + + + + org.apache.commons + commons-lang3 + + + + org.projectlombok + lombok + provided + + + com.fasterxml.jackson.core + jackson-databind + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + ru.otus.spring.integration.App + + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${checkstyle-plugin.version} + + + com.puppycrawl.tools + checkstyle + ${checkstyle.version} + + + + ${checkstyle.config.url} + + + + + check + + + + + + + diff --git a/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/App.java b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/App.java new file mode 100644 index 00000000..1b68561b --- /dev/null +++ b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/App.java @@ -0,0 +1,14 @@ +package ru.otus.spring.integration; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@Slf4j +@SpringBootApplication +public class App { + + public static void main(String[] args) { + SpringApplication.run(App.class, args); + } +} diff --git a/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/config/AppRunner.java b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/config/AppRunner.java new file mode 100644 index 00000000..f431ff12 --- /dev/null +++ b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/config/AppRunner.java @@ -0,0 +1,19 @@ +package ru.otus.spring.integration.config; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; +import ru.otus.spring.integration.services.OrderService; + +@Slf4j +@Component +@RequiredArgsConstructor +public class AppRunner implements CommandLineRunner { + final OrderService orderService; + + @Override + public void run(String... args) { + orderService.startGenerateOrdersLoop(); + } +} diff --git a/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/config/IntegrationConfig.java b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/config/IntegrationConfig.java new file mode 100644 index 00000000..59d9a13b --- /dev/null +++ b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/config/IntegrationConfig.java @@ -0,0 +1,38 @@ +package ru.otus.spring.integration.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.dsl.*; +import org.springframework.integration.scheduling.PollerMetadata; +import ru.otus.spring.integration.domain.Food; +import ru.otus.spring.integration.services.KitchenService; + +@Configuration +public class IntegrationConfig { + + @Bean + public MessageChannelSpec itemsChannel() { + return MessageChannels.queue(10); + } + + @Bean + public MessageChannelSpec foodChannel() { + return MessageChannels.publishSubscribe(); + } + + @Bean(name = PollerMetadata.DEFAULT_POLLER) + public PollerSpec poller() { + return Pollers.fixedRate(100).maxMessagesPerPoll(2); + } + + @Bean + public IntegrationFlow cafeFlow(KitchenService kitchenService) { + return IntegrationFlow.from(itemsChannel()) + .split() + .handle(kitchenService, "cook") + .transform(f -> new Food(f.name().toUpperCase())) + .aggregate() + .channel(foodChannel()) + .get(); + } +} diff --git a/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/domain/Food.java b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/domain/Food.java new file mode 100644 index 00000000..f27d5047 --- /dev/null +++ b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/domain/Food.java @@ -0,0 +1,6 @@ +package ru.otus.spring.integration.domain; + + +public record Food(String name) { + +} diff --git a/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/domain/OrderItem.java b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/domain/OrderItem.java new file mode 100644 index 00000000..69d9da98 --- /dev/null +++ b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/domain/OrderItem.java @@ -0,0 +1,5 @@ +package ru.otus.spring.integration.domain; + +public record OrderItem(String itemName) { + +} diff --git a/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/services/CafeGateway.java b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/services/CafeGateway.java new file mode 100644 index 00000000..85cae973 --- /dev/null +++ b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/services/CafeGateway.java @@ -0,0 +1,16 @@ +package ru.otus.spring.integration.services; + + +import org.springframework.integration.annotation.Gateway; +import org.springframework.integration.annotation.MessagingGateway; +import ru.otus.spring.integration.domain.Food; +import ru.otus.spring.integration.domain.OrderItem; + +import java.util.Collection; + +@MessagingGateway +public interface CafeGateway { + + @Gateway(requestChannel = "itemsChannel", replyChannel = "foodChannel") + Collection process(Collection orderItem); +} diff --git a/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/services/KitchenService.java b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/services/KitchenService.java new file mode 100644 index 00000000..2ae92af2 --- /dev/null +++ b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/services/KitchenService.java @@ -0,0 +1,9 @@ +package ru.otus.spring.integration.services; + +import ru.otus.spring.integration.domain.Food; +import ru.otus.spring.integration.domain.OrderItem; + +public interface KitchenService { + + Food cook(OrderItem orderItem); +} diff --git a/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/services/KitchenServiceImpl.java b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/services/KitchenServiceImpl.java new file mode 100644 index 00000000..8ad7c653 --- /dev/null +++ b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/services/KitchenServiceImpl.java @@ -0,0 +1,27 @@ +package ru.otus.spring.integration.services; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import ru.otus.spring.integration.domain.Food; +import ru.otus.spring.integration.domain.OrderItem; + +@Service +@Slf4j +public class KitchenServiceImpl implements KitchenService { + + @Override + public Food cook(OrderItem orderItem) { + log.info("Cooking {}", orderItem.itemName()); + delay(); + log.info("Cooking {} done", orderItem.itemName()); + return new Food(orderItem.itemName()); + } + + private static void delay() { + try { + Thread.sleep(3000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } +} diff --git a/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/services/OrderService.java b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/services/OrderService.java new file mode 100644 index 00000000..c5da0ac9 --- /dev/null +++ b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/services/OrderService.java @@ -0,0 +1,5 @@ +package ru.otus.spring.integration.services; + +public interface OrderService { + void startGenerateOrdersLoop(); +} diff --git a/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/services/OrderServiceImpl.java b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/services/OrderServiceImpl.java new file mode 100644 index 00000000..d3a85e7e --- /dev/null +++ b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/integration/services/OrderServiceImpl.java @@ -0,0 +1,65 @@ +package ru.otus.spring.integration.services; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.RandomUtils; +import org.springframework.stereotype.Service; +import ru.otus.spring.integration.domain.Food; +import ru.otus.spring.integration.domain.OrderItem; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.ForkJoinPool; +import java.util.stream.Collectors; + +@Service +@Slf4j +public class OrderServiceImpl implements OrderService { + private static final String[] MENU = {"coffee", "tea", "smoothie", "whiskey", "beer", "cola", "water"}; + + private final CafeGateway cafe; + + public OrderServiceImpl(CafeGateway cafe) { + this.cafe = cafe; + } + + @Override + public void startGenerateOrdersLoop() { + ForkJoinPool pool = ForkJoinPool.commonPool(); + for (int i = 0; i < 10; i++) { + int num = i + 1; + pool.execute(() -> { + Collection items = generateOrderItems(); + log.info("{}, New orderItems: {}", num, + items.stream().map(OrderItem::itemName) + .collect(Collectors.joining(","))); + Collection food = cafe.process(items); + log.info("{}, Ready food: {}", num, food.stream() + .map(Food::name) + .collect(Collectors.joining(","))); + }); + delay(); + } + } + + private static OrderItem generateOrderItem() { + return new OrderItem(MENU[RandomUtils.nextInt(0, MENU.length)]); + } + + private static Collection generateOrderItems() { + List items = new ArrayList<>(); + for (int i = 0; i < RandomUtils.nextInt(1, 5); ++i) { + items.add(generateOrderItem()); + } + return items; + } + + private void delay() { + try { + Thread.sleep(7000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + } +} diff --git a/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/test/bridge/BridgeApp.java b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/test/bridge/BridgeApp.java new file mode 100644 index 00000000..ba8b27d9 --- /dev/null +++ b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/test/bridge/BridgeApp.java @@ -0,0 +1,58 @@ +package ru.otus.spring.test.bridge; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.integration.annotation.Gateway; +import org.springframework.integration.annotation.IntegrationComponentScan; +import org.springframework.integration.annotation.MessagingGateway; +import org.springframework.integration.dsl.IntegrationFlow; +import org.springframework.integration.dsl.MessageChannels; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.MessageHandler; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +@SuppressWarnings("unused") +@SpringBootApplication +@IntegrationComponentScan +@Slf4j +public class BridgeApp { + public static void main(String[] args) { + ConfigurableApplicationContext ctx = SpringApplication.run(BridgeApp.class, args); + Map channels = ctx.getBeansOfType(MessageChannel.class); + log.warn("CHANNELS:"); + int i = 0; + for (Map.Entry entry : channels.entrySet()) { + log.warn("{}. {}/{} -> {}", ++i, entry.getKey(), entry.getValue().getClass().getSimpleName(), entry.getValue()); + } + log.warn("HANDLERS:"); + i = 0; + Map endpoints = ctx.getBeansOfType(MessageHandler.class); + for (Map.Entry entry : endpoints.entrySet()) { + log.warn("{}. {}/{} -> {}", ++i, entry.getKey(), entry.getValue().getClass().getSimpleName(), entry.getValue()); + } + + Bridge bean = ctx.getBean(Bridge.class); + List strings = List.of("TEST1", "end"); + Collection result = bean.send(strings); + log.warn("Bridge send: {}, receive: {}", strings, result); + } + + + @MessagingGateway + public interface Bridge { + @Gateway(requestChannel = "flow.input"/*, replyChannel = "p2pChannel"*/) + Collection send(Collection strings); + } + + @Bean + public IntegrationFlow flow() { + return f -> f + .channel(MessageChannels.queue("p2pChannel", 10)); + } +} diff --git a/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/test/gateway/GatewayApp.java b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/test/gateway/GatewayApp.java new file mode 100644 index 00000000..74666244 --- /dev/null +++ b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/test/gateway/GatewayApp.java @@ -0,0 +1,76 @@ +package ru.otus.spring.test.gateway; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.integration.annotation.Gateway; +import org.springframework.integration.annotation.IntegrationComponentScan; +import org.springframework.integration.annotation.MessagingGateway; +import org.springframework.integration.dsl.IntegrationFlow; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.MessageHandler; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; + +@SuppressWarnings("unused") +@SpringBootApplication +@IntegrationComponentScan +@Slf4j +public class GatewayApp { + public static void main(String[] args) { + ConfigurableApplicationContext ctx = SpringApplication.run(GatewayApp.class, args); + Map channels = ctx.getBeansOfType(MessageChannel.class); + log.warn("CHANNELS:"); + int i = 0; + for (Map.Entry entry : channels.entrySet()) { + log.warn("{}. {}/{} -> {}", ++i, entry.getKey(), entry.getValue().getClass().getSimpleName(), entry.getValue()); + } + log.warn("HANDLERS:"); + i = 0; + Map endpoints = ctx.getBeansOfType(MessageHandler.class); + for (Map.Entry entry : endpoints.entrySet()) { + log.warn("{}. {}/{} -> {}", ++i, entry.getKey(), entry.getValue().getClass().getSimpleName(), entry.getValue()); + } + Upcase upcase = ctx.getBean(Upcase.class); + Collection result = upcase.upcase(Arrays.asList("test", "new", "last")); + log.warn("Upcase result: {}", result); + + } + + @MessagingGateway + public interface Upcase { + @Gateway(requestChannel = "upcase.input") + Collection upcase(Collection strings); + } + + @Bean + public IntegrationFlow upcase() { + return f -> f//.channel("from-input-to-split") + .split() +// .split(list -> list.getObject().spliterator()) +// .split(getCustomSplitter(), "split") + .channel("from-split-to-transformer") + .transform(String::toUpperCase) + .channel("from-transformer-to-aggregate") + .aggregate(); +// .>filter(source -> source.stream().anyMatch(s -> s.startsWith("a"))) + } + + +// @Bean +// CustomSplitter getCustomSplitter() { +// return new CustomSplitter(); +// } +// +// public static class CustomSplitter { +// public Collection split(Message> message) { +// return message.getPayload().stream().skip(1).collect(Collectors.toList()); +// } +// } + + +} diff --git a/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/test/polling/PollingApp.java b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/test/polling/PollingApp.java new file mode 100644 index 00000000..8aa04973 --- /dev/null +++ b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/test/polling/PollingApp.java @@ -0,0 +1,92 @@ +package ru.otus.spring.test.polling; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.integration.annotation.Gateway; +import org.springframework.integration.annotation.IntegrationComponentScan; +import org.springframework.integration.annotation.MessagingGateway; +import org.springframework.integration.dsl.IntegrationFlow; +import org.springframework.integration.dsl.MessageChannelSpec; +import org.springframework.integration.dsl.MessageChannels; +import org.springframework.integration.endpoint.PollingConsumer; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.MessageHandler; + +import java.util.Map; + +import static java.lang.Thread.sleep; + +@SuppressWarnings("unused") +@SpringBootApplication +@IntegrationComponentScan +@Slf4j +public class PollingApp { + public static void main(String[] args) throws InterruptedException { + ConfigurableApplicationContext ctx = SpringApplication.run(PollingApp.class, args); + log.warn("POLLER:"); + Map pollers = ctx.getBeansOfType(PollingConsumer.class); + int i = 0; + for (Map.Entry entry : pollers.entrySet()) { + log.warn("{}. {}/{} -> {}", ++i, entry.getKey(), entry.getValue().getClass().getSimpleName(), entry.getValue()); + } + log.warn("CHANNELS:"); + Map channels = ctx.getBeansOfType(MessageChannel.class); + i = 0; + for (Map.Entry entry : channels.entrySet()) { + log.warn("{}. {}/{} -> {}", ++i, entry.getKey(), entry.getValue().getClass().getSimpleName(), entry.getValue()); + } + log.warn("HANDLERS:"); + i = 0; + Map endpoints = ctx.getBeansOfType(MessageHandler.class); + for (Map.Entry entry : endpoints.entrySet()) { + log.warn("{}. {}/{} -> {}", ++i, entry.getKey(), entry.getValue().getClass().getSimpleName(), entry.getValue()); + } + + Polling polling = ctx.getBean(Polling.class); + String result = polling.send("test"); + log.warn("Polling result: {}", result); + + sleep(5000); + ctx.close(); + } + + @MessagingGateway + public interface Polling { + @Gateway(requestChannel = "flow.input", replyChannel = "pubSub") + String send(String value); + } + +// @Bean(name = PollerMetadata.DEFAULT_POLLER) +// public PollerMetadata defaultPoller() { + + /// / return Pollers.fixedRate(10_000).get(); +// PollerMetadata pollerMetadata = new PollerMetadata(); +// pollerMetadata.setMaxMessagesPerPoll(5); +// pollerMetadata.setTrigger(new PeriodicTrigger(Duration.ofSeconds(3))); +// return pollerMetadata; +// } + @Bean + public IntegrationFlow flow() { + return f -> f + .channel("pubSub") + .channel("p2p"); + } + + @Bean + public MessageChannelSpec p2p() { + return MessageChannels.queue("p2p", 10); + } + + @Bean + public MessageChannelSpec p2p2() { + return MessageChannels.priority("p2p2").capacity(10); + } + + @Bean + public MessageChannelSpec pubSub() { + return MessageChannels.publishSubscribe("pubSub"); + } +} diff --git a/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/test/transform/TransformApp.java b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/test/transform/TransformApp.java new file mode 100644 index 00000000..c47be87b --- /dev/null +++ b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/java/ru/otus/spring/test/transform/TransformApp.java @@ -0,0 +1,91 @@ +package ru.otus.spring.test.transform; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.integration.annotation.Gateway; +import org.springframework.integration.annotation.IntegrationComponentScan; +import org.springframework.integration.annotation.MessagingGateway; +import org.springframework.integration.dsl.IntegrationFlow; +import org.springframework.integration.dsl.Transformers; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.MessageHandler; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; + +@SuppressWarnings("unused") +@SpringBootApplication +@IntegrationComponentScan +@Slf4j +public class TransformApp { + public static void main(String[] args) { + ConfigurableApplicationContext ctx = SpringApplication.run(TransformApp.class, args); + Map channels = ctx.getBeansOfType(MessageChannel.class); + log.warn("CHANNELS:"); + int i = 0; + for (Map.Entry entry : channels.entrySet()) { + log.warn("{}. {}/{} -> {}", ++i, entry.getKey(), entry.getValue().getClass().getSimpleName(), entry.getValue()); + } + log.warn("HANDLERS:"); + i = 0; + Map endpoints = ctx.getBeansOfType(MessageHandler.class); + for (Map.Entry entry : endpoints.entrySet()) { + log.warn("{}. {}/{} -> {}", ++i, entry.getKey(), entry.getValue().getClass().getSimpleName(), entry.getValue()); + } + Upcase upcase = ctx.getBean(Upcase.class); + Collection result = upcase.upcase(Arrays.asList(new Item("test"), new Item("new"), new Item("last"))); +// Collection result = upcase.upcase(Arrays.asList("test", "new", "last")); + log.warn("Upcase result: {}", result); + + } + + @Getter + public static class Item { + public Item() { + } + + public Item(String value) { + this.value = value; + } + + String value; + String name; + + public void setValue(String value) { + this.value = value; + } + + @Override + public String toString() { + return "Item{" + + "value='" + value + '\'' + + ", name='" + name + '\'' + + '}'; + } + } + + @MessagingGateway + public interface Upcase { + @Gateway(requestChannel = "upcase.input") + Collection upcase(Collection strings); + } + + @Bean + public IntegrationFlow upcase() { + return f -> f + .split() + .transform(Transformers.toMap()) + ., Map>transform(map -> { + map.replaceAll((k, v) -> v != null ? v.toUpperCase() : v); + return map; + }) + .transform(Transformers.fromMap(Item.class)) + .aggregate(); + } + +} diff --git a/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/resources/application.yml b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/resources/application.yml new file mode 100644 index 00000000..1a9aeb48 --- /dev/null +++ b/2025-11/spring-30-endpoints-flow-components/endpoints-flow-components-solution/src/main/resources/application.yml @@ -0,0 +1,5 @@ +logging: + level: + root: info + + org.springframework.integration: debug \ No newline at end of file diff --git a/2025-11/spring-30-endpoints-flow-components/pom.xml b/2025-11/spring-30-endpoints-flow-components/pom.xml new file mode 100644 index 00000000..d95546ef --- /dev/null +++ b/2025-11/spring-30-endpoints-flow-components/pom.xml @@ -0,0 +1,34 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.5.2 + + + ru.otus + endpoints-flow-components + 1.0 + + pom + + + endpoints-flow-components-exercise + endpoints-flow-components-solution + + + + 17 + 17 + UTF-8 + UTF-8 + 3.1.2 + 10.9.1 + https://raw.githubusercontent.com/OtusTeam/Spring/master/checkstyle.xml + + +