Search code examples
javaspring-integrationspring-dsl

assigning a different error handler to each handler in a chain


I have an application specifying a simple flow where a chain of services need to be invoked in a particular order to carry out some functionality. Spring integration and its DSL seems appropriate for this scenario.

Any of these individual services is allowed to fail (these services are calls to external applications), at which point the chain should stop. Not only this, the control flow also needs to be redirected somewhere else, depending on the failing service. In other words I need a different error handler for each service.

The problem is I'm having difficulty finding how to do this and I would much appreciate it if someone could point me in the right direction. This is how I've been tackling the problem so far:

@Configuration
@EnableAutoConfiguration
@IntegrationComponentScan
public class Application {

public static void main(String[] args) throws InterruptedException {
    ConfigurableApplicationContext ctx =
            SpringApplication.run(Application.class, args);

    System.out.println(ctx.getBean(MySource.class).send("foo"));

    ctx.close();
}

@MessagingGateway
public interface MySource {

    @Gateway(requestChannel = "flow.input")
    String send(String string);

}

@Bean
public IntegrationFlow flow() {
    return f -> f
            .handle(String.class, (i, map) -> {
 //             if (true){
 //                 throw new RuntimeException();
 //             }
                System.out.println(i);
                return i + "a";
            })
            .handle(String.class, (i, map) -> {
                System.out.println(i);
                return i + "b";
            })
            .handle(String.class, (i, map) -> {
                System.out.println(i);
                return i + "c";
            });
   }
}

I've been searching how to set an error handler/channel for each of those handlers but I didn't find anything useful.

I almost gave up in my search and started to write my own code (sigh), it looks something like this:

public class FlowHarness {

    public static void main(String[] args) {
        Flow flow = FlowBuilder.flow()
            .nextStep(i -> ((Integer)i) + 1, e -> System.out.println("failed at step 1"))
            .nextStep(i -> ((Integer)i) + 1, e -> System.out.println("failed at step 2"))
            .nextStep(i -> ((Integer)i) + 1, e -> System.out.println("failed at step 3"))
            .build();

        flow.execute(new Integer(1));
    }
}

The left hand side argument of .nextStep() is the handler whereas the right hand side argument is the error handler. It is very simplistic but I hope it illustrates the point.

Even though all of the real-life flow could be written in a big class I think it'd be more convenient to break down each of the subflows into separate private methods/classes. Many thanks.


Solution

  • The .handle() (as well as any other consumer endpoint) can be supplied with the ExpressionEvaluatingRequestHandlerAdvice: http://docs.spring.io/spring-integration/docs/4.3.9.RELEASE/reference/html/messaging-endpoints-chapter.html#expression-advice

    @Bean
    public IntegrationFlow advised() {
        return f -> f.handle((GenericHandler<String>) (payload, headers) -> {
            if (payload.equals("good")) {
                return null;
            }
            else {
                throw new RuntimeException("some failure");
            }
        }, c -> c.advice(expressionAdvice()));
    }
    
    @Bean
    public Advice expressionAdvice() {
        ExpressionEvaluatingRequestHandlerAdvice advice = new ExpressionEvaluatingRequestHandlerAdvice();
        advice.setSuccessChannelName("success.input");
        advice.setOnSuccessExpressionString("payload + ' was successful'");
        advice.setFailureChannelName("failure.input");
        advice.setOnFailureExpressionString(
                "payload + ' was bad, with reason: ' + #exception.cause.message");
        advice.setTrapException(true);
        return advice;
    }
    
    @Bean
    public IntegrationFlow success() {
        return f -> f.handle(System.out::println);
    }
    
    @Bean
    public IntegrationFlow failure() {
        return f -> f.handle(System.out::println);
    }