Search code examples
spring-integrationenterprise-integration

Spring Integration support for the Normalizer EIP


Spring Integration here. I was expecting to see a normalize(...) method off the IntegrationFlow DSL and was surprised to find there wasn't one (like .route(...) or .aggregate(...), etc.).

In fact, some digging on Google and the Spring Integration docs, and I can't seem to find any built-in support for the Normalizer EIP. So I've taken a crack at my own:

public class Normalizer extends AbstractTransformer {

    private Class<?> targetClass;
    private GenericConverter genericConverter;

    public Normalizer(Class<?> targetClass, GenericConverter genericConverter) {

        Optional<GenericConverter.ConvertiblePair> maybePair = genericConverter.getConvertibleTypes().stream()
            .filter(convertiblePair -> !convertiblePair.getTargetType().equals(targetClass))
            .findAny();
        assert(maybePair.isEmpty());

        this.targetClass = targetClass;
        this.genericConverter = genericConverter;

    }

    @Override
    protected Object doTransform(Message<?> message) {

        Object inbound = message.getPayload();
        return genericConverter.convert(inbound, TypeDescriptor.forObject(inbound), TypeDescriptor.valueOf(targetClass));

    }

}

The idea is that Spring already provides the GenericConverter SPI for converting multiple source types to 1+ target type instance. We just need a specialized flavor of that that has the same target type for all convertible pairings. So here we extend AbstractTransformer and pass it one of these GenericConverters to use. During initialization we just verify that all the possible convertible pairs convert to the same targetClass specified for the Normalizer.

The idea is I could instantiate it like so:

@Bean
public Normalizer<Fizz> fizzNormalizer(GenericConverter fizzConverter) {
    return new Normalizer(Fizz.class, fizzConverter);
}

And then put it in a flow:

IntegrationFlow someFlow = IntegrationFlows.from(someChannel())
    .transform(fizzNormalizer())
    // other components
    .get();

While I believe this will work, before I start using it too heavily I want to make sure I'm not overlooking anything in the Spring Integration framework that will accomplish/satisfy the Normalizer EIP for me. No point in trying to reinvent the wheel and all that jazz. Thanks for any insight.


Solution

  • If you take a closer look into that EI pattern, then you see:

    Use a Normalizer to route each message type through a custom Message Translator so that the resulting messages match a common format.

    The crucial part of this pattern that it is a composed one with a router as input endpoint and a set of transformers for each inbound message type.

    Since it is that kind of component which is data model dependent and more over the routing and transforming logic might differ from use-case to use-case, it is really hard to make an out-of-the-box single configurable component.

    Therefore you need to investigate what type of routing you need to do to chose a proper one for input: https://docs.spring.io/spring-integration/docs/current/reference/html/message-routing.html#router

    Then for every routed type you nee to implement respective transformer to produce a canonical data mode.

    All of the can be just wrapped into a @MessagegingGateway API to hide the normalize behind so-called pattern implementation.

    That's what I would do to follow that EI pattern recommendations.

    However if your use-case is so simple as just convert from one type to another, so yes, then you can rely on the ConversionService. You register your custom Converter: https://docs.spring.io/spring-integration/docs/current/reference/html/endpoint.html#payload-type-conversion. And then just use a .convert(Class) API from IntegrationFlowDefinition.

    But again: since there is no easy way to cover all the possible domain use-cases, we cannot provide an out-of-the-box Normalizer implementation.