diff --git a/2020-08/spring-28/.gitignore b/2020-08/spring-28/.gitignore
new file mode 100644
index 00000000..4ea52072
--- /dev/null
+++ b/2020-08/spring-28/.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/2020-08/spring-28/pom.xml b/2020-08/spring-28/pom.xml
new file mode 100644
index 00000000..66de84fc
--- /dev/null
+++ b/2020-08/spring-28/pom.xml
@@ -0,0 +1,17 @@
+
+
+ 4.0.0
+
+ ru.otus
+ spring-28
+ 1.0
+
+ pom
+
+
+ spring-28-exercise
+ spring-28-solution
+
+
diff --git a/2020-08/spring-28/spring-28-exercise/pom.xml b/2020-08/spring-28/spring-28-exercise/pom.xml
new file mode 100644
index 00000000..099b2634
--- /dev/null
+++ b/2020-08/spring-28/spring-28-exercise/pom.xml
@@ -0,0 +1,41 @@
+
+
+ 4.0.0
+
+ ru.otus
+ spring-28-exercise
+ 1.0-SNAPSHOT
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.2.6.RELEASE
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-integration
+
+
+ org.springframework
+ spring-messaging
+
+
+ org.apache.commons
+ commons-lang3
+ 3.7
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/2020-08/spring-28/spring-28-exercise/src/main/java/ru/otus/spring/integration/App.java b/2020-08/spring-28/spring-28-exercise/src/main/java/ru/otus/spring/integration/App.java
new file mode 100644
index 00000000..15edfe62
--- /dev/null
+++ b/2020-08/spring-28/spring-28-exercise/src/main/java/ru/otus/spring/integration/App.java
@@ -0,0 +1,70 @@
+package ru.otus.spring.integration;
+
+import org.apache.commons.lang3.RandomUtils;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.support.AbstractApplicationContext;
+import org.springframework.integration.annotation.IntegrationComponentScan;
+
+import org.springframework.integration.channel.PublishSubscribeChannel;
+import org.springframework.integration.channel.QueueChannel;
+import org.springframework.integration.config.EnableIntegration;
+import org.springframework.integration.dsl.IntegrationFlow;
+import org.springframework.integration.dsl.IntegrationFlows;
+
+import org.springframework.integration.dsl.MessageChannels;
+import ru.otus.spring.integration.domain.Food;
+import ru.otus.spring.integration.domain.OrderItem;
+
+
+@IntegrationComponentScan
+@SuppressWarnings({"resource", "Duplicates", "InfiniteLoopStatement"})
+@ComponentScan
+@Configuration
+@EnableIntegration
+public class App {
+ private static final String[] MENU = {"coffee", "tea", "smoothie", "whiskey", "beer", "cola", "water"};
+
+ @Bean
+ public QueueChannel itemsChannel() {
+ return MessageChannels.queue(10).get();
+ }
+
+ @Bean
+ public PublishSubscribeChannel foodChannel() {
+ return MessageChannels.publishSubscribe().get();
+ }
+
+ // TODO: create default poller
+
+ @Bean
+ public IntegrationFlow cafeFlow() {
+ return IntegrationFlows.from("itemsChannel")
+ // TODO: cook OrderItem in the kitchen
+ // TODO*: add splitter and aggregator
+ // TODO: forward it to the publish subscriber channel
+ .get();
+ }
+
+ public static void main(String[] args) throws Exception {
+ AbstractApplicationContext ctx = new AnnotationConfigApplicationContext(App.class);
+
+ // here we works with cafe using interface
+ Cafe cafe = ctx.getBean(Cafe.class);
+
+ while (true) {
+ Thread.sleep(1000);
+
+ OrderItem orderItem = generateOrderItem();
+ System.out.println("New orderItem: " + orderItem.getItemName());
+ Food food = cafe.process(orderItem);
+ System.out.println("Ready food: " + food.getName());
+ }
+ }
+
+ private static OrderItem generateOrderItem() {
+ return new OrderItem(MENU[RandomUtils.nextInt(0, MENU.length)]);
+ }
+}
diff --git a/2020-08/spring-28/spring-28-exercise/src/main/java/ru/otus/spring/integration/Cafe.java b/2020-08/spring-28/spring-28-exercise/src/main/java/ru/otus/spring/integration/Cafe.java
new file mode 100644
index 00000000..0114f741
--- /dev/null
+++ b/2020-08/spring-28/spring-28-exercise/src/main/java/ru/otus/spring/integration/Cafe.java
@@ -0,0 +1,12 @@
+package ru.otus.spring.integration;
+
+
+import ru.otus.spring.integration.domain.Food;
+import ru.otus.spring.integration.domain.OrderItem;
+
+// TODO: add messaging gateway annotation
+public interface Cafe {
+
+ // TODO: add gateway annotation with required channels
+ Food process(OrderItem orderItem);
+}
diff --git a/2020-08/spring-28/spring-28-exercise/src/main/java/ru/otus/spring/integration/domain/Food.java b/2020-08/spring-28/spring-28-exercise/src/main/java/ru/otus/spring/integration/domain/Food.java
new file mode 100644
index 00000000..16d8e9c6
--- /dev/null
+++ b/2020-08/spring-28/spring-28-exercise/src/main/java/ru/otus/spring/integration/domain/Food.java
@@ -0,0 +1,15 @@
+package ru.otus.spring.integration.domain;
+
+
+public class Food {
+
+ private final String name;
+
+ public Food(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/2020-08/spring-28/spring-28-exercise/src/main/java/ru/otus/spring/integration/domain/OrderItem.java b/2020-08/spring-28/spring-28-exercise/src/main/java/ru/otus/spring/integration/domain/OrderItem.java
new file mode 100644
index 00000000..68612e96
--- /dev/null
+++ b/2020-08/spring-28/spring-28-exercise/src/main/java/ru/otus/spring/integration/domain/OrderItem.java
@@ -0,0 +1,14 @@
+package ru.otus.spring.integration.domain;
+
+public class OrderItem {
+
+ private final String itemName;
+
+ public OrderItem(String itemName) {
+ this.itemName = itemName;
+ }
+
+ public String getItemName() {
+ return itemName;
+ }
+}
diff --git a/2020-08/spring-28/spring-28-exercise/src/main/java/ru/otus/spring/integration/kitchen/KitchenService.java b/2020-08/spring-28/spring-28-exercise/src/main/java/ru/otus/spring/integration/kitchen/KitchenService.java
new file mode 100644
index 00000000..8e40715f
--- /dev/null
+++ b/2020-08/spring-28/spring-28-exercise/src/main/java/ru/otus/spring/integration/kitchen/KitchenService.java
@@ -0,0 +1,16 @@
+package ru.otus.spring.integration.kitchen;
+
+import org.springframework.stereotype.Service;
+import ru.otus.spring.integration.domain.Food;
+import ru.otus.spring.integration.domain.OrderItem;
+
+@Service
+public class KitchenService {
+
+ public Food cook(OrderItem orderItem) throws Exception {
+ System.out.println("Cooking " + orderItem.getItemName());
+ Thread.sleep(3000);
+ System.out.println("Cooking " + orderItem.getItemName() + " done");
+ return new Food(orderItem.getItemName());
+ }
+}
diff --git a/2020-08/spring-28/spring-28-solution/pom.xml b/2020-08/spring-28/spring-28-solution/pom.xml
new file mode 100644
index 00000000..d5b2e905
--- /dev/null
+++ b/2020-08/spring-28/spring-28-solution/pom.xml
@@ -0,0 +1,41 @@
+
+
+ 4.0.0
+
+ ru.otus
+ spring-28-solution
+ 1.0-SNAPSHOT
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.2.6.RELEASE
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-integration
+
+
+ org.springframework
+ spring-messaging
+
+
+ org.apache.commons
+ commons-lang3
+ 3.7
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/2020-08/spring-28/spring-28-solution/src/main/java/ru/otus/spring/integration/App.java b/2020-08/spring-28/spring-28-solution/src/main/java/ru/otus/spring/integration/App.java
new file mode 100644
index 00000000..cc241d10
--- /dev/null
+++ b/2020-08/spring-28/spring-28-solution/src/main/java/ru/otus/spring/integration/App.java
@@ -0,0 +1,93 @@
+package ru.otus.spring.integration;
+
+import org.apache.commons.lang3.RandomUtils;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.support.AbstractApplicationContext;
+import org.springframework.integration.annotation.IntegrationComponentScan;
+import org.springframework.integration.channel.DirectChannel;
+import org.springframework.integration.channel.PublishSubscribeChannel;
+import org.springframework.integration.channel.QueueChannel;
+import org.springframework.integration.config.EnableIntegration;
+import org.springframework.integration.dsl.IntegrationFlow;
+import org.springframework.integration.dsl.IntegrationFlows;
+import org.springframework.integration.dsl.MessageChannels;
+import org.springframework.integration.dsl.Pollers;
+
+import org.springframework.integration.scheduling.PollerMetadata;
+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.stream.Collectors;
+
+
+@IntegrationComponentScan
+@SuppressWarnings({"resource", "Duplicates", "InfiniteLoopStatement"})
+@ComponentScan
+@Configuration
+@EnableIntegration
+public class App {
+ private static final String[] MENU = {"coffee", "tea", "smoothie", "whiskey", "beer", "cola", "water"};
+
+ @Bean
+ public QueueChannel itemsChannel() {
+ return MessageChannels.queue(10).get();
+ }
+
+ @Bean
+ public PublishSubscribeChannel foodChannel() {
+ return MessageChannels.publishSubscribe().get();
+ }
+
+ @Bean (name = PollerMetadata.DEFAULT_POLLER )
+ public PollerMetadata poller () {
+ return Pollers.fixedRate(100).maxMessagesPerPoll(2).get() ;
+ }
+
+ @Bean
+ public IntegrationFlow cafeFlow() {
+ return IntegrationFlows.from("itemsChannel")
+ .split()
+ .handle("kitchenService", "cook")
+ .aggregate()
+ .channel("foodChannel")
+ .get();
+ }
+
+ public static void main(String[] args) throws Exception {
+ AbstractApplicationContext ctx = new AnnotationConfigApplicationContext(App.class);
+
+ // here we works with cafe using interface
+ Cafe cafe = ctx.getBean(Cafe.class);
+
+ while (true) {
+ Thread.sleep(1000);
+
+ Collection items = generateOrderItems();
+ System.out.println("New orderItems: " +
+ items.stream().map(OrderItem::getItemName)
+ .collect(Collectors.joining(",")));
+ Collection food = cafe.process(items);
+ System.out.println("Ready food: " + food.stream()
+ .map(Food::getName)
+ .collect(Collectors.joining(",")));
+ }
+ }
+
+ 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;
+ }
+}
diff --git a/2020-08/spring-28/spring-28-solution/src/main/java/ru/otus/spring/integration/Cafe.java b/2020-08/spring-28/spring-28-solution/src/main/java/ru/otus/spring/integration/Cafe.java
new file mode 100644
index 00000000..6b31f40a
--- /dev/null
+++ b/2020-08/spring-28/spring-28-solution/src/main/java/ru/otus/spring/integration/Cafe.java
@@ -0,0 +1,16 @@
+package ru.otus.spring.integration;
+
+
+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 Cafe {
+
+ @Gateway(requestChannel = "itemsChannel", replyChannel = "foodChannel")
+ Collection process(Collection orderItem);
+}
diff --git a/2020-08/spring-28/spring-28-solution/src/main/java/ru/otus/spring/integration/domain/Food.java b/2020-08/spring-28/spring-28-solution/src/main/java/ru/otus/spring/integration/domain/Food.java
new file mode 100644
index 00000000..16d8e9c6
--- /dev/null
+++ b/2020-08/spring-28/spring-28-solution/src/main/java/ru/otus/spring/integration/domain/Food.java
@@ -0,0 +1,15 @@
+package ru.otus.spring.integration.domain;
+
+
+public class Food {
+
+ private final String name;
+
+ public Food(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/2020-08/spring-28/spring-28-solution/src/main/java/ru/otus/spring/integration/domain/OrderItem.java b/2020-08/spring-28/spring-28-solution/src/main/java/ru/otus/spring/integration/domain/OrderItem.java
new file mode 100644
index 00000000..68612e96
--- /dev/null
+++ b/2020-08/spring-28/spring-28-solution/src/main/java/ru/otus/spring/integration/domain/OrderItem.java
@@ -0,0 +1,14 @@
+package ru.otus.spring.integration.domain;
+
+public class OrderItem {
+
+ private final String itemName;
+
+ public OrderItem(String itemName) {
+ this.itemName = itemName;
+ }
+
+ public String getItemName() {
+ return itemName;
+ }
+}
diff --git a/2020-08/spring-28/spring-28-solution/src/main/java/ru/otus/spring/integration/kitchen/KitchenService.java b/2020-08/spring-28/spring-28-solution/src/main/java/ru/otus/spring/integration/kitchen/KitchenService.java
new file mode 100644
index 00000000..8e40715f
--- /dev/null
+++ b/2020-08/spring-28/spring-28-solution/src/main/java/ru/otus/spring/integration/kitchen/KitchenService.java
@@ -0,0 +1,16 @@
+package ru.otus.spring.integration.kitchen;
+
+import org.springframework.stereotype.Service;
+import ru.otus.spring.integration.domain.Food;
+import ru.otus.spring.integration.domain.OrderItem;
+
+@Service
+public class KitchenService {
+
+ public Food cook(OrderItem orderItem) throws Exception {
+ System.out.println("Cooking " + orderItem.getItemName());
+ Thread.sleep(3000);
+ System.out.println("Cooking " + orderItem.getItemName() + " done");
+ return new Food(orderItem.getItemName());
+ }
+}