Статьи

Spring Integration — Приложение с нуля, часть 1

Прежде чем мы начнем

В этом уроке вы узнаете, что такое Spring Integration , как его использовать и какие проблемы он помогает решать. Мы создадим образец приложения с нуля и продемонстрируем некоторые из основных компонентов Spring Integration. Если вы новичок в Spring, посмотрите другой учебник по Spring, написанный мной — Должны ли мы сделать Spring вместе? Также обратите внимание, что вам не нужны никакие специальные инструменты, однако вы можете получить лучший опыт для создания приложений Spring Integration с помощью IntelliJ IDEA или Spring Tool Suite (вы можете получить некоторые причудливые диаграммы с STS). Вы можете либо следовать этому руководству шаг за шагом и самостоятельно создавать приложение с нуля, либо вы можете пойти дальше и получить код из github:

СКАЧАТЬ ИСТОЧНИКИ ЗДЕСЬ: https://github.com/vrto/spring-integration-invoices

Какой бы путь вы ни выбрали, пора начинать!

Приложение для обработки счетов — функциональное описание

Представьте, что вы работаете в какой-то компании, которая периодически получает большое количество счетов от различных подрядчиков. Мы собираемся создать систему, которая сможет получать счета, отфильтровывать соответствующие, создавать платежи (местные или иностранные) и отправлять их в некоторые банковские службы. Несмотря на то, что система будет довольно наивной и, конечно, не готовой для предприятия, мы постараемся создать ее с хорошей масштабируемостью, гибкостью и независимым дизайном.

Прежде чем продолжить, вы должны понять одну вещь: Spring Integration — это не только обмен сообщениями . Spring Integration — это встроенная корпоративная сервисная шина, которая позволяет беспрепятственно подключать бизнес-логику к каналам обмена сообщениями. Сообщения могут обрабатываться как программно (через Spring Integration API), так и автоматически (самой платформой — более высокий уровень развязки). Сообщение — это то, что путешествует по каналам. Сообщение имеет заголовки и полезную нагрузку — что в нашем случае будет актуальным релевантным контентом (классами домена). Давайте посмотрим на следующую картину, которая является кратким описанием системы, и пройдемся по важным частям:

Схема интеграции счетов

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

  1. Invoices Gateway — это место, где мы будем выставлять новые счета, чтобы они могли войти в слой обмена сообщениями
  2. Splitter — система предназначена для приема коллекции счетов, но нам нужно будет обрабатывать каждый счет отдельно. Более конкретно, сообщение с полезной нагрузкой типа «Коллекция» будет разделено на несколько сообщений, где каждое сообщение будет иметь индивидуальную накладную в качестве полезной нагрузки.
  3. Фильтр — наша система предназначена для автоматической обработки только тех счетов, которые выдают менее 10 000 долларов США.
  4. Маршрутизатор. В некоторых счетах используются номера счетов IBAN, и у нас есть две разные учетные записи: одна для локальных транзакций, а другая для иностранных транзакций. Задача компонента маршрутизатора — отправить сообщение, содержащее счет-фактуру, на правильный канал — либо для локальных счетов, либо для внешних счетов.
  5. Трансформаторы. Несмотря на то, что мы принимаем счета в систему, наши банковские API работают с другими типами — Платежами. Работа компонента-преобразователя состоит в том, чтобы принять какое-то сообщение и преобразовать его в другое сообщение в соответствии с предоставленной логикой. Мы хотим преобразовать полезную нагрузку исходного сообщения (инвойса) в новую полезную нагрузку — платеж.
  6. Активатор банковского обслуживания — после того, как мы обработали счета и произвели некоторые фактические платежи, мы готовы поговорить с внешней банковской системой. Мы показали обслуживание таких систем, и когда сообщение, несущее платеж, поступает в правильный (банковский) канал, мы хотим активировать некоторую логику — передать платеж банку и позволить банку выполнить дальнейшую обработку.

Создание проекта

К настоящему времени у вас должен быть общий обзор того, что делает система и как она структурирована. Перед тем, как мы начнем писать код, вам понадобится настоящий проект Maven, а также настройка структуры и требуемых зависимостей. Если вы знакомы с Maven, посмотрите файл pom.xml ниже, в противном случае, если вы хотите сэкономить время, вы можете использовать шаблон проекта, который я создал для вас: скачайте шаблон проекта Maven .

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?xml version='1.0' encoding='UTF-8'?>
         xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
    <modelVersion>4.0.0</modelVersion>
 
    <groupId>spring-integration-invoices</groupId>
    <artifactId>spring-integration-invoices</artifactId>
    <version>1.0-SNAPSHOT</version>
 
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>3.2.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.integration</groupId>
            <artifactId>spring-integration-core</artifactId>
            <version>2.2.1.RELEASE</version>
        </dependency>
 
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.16</version>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>13.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>6.5.2</version>
        </dependency>
    </dependencies>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
 
</project>

Давайте теперь рассмотрим шесть основных компонентов системы более подробно и разберемся с реальным кодом.

1. Шлюз счетов

Во-первых, давайте посмотрим код для Invoice — который будет одним из основных классов в нашей системе. Я буду использовать пакет com.vrtoonjava в качестве корневого пакета, а счета и банковские операции — в качестве подпакетов :

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.vrtoonjava.invoices;
 
import com.google.common.base.Objects;
 
import java.math.BigDecimal;
 
public class Invoice {
 
    private final String iban;
    private final String address;
    private final String account;
    private final BigDecimal dollars;
 
    public Invoice(String iban, String address, String account, BigDecimal dollars) {
        this.iban = iban;
        this.address = address;
        this.account = account;
        this.dollars = dollars;
    }
 
    public boolean isForeign() {
        return null != iban && !iban.isEmpty();
    }
 
    public String getAddress() {
        return address;
    }
 
    public String getAccount() {
        return account;
    }
 
    public BigDecimal getDollars() {
        return dollars;
    }
 
    public String getIban() {
        return iban;
    }
 
    @Override
    public String toString() {
        return Objects.toStringHelper(this)
                .add('iban', iban)
                .add('address', address)
                .add('account', account)
                .add('dollars', dollars)
                .toString();
    }
 
}

Представьте, что мы получаем счета из другой системы (будь то база данных, веб-сервис или что-то еще), но мы не хотим связывать эту часть с уровнем интеграции. Мы будем использовать компонент Gateway для этой цели. Шлюз вводит контракт, который отделяет клиентский код от уровня интеграции (в нашем случае это зависимости от Spring Integration). Давайте посмотрим код для InvoiceCollectorGateway :

01
02
03
04
05
06
07
08
09
10
11
12
package com.vrtoonjava.invoices;
 
import java.util.Collection;
 
/**
 * Defines a contract that decouples client from the Spring Integration framework.
 */
public interface InvoiceCollectorGateway {
 
    void collectInvoices(Collection<Invoice> invoices);
 
}

Теперь, чтобы фактически использовать Spring Integration, нам нужно создать стандартный файл конфигурации Spring и использовать пространство имен Spring Integration. Чтобы начать, вот файл invoices-int-schema.xml . Поместите его в src / main / resources . Обратите внимание, что мы уже определили адаптер канала регистрации — это специальный канал, куда мы будем отправлять сообщения из регистратора. Мы также используем прослушивание — вы можете думать о нем как о каком-то глобальном перехватчике, который будет отправлять связанные с журналом сообщения на канал регистратора.

01
02
03
04
05
06
07
08
09
10
<?xml version='1.0' encoding='UTF-8'?>
       xmlns:xsi = 'http://www.w3.org/2001/XMLSchema-instance'
 
    <!-- intercept and log every message -->
    <int:logging-channel-adapter id='logger' level='DEBUG' />
    <int:wire-tap channel = 'logger' />
</beans>

Давайте вернемся к нашим воротам сейчас. Мы определили интерфейс шлюза — это зависимость, которую будет использовать клиент. Когда клиент вызывает метод collectInvoices , шлюз отправит новое сообщение (содержащее полезную нагрузку List) в канал newInvoicesChannel . Это оставляет клиента отделенным от средств обмена сообщениями, но позволяет нам поместить результат в реальный канал обмена сообщениями. Чтобы настроить шлюз, добавьте следующий код в конфигурацию схемы интеграции:

1
2
3
4
5
6
<int:channel id = 'newInvoicesChannel' />
 
<int:gateway id='invoicesGateway'
     service-interface='com.vrtoonjava.invoices.InvoiceCollectorGateway'>
    <int:method name='collectInvoices' request-channel='newInvoicesChannel' />
</int:gateway>

2. Счет Разделитель

Со Шлюза мы отправляем одно большое сообщение в систему, которое содержит коллекцию счетов-фактур, другими словами, — Сообщение имеет полезную нагрузку типа Коллекции. Поскольку мы хотим обрабатывать счета по отдельности, мы получим результат из newInvoicesChannel и используем компонент сплиттера, который создаст несколько сообщений. Каждое из этих новых сообщений будет иметь полезную нагрузку типа Invoice. Затем мы разместим сообщения на новый канал — singleInvoicesChannel . Мы будем использовать Splitter по умолчанию, который предоставляет Spring Integration (по умолчанию Spring Integration использует DefaultMessageSplitter, который делает именно то, что мы хотим). Вот как мы определяем сплиттер:

1
2
3
4
5
<int:splitter
        input-channel='newInvoicesChannel'
        output-channel='singleInvoicesChannel' />
 
<int:channel id = 'singleInvoicesChannel' />

3. Фильтрация некоторых счетов

Бизнес-пример использования нашей системы требует от нас автоматической обработки только тех счетов, которые выдают нам менее 10 000 долларов США. Для этого мы введем компонент Filter . Мы будем получать сообщения из singleInvoicesChannel , применять к ним нашу логику фильтрации, а затем записывать сопоставленные результаты в новый канал FilterInvoicesChannel . Во-первых, давайте создадим стандартный класс Java, который будет содержать логику фильтрации для одного счета. Обратите внимание, что мы используем аннотацию @Component (что делает его стандартным компонентом Spring) и помечаем метод фильтрации аннотацией @Filter , которая сообщит Spring Integration использовать этот метод для фильтрации логики:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.vrtoonjava.invoices;
 
import org.springframework.integration.annotation.Filter;
import org.springframework.stereotype.Component;
 
@Component
public class InvoiceFilter {
 
    public static final int LOW_ENOUGH_THRESHOLD = 10_000;
 
    @Filter
    public boolean accept(Invoice invoice) {
        boolean lowEnough =
                invoice.getDollars().intValue() < LOW_ENOUGH_THRESHOLD;
        System.out.println('Amount of $' + invoice.getDollars()
                + (lowEnough ? ' can' : ' can not')
                + ' be automatically processed by system');
 
        return lowEnough;
    }
 
}

Обратите внимание, что это стандартный POJO, который мы можем легко протестировать ! Как я уже говорил, Spring Integration не связывает нас с возможностями обмена сообщениями. Для краткости я не буду вставлять юнит-тесты в этом руководстве, но если вам интересно, скачайте проект github и посмотрите сами тесты .

Давайте определим каналы ввода / вывода для уровня сообщений и подключим фильтр. Добавьте следующий код в конфигурацию схемы интеграции:

1
2
3
4
5
6
<int:filter
    input-channel='singleInvoicesChannel'
    output-channel='filteredInvoicesChannel'
    ref='invoiceFilter' />
 
<int:channel id = 'filteredInvoicesChannel' />

4. Маршрутизация счетов

Пока что мы разделили и отфильтровали некоторые счета. Теперь пришло время более тщательно проверить содержание каждого счета и решить, является ли он счетом, выданным из текущей страны (местная) или из другой страны (иностранная). Для этого мы можем подойти как и прежде и использовать собственный класс для логики маршрутизации. Мы (для демонстрационных целей) сейчас воспользуемся другим подходом — мы установим Spring Expression Language (SpEL), чтобы использовать и обрабатывать маршрутизацию полностью декларативно. Помните метод isForeign в классе Invoice? Мы можем напрямую вызвать его с помощью SpEL в объявлении маршрутизатора (используя атрибут селекторного выражения)! Маршрутизатор взглянет на полезную нагрузку, оценит, является ли это иностранный или локальный счет, и перенаправит его на соответствующий канал:

1
2
3
4
5
6
7
<int:recipient-list-router input-channel='filteredInvoicesChannel'>
    <int:recipient channel = 'foreignTransactions' selector-expression='payload.foreign' />
    <int:recipient channel = 'localTransactions' selector-expression='!payload.foreign' />
</int:recipient-list-router>
 
<int:channel id = 'foreignTransactions' />
<int:channel id = 'localTransactions' />

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

Ссылка: Spring Integration — Приложение с нуля, часть 1 от нашего партнера JCG Михала Вртиака в блоге vrtoonjava .