Announcement Announcement Module
Collapse
No announcement yet.
Template Method Pattern using Spring Integration? Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Template Method Pattern using Spring Integration?

    Hi,

    I have a use case that is good fit for the Template Method design pattern (http://en.wikipedia.org/wiki/Template_method_pattern) and I wanted to find out whether it is possible to implement something similar to this pattern in Spring Integration.

    Basically, my use case is as follows: I have a common set of processing steps that I need to perform, e.g.

    (1) Receive message
    (2) Determine message type
    (3) Unmarshall message
    (4) Transform message
    (5) Enrich message headers
    (6) Validate message
    (7) Persist message to Database
    (8) Persist message IDs for lookup/cross-referencing
    (9) Prioritize message
    (10) Forward message to publication broker

    The first version of our system only deals with a single message type, and it was straightforward to create a pipeline of channels and message endpoints that represented the different processing stages shown above.

    Now, however, we want to add various new message types. The processing steps for these other message types are basically the same, but the actual implementation of particular steps may be different depending on the message type.

    The simplest approach is to add a router that branches the flow based on message type (i.e. after step 2), and then effectively clone the remainder of the flow for each message type, but this means that the common processing logic (i.e. which steps to perform and in what order) is duplicated for each message type. We also have certain steps in the flow where the processing is not message-type specific, but if we converge back to a common node to perform this step, then we need another router node after it to branch the flow again for subsequent steps. This approach may be fine for 2 or 3 message types, but quickly becomes unwieldy beyond that.

    One alternative is to not use Spring Integration for this part of the system - i.e. write a class with a method defining the main processing steps, then using dependency injection and/or method overriding, we customise the behaviour as required for each message type, but we do want to retain the loose coupling and flexibility that we get from using Spring Integration.

    I have also thought about using a combination of placeholder properties and splitting up the config into separate files. I could then set the classpath to include the correct set of config files and properties files for the required message type. This would work if we had a separate JVM for processing message type, but I don't think we could use this approach if we wanted a single JVM that handled all the various message types.

    Am I missing an obvious solution here? Is there a feature either in standard Spring configuration or specific to Spring Integration that I could use to implement this use case?

    Any help/advice would be much appreciated.

    Best Regards
    -Matt

  • #2
    Matt

    Just a thought, but what if you were to combine Template pattern with Delegate?
    For example. Lets say you have 2 types of Messages that has to go through the same set of endpoints but as you said the implementation of these endpoints could be different based on the type of the message. So from Integration concern the flows are identical, but from business the processing steps are different.

    Code:
    Message type 1
    channel -> service-activator -> cannel -> service-activator
                       ref="beanA"                         ref="beanB"
    
    Message type 2
    channel -> service-activator -> cannel -> service-activator
                       ref="beanA1"                       ref="beanB1"
    Instead of wiring it as shown above you could easily create a delegateA and delegateB and inject them with all the possible combination of the actual processors (beanA, beanA1 etc.).
    This way your flows don't have to be duplicated.
    I understand that the immediate argument is that now the delegate is performing some form of the routing, but i would argue that this is not an integration routing but rather delegation to a specific business logic thus out of scope of integration concern.
    What do you think

    Comment


    • #3
      Hi Oleg,

      Thanks for your reply. I'm not sure I understand what you mean though. I can see how you might create different delegates, something like this:

      Code:
      <bean id="messageAlphaProcessor" class="com.blah.stuff.MessageAlphaProcessor">
        <property id="unmarshaller" ref="alphaUnmarshaller"/>
        <property id="transformer" ref="alphaTransformer"/>
        <property id="enricher" ref="alphaEnricher"/>
        <property id="validator" ref="alphaValidator"/>
        ...
      </bean>
      
      <bean id="messageBetaProcessor" class="com.blah.stuff.MessageBetaProcessor">
        <property id="unmarshaller" ref="betaUnmarshaller"/>
        <property id="transformer" ref="betaTransformer"/>
        <property id="enricher" ref="betaEnricher"/>
        <property id="validator" ref="betaValidator"/>
        ...
      </bean>
      So, using the above, the endpoints could refer to the delegate instead of the individual beans, but wouldn't you still need to have multiple copies of the 'flow' - one for each message type? i.e. you would end up with this:

      Message type 1
      Code:
      channel -> service-activator -> cannel -> service-activator
                         ref="messageAlphaProcessor"                         ref="messageAlphaProcessor"
      Message type 2
      Code:
      channel -> service-activator -> cannel -> service-activator
                         ref="messageBetaProcessor"                       ref="messageBetaProcessor"
      Perhaps the appropriate delegate for the current message type could be set as an attribute on the message itself, at the point when the message type is determined? Is that what you were thinking of?

      Sorry if I'm missing the wood for the trees here, but if you can provide more detail on your proposed approach, I would be grateful.

      Many thanks
      -Matt

      Comment


      • #4
        Hi Oleg,

        Would you mind providing some extra detail about your proposed approach?

        Thanks
        -Matt

        Comment


        • #5
          Matt sorry, we were actually discussing it the other day and I'll take my approach back and propose a different one.
          So you have flows where some endpoints are the same and some are different and you want to be able to reuse as much of what is reusable and don't get in the business of duplicating flows. I believe that is your use case. Right?
          If so here is what you can do:
          Code:
                      
                         /- channel - SA \
          channel - PTR  - channel - SA - channel - [reusable-endpoint] - channel . . .
                         \- channel - SA /
          As you can see from above I am not really duplicating anything other then recognizing the processing segments of my flow that are different vs the ones that are the same.
          So in the above i may have 3 different message types (let's say different based on the payload), and I know that i need 3 different beans(services) to process these different payloads, so i have 3 service-activators. I also add payload-type-router (PTR) to route to these services. But the trick is that after that segment the next processing step is the same so all I do is define the same output-channel to all 3 service-activators and they eventually end up in the reusable-endpoint (whatever that might be. . . another SA?) and so on. Does that answer your question?

          Comment


          • #6
            Hi Oleg,

            Thanks for your reply. The use of the payload-type-router is a good approach. At the moment, I set a property in the header indicating the message type, then use a header value router to perform the actual routing -- the payload-type-router achieves this in a single step. Other than that, however, your approach isn't actually any different to what we currently have.

            The problem is that we have several steps in a row where the processing is different depending on the message type, so extending your example so it looks more like our actual use case, we would have:

            Code:
                          /- unmarshal_A - unm_A_SA - transform_A - tfm_A_SA - enrich_A - enr_A_SA \
            channel - PTR -  unmarshal_B - unm_B_SA - transform_B - tfm_B_SA - enrich_B - enr_B_SA - channel - validate - [reusable-endpoint] - channel...
                          \- unmarshal_C - unm_C_SA - transform_C - tfm_C_SA - enrich_C - enr_C_SA /
            Here, the required processing steps are as follows:
            1. unmarshall (message type dependent)
            2. transform (message type dependent)
            3. enrich (message type dependent)
            4. validate (common)

            But using the proposed approach, the logic that says unmarshalling is followed by transformation which is then followed by enrichment is repeated multiple times - one per message type.

            I'm also considering another approach which was suggested by one of my colleagues, which is to leverage the ability of Spring Integration to automatically delegate to a method based on the payload type, e.g.

            Code:
            <channel id="transformChannel"/>
            
            <transformer input-channel="transformChannel" output-channel="enrichChannel">
              <bean class="com.stuff.blah.MessageTransformer"/>
            </transformer>
            
            <channel id="enrichChannel"/>
            ...
            And then the MessageTransformer class would look like this:

            Code:
            public class MessageTransformer {
              @Transformer
              public Train transform(Train train) {
                ...
              }
            
              @Transformer
              public Plane transform(Plane plane) {
                ...
              }
            
              @Transformer
              public Automobile transform(Automobile automobile) {
                ...
              }
            }
            This achieves the objective in terms of the Spring config (i.e. generic with no duplication of processing logic), but I don't like the way that you end up with classes that deal with different message types, i.e. ideally I want to have a completely different class (or set of classes) for dealing with Planes, Trains and Automobiles (sorry!). We can structure it so that the MessageTransformer class (for example) doesn't contain any real business logic, but instead delegates to the appropriate AutomobileTransformer, PlaneTransformer etc., but it still means you would have to maintain a set of classes which exist purely to delegate to the appropriate classes that contain the business logic, and these core delegation classes would need to be updated each time a new message type is added.

            Ideally, I'd like to be able to support a new message type just by updating the Spring config and adding new classes with specific processing for the new message type, i.e. change config and add new code, but not change existing code.

            Perhaps my purist tendencies are getting the better of me, but do you see what I'm getting at?

            I'm going to start putting together a prototype using the approach described above, but any further thoughts/approaches are most welcome.

            Thanks
            -Matt

            Comment


            • #7
              My colleague has suggested the following change to address my concerns with the earlier approach. Basically, we could look up the relevant bean by name in the Spring application context. So, instead of having a different method per message type, we would have one method that takes Object as its parameter, like so:

              Code:
              @Transformer
              Object transform(Object msgPayload) {
              	String msgType = msgPayload.getClass().getSimpleName();
              
              	Transformer transformer = (Transformer) ctx.getBean(msgType + "Transformer");
              	response = transformer.transform(msgPayload);
              	return response;
              }
              Thoughts?

              Thanks
              -Matt

              Comment


              • #8
                Matt

                Ironically your last solution was essentially what I was proposing initially. The Transformer code that you provided is really a delegating transformer, which performs lookup of concrete transformers in the bean factory. As minor improvement, if you know all your transformers ahead of time, I would maintain an internal map of transformer references to save some time on bean factory lookup.

                Also I am not sure if you are aware, but we currently support SpEL allowing you to select which bean is going to be invoked for a particular endpoint.
                For example:
                Code:
                <transformer expression="@myTransformer.transform(payload)"/>
                
                <bean id="myTransformer" .../>
                As you can see from the above instead of configuring bean via 'ref' attribute, one can simply use '@' and point to a particular bean by name.
                Now I realize that this might not help you a lot. What would help you is if bean name could be computed dynamically and then looked up:
                Code:
                @{headers['beanName']}.transform(payload)
                The above would accomplish exactly what you are proposing with the last solution, but you won't need to implement your own delegate and lookup since it will be taken care of by SpEL. However the above is also not yet supported but is being discussed and could become available in the future SpEL.

                Overall I think your last solution is good for the time being.

                Another suggestion to address the dynamics you are looking for is to use Groovy support that is available with SI 2.0.

                Comment


                • #9
                  Oleg - thanks for your reply. It sounds like we're on the right track now, albeit via a rather circuitous route! I had read a bit about SpEL in the SI 2.0 reference guide -- it looks quite powerful, so we will be looking to start using it in our application. The dynamic computation of the bean name per your example would be a useful addition too. I've not used Groovy before but I will read up on this as well.

                  Thanks for your help

                  -Matt

                  Comment


                  • #10
                    No problem. Your use case is very interesting and sparked a lot of debate on our end so keep us posted on your progress

                    Comment


                    • #11
                      Nice forum. I am doing the same and as Matt pointed out the number of transformers explodes. And, I think Marshallers/Unmarshellars beans explodes equally if you are using JibX:OXM with increase in number of types.

                      I am writing transformers but somehow I am not keen going getBean route in the code, may be using ObjectFactoryCreatingFactoryBean to get bean lookup with messageType is nice? Still looking for some suitable sloution. SpEL is good.

                      For me to get the right transformer is dependent on messageType plus few other things!

                      Once again intersting post and I am going to follow up this one!
                      Last edited by rock_star; Jan 13th, 2011, 12:55 PM.

                      Comment


                      • #12
                        Hello Matt,

                        Just to add to my post above, I was curious to know if you achieved something similar for unmarshallers as you achieved for transformers.

                        For me, I am ending up with so many marshalling-unmarshalling channels/routes/flows.

                        For example
                        Code:
                        <!-- Marshalling/Unmarshaling for messageType-A
                        <si-xml:marshalling-transformer id="responseMarshaller"
                        		input-channel="inputChannel-1" output-channel="outputChannel-1"
                        		marshaller="classAMarshaller" result-transformer="resultTransformer" />
                        	
                        <si-xml:unmarshalling-transformer id="responseUnMarshaller"
                        		input-channel="inputChannel-2" output-channel="outputChannel-2"
                        		marshaller="classAMarshaller" result-transformer="resultTransformer" />
                        		
                        <oxm:jibx-marshaller id="classAMarshaller"
                        		target-class="com.abc.ClassAMarshaller"  />
                        If we look at the config above then we ended up with 4 channels for a given message type A.

                        Concerns:
                        • Now, suppose if we have 10 messages types( we have 20 atleast in system ) then as per above configuration, we will end up with 40( 80 ) channels and 30(60) beans as three beans( <si-xml:marshalling-transformer) for a given message above.
                        • Also, if the same message type flows in different kind of flows then, I will have to create extra 4 channels for the same message type. Lets say

                          Queue1 -->Adaptor 1---> responseMarshaller--> processing --> Unmarshalling --> do something----> endPoint1
                          Queue2 -->Adaptor 1---> responseMarshaller--> processing --> Unmarshalling --> do something----> endPoint2

                          I doubt in this scenario we ca share the channels above( inputChannel-1/ outputChannel-1).

                        The above two concerns make my configs complicated.
                        I am wondering if you achieved something similar to Transformer for marshaller/unmarshaller and any input/advice would be great.

                        Thanks

                        Comment

                        Working...
                        X