0

I want to rewrite following xml sample using java DSL

xml config:

    <int:channel id="findUserServiceChannel"/>
    <int:channel id="findUserByUsernameServiceChannel"/>

    <!-- See also:
        https://docs.spring.io/spring-integration/reference/htmlsingle/#gateway-proxy
        https://www.enterpriseintegrationpatterns.com/MessagingGateway.html -->
    <int:gateway id="userGateway" default-request-timeout="5000"
                 default-reply-timeout="5000"
                 service-interface="org.springframework.integration.samples.enricher.service.UserService">
        <int:method name="findUser"                  request-channel="findUserEnricherChannel"/>
        <int:method name="findUserByUsername"        request-channel="findUserByUsernameEnricherChannel"/>
        <int:method name="findUserWithUsernameInMap" request-channel="findUserWithMapEnricherChannel"/>
    </int:gateway>

    <int:enricher id="findUserEnricher"
                  input-channel="findUserEnricherChannel"
                  request-channel="findUserServiceChannel">
        <int:property name="email"    expression="payload.email"/>
        <int:property name="password" expression="payload.password"/>
    </int:enricher>

    <int:enricher id="findUserByUsernameEnricher"
                  input-channel="findUserByUsernameEnricherChannel"
                  request-channel="findUserByUsernameServiceChannel"
                  request-payload-expression="payload.username">
        <int:property name="email"    expression="payload.email"/>
        <int:property name="password" expression="payload.password"/>
    </int:enricher>

    <int:enricher id="findUserWithMapEnricher"
                  input-channel="findUserWithMapEnricherChannel"
                  request-channel="findUserByUsernameServiceChannel"
                  request-payload-expression="payload.username">
        <int:property name="user"    expression="payload"/>
    </int:enricher>

    <int:service-activator id="findUserServiceActivator"
                           ref="systemService" method="findUser"
                           input-channel="findUserServiceChannel"/>

    <int:service-activator id="findUserByUsernameServiceActivator"
                           ref="systemService" method="findUserByUsername"
                           input-channel="findUserByUsernameServiceChannel"/>

    <bean id="systemService"
          class="org.springframework.integration.samples.enricher.service.impl.SystemService"/>

For now I have following:

config:

@Configuration
@EnableIntegration
@IntegrationComponentScan
public class Config {

    @Bean
    public SystemService systemService() {
        return new SystemService();
    }

    @Bean
    public IntegrationFlow findUserEnricherFlow(SystemService systemService) {
        return IntegrationFlows.from("findUserEnricherChannel")
                .<User>handle((p, h) -> systemService.findUser(p))
                .get();
    }

    @Bean
    public IntegrationFlow findUserByUsernameEnricherFlow(SystemService systemService) {
        return IntegrationFlows.from("findUserByUsernameEnricherChannel")
                .<User>handle((p, h) -> systemService.findUserByUsername(p.getUsername()))
                .get();
    }

    @Bean
    public IntegrationFlow findUserWithUsernameInMapFlow(SystemService systemService) {
        return IntegrationFlows.from("findUserWithMapEnricherChannel")
                .<Map<String, Object>>handle((p, h) -> {
                    User user = systemService.findUserByUsername((String) p.get("username"));
                    Map<String, Object> map = new HashMap<>();
                    map.put("username", user.getUsername());
                    map.put("email", user.getEmail());
                    map.put("password", user.getPassword());
                    return map;
                })
                .get();
}
}

service interface:

@MessagingGateway
public interface UserService {

    /**
     * Retrieves a user based on the provided user. User object is routed to the
     * "findUserEnricherChannel" channel.
     */
    @Gateway(requestChannel = "findUserEnricherChannel")
    User findUser(User user);

    /**
     * Retrieves a user based on the provided user. User object is routed to the
     * "findUserByUsernameEnricherChannel" channel.
     */
    @Gateway(requestChannel = "findUserByUsernameEnricherChannel")
    User findUserByUsername(User user);

    /**
     * Retrieves a user based on the provided username that is provided as a Map
     * entry using the mapkey 'username'. Map object is routed to the
     * "findUserWithMapChannel" channel.
     */
    @Gateway(requestChannel = "findUserWithMapEnricherChannel")
    Map<String, Object> findUserWithUsernameInMap(Map<String, Object> userdata);

}

and target service:

public class SystemService {

    public User findUser(User user) {
            ...
    }

    public User findUserByUsername(String username) {
            ...    
    }

}

main method:

public static void main(String[] args) {
    ConfigurableApplicationContext ctx = new SpringApplication(MyApplication.class).run(args);
    UserService userService = ctx.getBean(UserService.class);
    User user = new User("some_name", null, null);
    System.out.println("Main:" + userService.findUser(user));
    System.out.println("Main:" + userService.findUserByUsername(user));
    Map<String, Object> map = new HashMap<>();
    map.put("username", "vasya");
    System.out.println("Main:" + userService.findUserWithUsernameInMap(map));
}

output:

2019-08-30 14:09:29.956  INFO 12392 --- [           main] enricher.MyApplication                   : Started MyApplication in 2.614 seconds (JVM running for 3.826)
2019-08-30 14:09:29.966  INFO 12392 --- [           main] enricher.SystemService                   : Calling method 'findUser' with parameter User{username='some_name', password='null', email='null'}
Main:User{username='some_name', password='secret', email='some_name@springintegration.org'}
2019-08-30 14:09:29.967  INFO 12392 --- [           main] enricher.SystemService                   : Calling method 'findUserByUsername' with parameter: some_name
Main:User{username='some_name', password='secret', email='some_name@springintegration.org'}
2019-08-30 14:09:29.967  INFO 12392 --- [           main] enricher.SystemService                   : Calling method 'findUserByUsername' with parameter: vasya
Main:{password=secret, email=vasya@springintegration.org, username=vasya}

As you can see everything is working properly but I do transformations inside the configuration. I am not sure if I have to do it because xml configuration dooesn't have such transformations and everything somehow works using internal magic. Is it correct way or should I use some internal DSL magic for transformations?

P.S.

I suppose that Config class can be simplified somehow. I mean findUserByUsernameEnricherFlow findUserWithUsernameInMapFlow methods

update

I realized that I don't really understand how the XML config works:

Let's consider method Userservice#findUserWithUsernameInMap method

It has following interface:

Map<String, Object> findUserWithUsernameInMap(Map<String, Object> userdata);

And it eventually invokes findUserByUsername method of SystemService:

public User findUserByUsername(String username) 

Because client code work with Userservice there are 2 transformations inside:

  1. on way TO (before SystemService#findUserByUsername invocation) because Userservice#findUserWithUsernameInMapaccept Map<String, Object> but SystemService#findUserByUsername accepts String

  2. On way BACK(afterSystemService#findUserByUsername invocation) because SystemService#findUserByUsernamereturns User but Userservice#findUserWithUsernameInMap returns Map<String, Object>

Where exactly these transformations are declared in the xml configuration?

I have a suggestion that request-payload-expression is ised to make TO tranformation. Looks like it can work with Map using the same manner as with Object. But BACK transformation is not clear at all. Sure configiration has

<int:property name="user"    expression="payload"/>

But I have no idea what does it mean.

gstackoverflow
  • 36,709
  • 117
  • 359
  • 710
  • Would you mind to simplify your question to exact piece of code you don't like. Right now it is too much custom code and I believe may of those like are not related to the topic. Please, be as clear as possible and respect other people time who would like to help you... – Artem Bilan Aug 30 '19 at 13:38
  • @Artem Bilan I am sorry. I added information about code which might be simplified. But I think all code is related to the topic and can't be removed – gstackoverflow Aug 30 '19 at 13:46

2 Answers2

2

The Java DSL equivalent of the <int:enricher> is .enrich(). so, your findUserEnricherFlow should be like this:

@Bean
public IntegrationFlow findUserEnricherFlow(SystemService systemService) {
    return IntegrationFlows.from("findUserEnricherChannel")
            .enrich((enricher) -> enricher
                         .requestChannel("findUserServiceChannel")
                         .propertyExpression("email", "payload.email")
                         .propertyExpression("password", "payload.password"))

            .get();
}

You still could simply your question pointing only to one gateway method and one enricher...

Artem Bilan
  • 113,505
  • 11
  • 91
  • 118
  • Will be SystemService somehow invoked in your example? I invoke **userService.findUser(user)** and I expect that SI will dispatch that request into theSystemService#findUser – gstackoverflow Aug 30 '19 at 14:03
  • The `SystemService` must be in the separate `IntegrationFlow` which has to be subscribed to that `findUserServiceChannel` request channel. Spring Integration doesn't dispatch in case of direct code call. That is already your `serService.findUser(user)` code responsibility to delegate properly. Here is just no any Spring Integration hooks. – Artem Bilan Aug 30 '19 at 14:14
  • Let me understand what is going on here. When I invoke **userService.findUser(user)** from main method SI *routes* request to the enricher and set 2 properties of some magic entity based on payload(user object with filled name). After it request redirected to the SystemService. So my question is what the ai, of enricher here and what the magic entity is filled inside the enricher – gstackoverflow Aug 31 '19 at 08:00
  • I understand that enricher is a thing between UserService and SystemService but theit interfaces are different so we have to have some adapter in the middle. Looks like enricher is an adapter here but I can't understand how it works here – gstackoverflow Aug 31 '19 at 08:13
  • I suppose it should be that enricher: https://www.enterpriseintegrationpatterns.com/patterns/messaging/DataEnricher.html – gstackoverflow Sep 01 '19 at 20:02
  • I added update to the topic with as much details as I was able to provide information – gstackoverflow Sep 01 '19 at 21:01
  • I added the answer - could you please take a look? Is it looks correct ? – gstackoverflow Sep 03 '19 at 14:02
  • OK. You can live with that as well. If you understand now how `enricher` works, there is nothing to discuss any more. – Artem Bilan Sep 03 '19 at 14:12
  • What do you mean when you write **You can live with that as well** ? Do you mean any negative about my source ? – gstackoverflow Sep 03 '19 at 14:17
  • Actually I found detailed explanation exactly this source here: https://docs.spring.io/spring-integration/docs/5.0.14.BUILD-SNAPSHOT/reference/html/messaging-transformation-chapter.html#payload-enricher – gstackoverflow Sep 03 '19 at 14:19
  • There is `requestSubFlow()` instead of `requestChannel()` and extra `IntegrationFlow` for that channel. So, your code might be shorter, but would not be so loosely coupled. – Artem Bilan Sep 03 '19 at 14:19
  • So, I think we are good. It is indeed might be the fact that Java configuration won't look same way as XML. I would say you have much more freedom with only Java, so you can have context, type safity and auto-completion in your IDE. – Artem Bilan Sep 03 '19 at 14:20
  • yes, using java for configuration much more convenient(at leas for me). I will try to use **requestSubFlow()** to be familiar with that feature. I want to know your opinion about **.enrich().handle()** instead of external IntegrationFlow – gstackoverflow Sep 03 '19 at 14:25
  • I investigated **requestSubFlow** source and found that it just extracts requestChannel from flow argument. It even doesn't register flow if it is not registered – gstackoverflow Sep 03 '19 at 14:32
  • added example with requestSubFlow to the answer. Is it correct ? it looks the same as requestChannel for me. – gstackoverflow Sep 03 '19 at 14:34
  • You don't need that ` from("findUserServiceChannel")` in the sub-flow. And it doesn't look fully as sub-flow in your case. Please, read docs on the matter: https://docs.spring.io/spring-integration/reference/html/#java-dsl-subflows – Artem Bilan Sep 03 '19 at 14:37
  • I read this part of manual but didn't get what you mean. Although I have a suggestion what you mean(updated last lines in answer). Is it what you mean now ? – gstackoverflow Sep 03 '19 at 14:57
  • That's OK, too, but what I mean by that doc is something like this: `enricherSpec.requestSubFlow(flow -> flow.handle((p, h) -> systemService.findUser(p)))`. Fully without that extra `subflow` method. – Artem Bilan Sep 03 '19 at 15:01
0

Eventually I was able to rewrite xml to java DSL. Unfortunately it is a bit more verbose:

config:

@Configuration
@EnableIntegration
@IntegrationComponentScan
public class Config {

    @Bean
    public SystemService systemService() {
        return new SystemService();
    }

    //first flow
    @Bean
    public IntegrationFlow findUserEnricherFlow() {
        return IntegrationFlows.from("findUserEnricherChannel")
                .enrich(enricherSpec ->
                        enricherSpec.requestChannel("findUserServiceChannel")
                                .<User>propertyFunction("email", (message) ->
                                        (message.getPayload()).getEmail()
                                ).<User>propertyFunction("password", (message) ->
                                (message.getPayload()).getPassword()
                        ))
                .get();
    }

    @Bean
    public IntegrationFlow findUserServiceFlow(SystemService systemService) {
        return IntegrationFlows.
                from("findUserServiceChannel")
                .<User>handle((p, h) -> systemService.findUser(p))
                .get();
    }

    //second flow
    @Bean
    public IntegrationFlow findUserByUsernameEnricherFlow() {
        return IntegrationFlows.from("findUserByUsernameEnricherChannel")
                .enrich(enricherSpec ->
                        enricherSpec.requestChannel("findUserByUsernameRequestChannel")
                                .<User>requestPayload(userMessage -> userMessage.getPayload().getUsername())
                                .<User>propertyFunction("email", (message) ->
                                        (message.getPayload()).getEmail()
                                ).<User>propertyFunction("password", (message) ->
                                (message.getPayload()).getPassword()
                        ))
                .get();

    }

    @Bean
    public IntegrationFlow findUserByUsernameServiceFlow(SystemService systemService) {
        return IntegrationFlows.from("findUserByUsernameRequestChannel")
                .<String>handle((p, h) -> systemService.findUserByUsername(p))
                .get();
    }

    //third flow
    @Bean
    public IntegrationFlow findUserWithUsernameInMapEnricherFlow() {
        return IntegrationFlows.from("findUserWithMapEnricherChannel")
                .enrich(enricherSpec ->
                        enricherSpec.requestChannel("findUserWithMapRequestChannel")
                                .<Map<String, User>>requestPayload(userMessage -> userMessage.getPayload().get("username"))
                                .<User>propertyFunction("user", Message::getPayload)
                ).get();
    }

    @Bean
    public IntegrationFlow findUserWithUsernameInMapServiceFlow(SystemService systemService) {
        return IntegrationFlows.from("findUserWithMapRequestChannel")
                .<String>handle((p, h) -> systemService.findUserByUsername(p))
                .get();
    }
}

Also I added a bit annotations in the UserService:

@MessagingGateway
public interface UserService {

    /**
     * Retrieves a user based on the provided user. User object is routed to the
     * "findUserEnricherChannel" channel.
     */
    @Gateway(requestChannel = "findUserEnricherChannel")
    User findUser(User user);

    /**
     * Retrieves a user based on the provided user. User object is routed to the
     * "findUserByUsernameEnricherChannel" channel.
     */
    @Gateway(requestChannel = "findUserByUsernameEnricherChannel")
    User findUserByUsername(User user);

    /**
     * Retrieves a user based on the provided username that is provided as a Map
     * entry using the mapkey 'username'. Map object is routed to the
     * "findUserWithMapChannel" channel.
     */
    @Gateway(requestChannel = "findUserWithMapEnricherChannel")
    Map<String, Object> findUserWithUsernameInMap(Map<String, Object> userdata);

}

All sources can be found here: https://github.com/gredwhite/spring-integration/tree/master/complete/src/main/java/enricher


Also I discovered that:

@Bean
public IntegrationFlow findUserEnricherFlow(SystemService systemService) {
    return IntegrationFlows.from("findUserEnricherChannel")
            .enrich(enricherSpec ->
                    enricherSpec//.requestChannel("findUserServiceChannel")
                            .<User>propertyFunction("email", (message) ->
                                    (message.getPayload()).getEmail()
                            ).<User>propertyFunction("password", (message) ->
                            (message.getPayload()).getPassword()
                    ))
            .get();
}

@Bean
public IntegrationFlow findUserServiceFlow(SystemService systemService) {
    return IntegrationFlows.
            from("findUserServiceChannel")
            .<User>handle((p, h) -> systemService.findUser(p))
            .get();
}

can be rewritten in more concise manner:

@Bean
public IntegrationFlow findUserEnricherFlow(SystemService systemService) {
    return IntegrationFlows.from("findUserEnricherChannel")
            .enrich(enricherSpec ->
                    enricherSpec//.requestChannel("findUserServiceChannel")
                            .<User>propertyFunction("email", (message) ->
                                    (message.getPayload()).getEmail()
                            ).<User>propertyFunction("password", (message) ->
                            (message.getPayload()).getPassword()
                    ))
            .<User>handle((p, h) -> systemService.findUser(p))
            .get();
}

one more option:

@Bean
public IntegrationFlow findUserEnricherFlow(SystemService systemService) {
    return IntegrationFlows.from("findUserEnricherChannel")
            .enrich(enricherSpec ->
                    enricherSpec.requestSubFlow(flow -> flow.<User>handle((p, h) -> systemService.findUser(p))
                    ).<User>propertyFunction("email", (message) ->
                            (message.getPayload()).getEmail()
                    ).<User>propertyFunction("password", (message) ->
                            (message.getPayload()).getPassword()
                    ))
            .get();
}
gstackoverflow
  • 36,709
  • 117
  • 359
  • 710