Pull to refresh

Comments 16

Вопрос касательно микросервисной архитектуры.

Всем известно, что у фреймворка SpringBoot существует своя экосистема для построения микросервисов. Также, не секрет, известно, что проектов, построенные на Apache Camel, хорошо работают под управлением контейнеров Apache Karaf.

Мне интересно, есть ли возможность реализации проекта с интерфейсом для пользователя, написанном на SpringBoot, бизнес-логика которого использует Apache Camel в качестве маршрутизатора потока информации, в виде бандла (bundle) для Apache Karaf? Запустится ли SpringBoot внутри Karaf-контейнера? Или следует использовать экосистему SpringBoot-а для этих целей, добавляя в нее модуль Apache Camel? Или, возможно, нужно их держать отдельно, связывая посредством Message-брокеров (ActiveMQ) и баз данных?
Зачем вам spring boot внутри карафа? Это практически лишено смысла.
Да, немного почитав, я, что не нужно.
Вопрос по работе с бинами в роутах.
Начну с описания проблемы, которую нужно решить.
В процессе обработки сообщения нужно выполнить определённую бизнес-логику. Бизнес-логика инкапсулирована в бине. В качестве di-контейнера используется spring.
Сделать это можно, например, так:
.bean("myBeanName")

Далее, в контейнере хранится несколько объектов одного класса, отличающиеся названием бина ну и, соответственно, преобразованием сообщения. В сообщении в свойствах хранится название бина, который должен его обработать.
Теперь суть вопроса: как в роуте указать, какой именно бин нужно выполнить?
Т.е. что-то типа такого:
.bean("${exchangeProperty[propertyName].getMyBeanName()}")

Т.о. имеем роут, который динамически получает из сообщения название бина и выполняет его. При получении разных сообщений выполняем разные бины.
.bean(exchangeProperty[propertyName].getMyBeanName())

а так не сработает?
Нет, exchangeProperty возвращает ValueBuilder. Я его не могу привести к кастомному типу.
Вообще, я нашёл способ выполнить бин по названию из свойств самого сообщения:
.routingSlip(simple("bean:${exchangeProperty[propertyName].getMyBeanName()}"))

Но есть 2 проблемы.
1. По-моему, костыльно. Я думаю, должен быть какой-то «правильный» способ получить название бина из свойств сообщения.
2. Один из роутов мне нужно выполнить с транзакцией:
.transacted("customTransactionPolicy")

, где customTransactionPolicy — имя кастомного бина, который так же меняется в зависимости от того, какое сообщение пришло в роут.
Вот здесь мне не удалось никак подставить нужный бин.
Я создал тестовый проект из мавен-архетипа camel-archetype-spring-boot, и сделал вот что:

@Component
public class TestBean1 {

    @Handler
    public void test1(Exchange exchange) {
        System.out.println("TEST 1");
    }

}

@Component
public class MySpringBootRouter extends RouteBuilder {

    @Override
    public void configure() {

        Object bean1 = getContext().getRegistry().lookupByName("testBean1");

        from("timer:trigger")
                .bean(bean1)
                .transform().simple("ref:myBean")
                .to("log:out");
    }

    @Bean
    String myBean() {
        return "I'm Spring bean!";
    }

}



и получаю в консоль вот такое:

TEST 1
2018-04-02 15:27:56.643 INFO 15716 --- [timer://trigger] out : Exchange[ExchangePattern: InOnly, BodyType: String, Body: I'm Spring bean!]
TEST 1
2018-04-02 15:27:57.624 INFO 15716 --- [timer://trigger] out : Exchange[ExchangePattern: InOnly, BodyType: String, Body: I'm Spring bean!]
TEST 1
2018-04-02 15:27:58.624 INFO 15716 --- [timer://trigger] out : Exchange[ExchangePattern: InOnly, BodyType: String, Body: I'm Spring bean!]
TEST 1
2018-04-02 15:27:59.623 INFO 15716 --- [timer://trigger] out : Exchange[ExchangePattern: InOnly, BodyType: String, Body: I'm Spring bean!]
TEST 1
2018-04-02 15:28:00.625 INFO 15716 --- [timer://trigger] out : Exchange[ExchangePattern: InOnly, BodyType: String, Body: I'm Spring bean!]



Попробуйте использовать конструкцию
getContext().getRegistry().lookupByName("testBean1");
для получения конкретного бина по имени.
В вашем примере на момент загрузки роута уже известно имя бина — «testBean1»:
Object bean1 = getContext().getRegistry().lookupByName("testBean1");

По идее, в этом случае роут должен отработать правильно и если просто указать:
.bean("testBean1")


В случае с динамическим получением бина проблема будет выглядеть так:
Есть 2 объекта — MasterObject1 и MasterObject2, реализующих общий интерфейс.

// Первый основной роут 
fromF("timer://mainRoute1?period=5s")
     // устанавливаем в свойство "master" объект, который содержит имя целевого бина  
     .process(exchange -> exchange.setProperty("master", MasterObject1))
     .toF("direct:actionRoute")
;

// Второй основной роут 
fromF("timer://mainRoute2?period=5s")
     .process(exchange -> exchange.setProperty("master", MasterObject2))
     .toF("direct:actionRoute")
;

// Роут, в котором нужно выполнить бизнес-логику, инкапсулированную в конкретном бине объекта 
fromF("direct:actionRoute")
     // а здесь пробуем получить бин из объекта, который ранее положили в свойство
     .bean("${exchangeProperty[master].getMyBeanName()}")
;



Вот проблема именно в этом месте:
.bean("${exchangeProperty[master].getMyBeanName()}")

Имя бина неизвестно на момент загрузки роута.
Кажется, я нашел решение для вашего случая. Описано здесь.

Вот тест:

@Component
public class BeanResolver {
    public String routeMe(String body, Exchange exchange) {
        return "bean:"+exchange.getProperty("master");
    }
}

@Component
public class MasterObject1 {

    private static final Logger logger = LoggerFactory.getLogger(MasterObject1.class);

    @Handler
    public void handler(Exchange exchange) {
        logger.warn("HELLO FROM "+this.getClass().getName());
    }
}

@Component
public class MySpringBootRouter extends RouteBuilder {

    @Override
    public void configure() {

        from("timer:trigger")
                .process(exchange -> exchange.setProperty("master", "masterObject1"))
                .dynamicRouter(method(BeanResolver.class, "routeMe"));
    }

}



Выхлоп:

2018-04-03 10:39:07.695 WARN 3228 --- [timer://trigger] ru.bvn13.test.MasterObject1 : HELLO FROM ru.bvn13.test.MasterObject1
2018-04-03 10:39:07.695 WARN 3228 --- [timer://trigger] ru.bvn13.test.MasterObject1 : HELLO FROM ru.bvn13.test.MasterObject1
Да, это может быть возможным решением, если код немного доработать. Класс BeanResolver будет выглядеть так:
@Component
public class BeanResolver {
    private static Boolean EXECUTED = false;

    public String execute(Exchange exchange) {
        if (EXECUTED) {
            EXECUTED = false;
            return null;
        } else {
            EXECUTED = true;
            return "bean:" + ((Agent) exchange.getProperty("master")).getBean();
        }
    }
}


Здесь проблема в том, что элемент .dynamicRouter() выполняется циклично до тех пор, пока выражение для определения бина не вернёт null.
camel.apache.org/dynamic-router.html
В данном случае придётся запилить статическую переменную, которая будет следить за тем, чтобы роут выполнился только один раз.

В данном случае проще воспользоваться тем примером, что я скидывал выше:
.routingSlip(simple("bean:${exchangeProperty[master].getBean()}"))

Писать меньше, но всё-равно костыльно.

Но оба решения у меня не получилось использовать для .transacted(«myBeanName»)
Этот элемент принимает только название бина в виде строки. Я пробовал передавать метод, который возвращает строку, но не могу получить объект exchange в методе.
Но всё-равно большое спасибо за советы. Я просто предполагал, что должен быть какой-то простой способ получения бина динамически, т.к. мне это не кажется какой-то экзотической задачей.
Я тут поразмыслил над вашим кейсом. Вряд ли у вас возможен случай, когда вы не можете достоверно определить бин. В том смысле, что название бина составляется в рантайме. Скорее всего, выбор все-таки из конечного числа бинов. И если добавляется новый обработчик-бин, то нужно его прописать и перекомпилировать. В таком случае вам подойдет вариант с Router-ом. Т.е. вы добавляете бин, прописываете проперти и добавляете условие по которому этот бин «сработает».

from("timer:trigger")
    .process(exchange -> exchange.setProperty("master", "masterObject1"))
    .choice()
         .when(exchange -> exchange.getProperty("master").equals("masterObject1"))
         .bean("masterObject1")
         .otherwise().log("NONE")

Задавайте вопросы Клаусу в комментариях к этому тексту, и автор самого интересного вопроса (которого мы с Клаусом выберем уже 2 апреля) получит от нас и наших друзей из Jug.ru билет на конференцию и сможет не только услышать Клауса и других участников вживую, но и принять участие в розыгрыше книги «Camel in Action».

Уже не будет розыгрыша? или это и был розыгрыш?
Здравствуйте! Как раз показываем Клаусу вопросы, к вечеру отпишемся
Я один не понимаю, откуда мне знать время, за которое скопируется файл? Для чего это вообще тут? Почему нельзя подождать просто завершения операции? Такие примеры пугают.
Здравствуйте! Говорят, что эта задержка сделана потому что операция копирования происходит один раз и сам процесс останавливается. То есть он делает это копирование один раз и останавливается, поэтому вставлена задержка:
github.com/camelinaction/camelinaction2/blob/9c63fb9bb851ebda3391c9736c94bc66f88c8765/chapter1/file-copy/src/main/java/camelinaction/FileCopierWithCamel.java

Мы также добавили метод sleep, чтобы дать приложению Camel время на копирование файлов, так как в демонстрационном режиме в отличии от реальных систем оно не запущено постоянно, а будет работать на время установленной задержки между стартом и остановкой контекста.

Sign up to leave a comment.