Статьи

Выполнение запросов по запросу: исходящий шлюз MongoDB

1. Введение

Для чтения данных из MongoDb Spring Integration поставляется с адаптером входящего канала MongoDb. Этот адаптер использует модуль опроса для непрерывного получения документов из базы данных. Однако иногда нам может потребоваться запросить базу данных по запросу, основываясь на результате другой конечной точки.

Используя преимущества расширяемости Spring, я реализовал исходящий шлюз MongoDb. Назначение этого шлюза — отреагировать на какой-либо запрос, сделать запрос к базе данных и вернуть результат.

Чтобы показать вам, как работает шлюз, я буду использовать простой пример и модифицирую его для реализации шлюза с различными конфигурациями.

Этот пример состоит из шлюза обмена сообщениями в качестве точки входа в поток интеграции. Как только сообщение поступает в поток, исходящий шлюз mongoDb выполняет запрос к базе данных, а затем результат отправляется на другой канал, где его обрабатывает активатор службы.

Исходный код для этих примеров и реализации шлюза можно найти в моем репозитории .

2- пример Java DSL

Я реализовал статический класс MongoDb, чтобы упростить определение шлюза. Я взял идею из класса Spring Integration Jpa .

В следующей конфигурации вы можете увидеть поток requestPerson . Вызов метода send PersonService отправит сообщение потоку, где исходящий шлюз mongoDb запросит базу данных с помощью предварительно определенного запроса ({id: 1}):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@ComponentScan("xpadro.spring.integration.mongodb")
@IntegrationComponentScan("xpadro.spring.integration.mongodb")
public class JavaDSLQueryConfiguration {
     
    @MessagingGateway
    public interface PersonService {
         
        @Gateway(requestChannel = "requestPerson.input")
        void send(RequestMessage requestMessage);
    }
 
    @Bean
    @Autowired
    public IntegrationFlow requestPerson(MongoDbFactory mongo) {
        return f -> f
                .handle(outboundGateway(mongo))
                .handle(resultHandler(), "handle");
    }
 
    @Bean
    public ResultHandler resultHandler() {
        return new ResultHandler();
    }
 
    private MongoDbOutboundGatewaySpec outboundGateway(MongoDbFactory mongo) {
        return MongoDb.outboundGateway(mongo)
                .query("{id: 1}")
                .collectionNameExpression(new LiteralExpression("person"))
                .expectSingleResult(true)
                .entityClass(Person.class);
    }
}

Обработчик результатов — это «очень полезный» компонент, который будет регистрировать найденного человека:

1
2
3
4
5
6
public class ResultHandler {
 
    public void handle(Person person) {
        System.out.println(String.format("Person retrieved: %s", person));
    }
}

Чтобы запустить поток, следующее приложение отправляет сообщение на шлюз PersonService:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
@SpringBootApplication
@EnableIntegration
@Import(JavaDSLQueryConfiguration.class)
public class JavaDSLQueryApplication extends AbstractApplication {
 
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(JavaDSLQueryApplication.class, args);
        new JavaDSLQueryApplication().start(context);
    }
 
    public void start(ConfigurableApplicationContext context) {
        resetDatabase(context);
 
        JavaDSLQueryConfiguration.PersonService personService = context.getBean(JavaDSLQueryConfiguration.PersonService.class);
        personService.send(new RequestMessage());
    }
}

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

3- пример Java DSL с динамическим выражением запроса

В предыдущем примере было полезно посмотреть, как определить шлюз, но жестко закодированный запрос может быть не самым распространенным случаем.

В этом примере запрос определяется в сообщении, отправляемом в поток интеграции:

1
personService.send(new RequestMessage("{id : 2}"));

В файле конфигурации свойство шлюза queryExpression разрешает запрос динамически, извлекая свойство data из полезной нагрузки сообщения:

1
2
3
4
5
6
7
private MongoDbOutboundGatewaySpec outboundGateway(MongoDbFactory mongo) {
        return MongoDb.outboundGateway(mongo)
                .queryExpression("payload.data")
                .collectionNameExpression(new LiteralExpression("person"))
                .expectSingleResult(true)
                .entityClass(Person.class);
    }

4- Пример Java DSL, возвращающий несколько результатов

Два предыдущих примера извлекли один документ из базы данных. В следующем примере запрос возвращает список со всеми документами, соответствующими запросу:

В сообщении запроса мы указываем запрос, чтобы найти все документы в коллекции лиц:

1
personService.send(new RequestMessage("{}"));

В конфигурации мы должны удалить свойство waitSingleResult из шлюза (или установить для него значение false). Дополнительно мы можем указать лимит:

1
2
3
4
5
6
7
private MongoDbOutboundGatewaySpec outboundGateway(MongoDbFactory mongo) {
        return MongoDb.outboundGateway(mongo)
                .queryExpression("payload.data")
                .collectionNameExpression(new LiteralExpression("person"))
                .entityClass(Person.class)
                .maxResults(2);
    }

Наконец, мы должны определить другой метод в классе ResultHandler для обработки нескольких результатов:

1
2
3
4
public void handle(List persons) {
        String names = persons.stream().map(Person::getName).collect(Collectors.joining(", "));
        System.out.println(String.format("Persons retrieved: %s", names));
    }

5- Пример конфигурации Java

В этом последнем примере Java Config используется вместо Java DSL для настройки всего потока. На стороне приложения все то же самое. Мы просто запрашиваем персональную службу для конкретного документа:

1
personService.send(new RequestMessage("{id : 3}"));

При использовании Java Config мы должны построить MongoDbExecutor , который используется шлюзом для выполнения запросов.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@ComponentScan("xpadro.spring.integration.mongodb")
@IntegrationComponentScan("xpadro.spring.integration.mongodb")
public class JavaConfigQueryConfiguration {
 
    @MessagingGateway
    public interface PersonService {
 
        @Gateway(requestChannel = "personInput")
        void send(RequestMessage requestMessage);
    }
 
    @Bean
    public ResultServiceActivator resultHandler() {
        return new ResultServiceActivator();
    }
 
    @Bean
    @ServiceActivator(inputChannel = "personInput")
    public MessageHandler mongodbOutbound(MongoDbFactory mongo) {
        MongoDbExecutor mongoDbExecutor = new MongoDbExecutor(mongo);
        mongoDbExecutor.setCollectionNameExpression(new LiteralExpression("person"));
        mongoDbExecutor.setMongoDbQueryExpression("payload.data");
        mongoDbExecutor.setExpectSingleResult(true);
        mongoDbExecutor.setEntityClass(Person.class);
 
        MongoDbOutboundGateway gateway = new MongoDbOutboundGateway(mongoDbExecutor);
        gateway.setOutputChannelName("personOutput");
 
        return gateway;
    }
}

Прослушивая выходной канал шлюза, мы определяем активатор службы для обработки найденного человека:

1
2
3
4
5
6
7
public class ResultServiceActivator {
 
    @ServiceActivator(inputChannel = "personOutput")
    public void handle(Person person) {
        System.out.println(String.format("Person retrieved: %s", person));
    }
}

6. Заключение

Исходящий шлюз подходит, когда вам нужно выполнять запросы по требованию вместо активного опроса базы данных. В настоящее время эта реализация поддерживает настройку с помощью Java Config и Java DSL. На данный момент я не реализовал парсеры, необходимые для поддержки конфигурации XML, так как я думаю, что эти два способа конфигурации покрывают основную необходимость.

Если вы нашли этот пост полезным, поделитесь им или пометьте мой репозиторий 🙂

Я публикую свои новые сообщения в Google Plus и Twitter. Следуйте за мной, если вы хотите быть в курсе нового контента.