9 Replies Latest reply on Jun 27, 2012 8:16 PM by Erhard Siegl

    Transformer or Adapter

    Erhard Siegl Novice

      I was thinking about the recent transformer questions from Eduardo in https://community.jboss.org/thread/201487?tstart=0 and https://community.jboss.org/thread/201028?tstart=0 and whether the Transformer (as I understand it) is the right concept for some transformation problems. Maybe we also need an adapter between interfaces. I will explain this in an example.

       

      I have an Ordering service implemented by OrderingBean and a Deliver service implementet by DeliverBean. The DeliverBean calls the Ordering service (has Ordering as reference). The Ordering Interface looks like that:

       

      {code}

      public interface Ordering {

       

                public void process(Order order);

                public void check(Order order);

      }

      {code}

       

      The DeliverBean:

       

       

      {code}

      @Service(value = Deliver.class, name = "Deliver")

      public class DeliverBean implements Deliver {

       

             @Inject @Reference

                Ordering ordering;

       

             @Override

                public void process(Order order) {

                ...

                ordering.process(order);

                }

      }

      {code}

       

      Everything is fine. But now I get new requirements and the Ordering interface changes a little. Also the Order itself changes a little and gets a new version:

       

       

      {code}

      public interface Ordering2 {

       

                public void process(Order2 order);

                public void check(Order2 order);

                public void cancel(Order2 order);

      }

      {code}

       

      Well, thats a common problem with common solutions

      1. Change all consumers of Ordering to use the new interface
      2. Make a copy from the OrderingBean and keep two implementations
      3. Write an adapter

       

      Often you would go for the adapter. A switchyard Transformer doesn't help here, since first it would only change the Order to Order2 and with the use of different interfaces I have to match these somehow. So I have the situation that the Reference in DeliverBean still uses the Ordering interface but the OrderingBean uses the Ordering2 interface. Similar to the Transformer an adapter could look like this:

       

      {code}

      public interface Adapter<F, T> {

           F adapt(final T to);

      }

      {code}

       

      The Adapter implementation from Ordering to Ordering2 could look like this:

       

       

      {code}

      public class OrderAdapter implements Adapter<Ordering, Ordering2> {

       

           @Override

           public Ordering adapt(final Ordering2 to) {

                return new Ordering() {

       

                    @Override

                    public void process(Order order) {

                          Order2 o2 = new Order2(order);

                          to.process(o2);

                          order.setOrdered(o2.isOrdered());

                    }

       

                    @Override

                    public void check(Order order) {

                          to.check(new Order2(order));

                    }

                };

           }

      }

      {code}

       

      With this Switchyard could inject

      Ordering ordering = new OrderAdapter().adapt(ordering2);

      to the DeliverBean while using the OrderingBean with the Ordering2 interface.

      One could use a Transormer in the Adapter to transform the order. As I understand it, the use of the Transformer in Switchyard it is a special case of such adapter where the methods of the interface are the same and only the argument-type changes.

       

      I was also wondering why Camel works well with transformers only. I think the rason is that Camel basicly uses only one Processor interface:

      process(Exchange ex):

      The Exchange is passed from one service to another and a transformer just changes the Bodies of the Exchange-Messages.

       

      I think as soon as different interfaces are used in the services, adapters between the interfaces are necessary.

      Any thoughts on this?

        • 1. Re: Transformer or Adapter
          Eduardo de Vera Toquero Newbie

          Hi Erhard,

           

          I like the idea of having my routing logic in Camel as a way to loosely couple services and having transformers do the work of transforming the payload from one service to the next one instead of using annotated references, even if I think that having the possibility of using both approaches is always great and dependending on the situation one solution might be better than the other.

           

          Nevertheless, I am inclined to think that what you are defining as an adapter should actually be a transformer. I still haven't heard back from the Switchyard team regarding the very simple example I have sent here.

           

          I do think that service versioning is something that needs to be discussed and documented better. From my understanding a different version of a service makes the service different, so that each version should have its own endpoint and that would actually allow having different upgrade times for the different customers. Or having the previous version of the service act as a proxy to the newer version, being responsible to adapt the changes so that consumers need not to be impacted.

           

          I also like the idea of having a Normalized Message Router where all messages coming and going are in canonical form (after having applied the VET stages of the VETO pattern) with their version as part of their qualified name, so that one can have different versions of the objects working with different versions of the services. Switchyard is flexible enough to allow such approach, but at the same time that flexibility allows lousy designs to leak in.

          • 2. Re: Transformer or Adapter
            Erhard Siegl Novice

            Hi Eduardo

            I like the idea of having my routing logic in Camel as a way to loosely couple services and having transformers do the work of transforming the payload from one service to the next one instead of using annotated references, even if I think that having the possibility of using both approaches is always great and dependending on the situation one solution might be better than the other.

            The question is not whether you use injection or not, the problem comes that Switchyard uses different interfaces for different services and Camel uses only one interface for all services. So Camel has to cope only with messages (data), but Switchyard has to handle methods (interfaces) as well.

            Nevertheless, I am inclined to think that what you are defining as an adapter should actually be a transformer.

            When you mean that adapters and transformers should be handled the same way, I agree. When you mean, they are (should be) actually the same thing, I disagree.

            A Transformer (in Switchyard) transforms messages from one format to another: "Transformation represents a change to the format and/or representation of a message's content."

            I interprete this so that there should be an isomorphism from one representation to the other, meaning essentially the messages are the same, only the format differs. Because of this isomorphism between the argument-types, the interfaces

             

            {code}

            package org.switchyard.example;

             

            public interface OrderService

                 void submitOrder(Order order);

            }

            {code}

            and

             

            {code}

            package org.switchyard.example;

             

            @OperationTypes(in = "{urn:switchyard-quickstart:bean-service:1.0}submitOrder")

            public interface OrderService {

                void submitOrder(String orderXML);

            }

            {code}

             

            are essentially the same. So with Transformers you essentially work with the same interfaces. (Of course you can write Transformers that don't define an isomorphism between types, but I don't think thats intended and you probably run into problems)

             

            An Adapter as I tried to describe it handles different interfaces. It maps an interface to another and not messages. You probaby don't have an isomorphism but only an injective mapping, so I think its wise to define only one direction. So Adapters (as described above) and Transformers (as I understand them in Switchyard) might be handled in a similar fashion but they are conceptually different.

             

            That might be (wild guess begin ) the reason why there could be problems with Java - Java Transformers. You have the interfaces

            public void operation(A parameter);

            and

            public void operation(B parameter);

            Since both are "real" Java-types, its likely that its not only the structure that differs and that you don't have an isomorphism between the types A and B. So its probaby not what the Switchyard team intended with transformers. Hence there are no such examples. (wild guess end ).

            • 3. Re: Transformer or Adapter
              Keith Babo Master

              A transformer in SwitchYard is an adapter as you've described above.  There's no reason why you can't use a transformer to map between Java types.  Check out the revised app that I attached to the other thread from Eduardo and let me know if something is missing.  Happy to explain more by example and certainly if a hole is found we will fill it.

              • 4. Re: Transformer or Adapter
                Keith Babo Master

                Erhard Siegl wrote:

                 

                The question is not whether you use injection or not, the problem comes that Switchyard uses different interfaces for different services and Camel uses only one interface for all services. So Camel has to cope only with messages (data), but Switchyard has to handle methods (interfaces) as well.

                 

                Let's be clear on something here.  This is not a problem that SwitchYard creates.  This is simply a fact of integration and SOA apps that is highlighted in SwitchYard because we require contracts for all services and references.  Once you go down that path, you realize that the implementations or bindings of various services are indeed using different representations of content and those need to be reconciled. Traditionally, we hack this into a service consumer or provider implementation or use a mediator to separate it out.  SwitchYard provides an additional option in a runtime interceptor that can map these types via a transformer.  Of course, you can continue to use the traditional methods of transformation in SwitchYard if that works for you.  View this as merely another option that's available to you.

                 

                 

                When you mean that adapters and transformers should be handled the same way, I agree. When you mean, they are (should be) actually the same thing, I disagree.

                A Transformer (in Switchyard) transforms messages from one format to another: "Transformation represents a change to the format and/or representation of a message's content."

                I interprete this so that there should be an isomorphism from one representation to the other, meaning essentially the messages are the same, only the format differs. Because of this isomorphism between the argument-types, the interfaces

                 

                
                package org.switchyard.example;
                
                public interface OrderService 
                     void submitOrder(Order order);
                }
                

                 

                and

                 

                
                package org.switchyard.example;
                
                @OperationTypes(in = "{urn:switchyard-quickstart:bean-service:1.0}submitOrder")
                public interface OrderService {
                    void submitOrder(String orderXML);
                }
                

                 

                 

                are essentially the same. So with Transformers you essentially work with the same interfaces. (Of course you can write Transformers that don't define an isomorphism between types, but I don't think thats intended and you probably run into problems)

                 

                I think you make a very good point here w/r/t type equivalence, but I feel that it's within range for transformers to map between types that are not equivalent.  This is particularly true in cases like:

                 

                - mapping between an edge format and a canonical representation

                - adjusting for version differences

                 

                Those are in bounds for declarative transformation.  Putting actual business logic in a transformer is running up against the edge and really going over the line, IMO. 

                • 5. Re: Transformer or Adapter
                  Erhard Siegl Novice

                  Thanx Keith for your explanations and code. I attached an example to show what I want to achieve. If that can be done with Transformers, it would be great, but I failed. Actually I ran into two problems.

                  The tests should work with a "mvn package".

                   

                  The first problem is, that with

                  cp switchyard6.xml  ./src/main/resources/META-INF/switchyard.xml

                  mvn package

                   

                  I get:

                  "Operation 'orderIt' is not defined on Service 'Ordering'."

                   

                  This is wrong since Service Ordering implements Ordering2 which has orderIt(). Furthermore with switchyard6.xml I only add one more component, so there should be no reason for the application to behave differently.

                   

                  The second problem comes to the core of the problem. In the working example I have in DeliverBean.java:

                   

                  {code}

                  @Service(value = Deliver.class, name = "Deliver")

                  public class DeliverBean implements Deliver {

                   

                      @Inject @Reference(value="Ordering")

                      Ordering2 ordering2;

                  //  Ordering ordering;

                   

                      @Override

                      public void process(Order order) {

                          System.out.println("Start "+this.getClass().getName());

                          Ordering ordering = new OrderAdapter().adapt(ordering2);

                   

                          order.setCount(0);

                          ordering.process(order);

                      }

                  }

                   

                  {code}

                   

                   

                  Of course I don't want the transformation in the Bean but this should be done with a Transformer or something alike. The code should look like that:

                   

                  {code}

                  @Service(value = Deliver.class, name = "Deliver")

                  public class DeliverBean implements Deliver {

                   

                      @Inject @Reference(value="Ordering")

                      Ordering ordering;

                  //  Ordering2 ordering2;

                   

                      @Override

                      public void process(Order order) {

                          System.out.println("Start "+this.getClass().getName());

                  //      Ordering ordering = new OrderAdapter().adapt(ordering2);

                   

                          order.setCount(0);

                          ordering.process(order);

                      }

                  }

                  {code}

                   

                  But then I get

                  Caused by: org.switchyard.exception.SwitchYardException: Operation process is not included in interface for service: {urn:at.objectbay.tests:switchyard-example1:0.0.1-SNAPSHOT}Ordering

                  which is true, since the Ordering Service implements Ordering2, where I changed the process() method to orderIt().

                   

                  The Transformer doesnt kick in:

                  {code}

                  public class OrderAdapter implements Adapter<Ordering, Ordering2> {

                   

                      @Override

                      @Transformer

                      public Ordering adapt(final Ordering2 to) {

                          System.out.println("Start "+this.getClass().getName());

                          return new Ordering() {

                        ...

                          };

                      }

                  }

                  {code}

                   

                  Apearently this was not the way do this.

                  • 6. Re: Transformer or Adapter
                    Keith Babo Master

                    OK, I see what you're getting at now.  I misinterpreted your original post as a request to simply map between Java types.  After reviewing what you posted above, I see that I was mistaken and the purpose of the adapter would be to handle contract transformation vs. data transformation.  I agree that having a way to insulate consumers from provider contract changes would be a nice feature. This could get messy if there's too much of it, but I can see how it could be a useful wedge for change management until you could get around to changing all consumers impacted by a change.  An additional thing to consider is that this is a problem that extends beyond Java interfaces.  Of the contract types we support now, WSDL would be another contract that could benefit from this type of thing.

                     

                    The real question for me is what kind of expectation users have in terms of changing a contract so that it's completely incompatible with existing consumers.  Some might say that a version of that service should continue to live on until the consumers of the prior contract can be migrated.  If it's a common enough case where people expect adapters as an insulating mechanism to incompatible contract change, then we will need something like adapters to address that.  Might be that we could simply provide an extension point to our existing addressing logic so that user code can influence the decision on whether a contract is compatible and which types are used for "to" and "from".  This extension plus our existing transformer support would likely fit the bill.

                    • 7. Re: Transformer or Adapter
                      Erhard Siegl Novice

                      As you said before, these things are not Switchyard-specific problems but general SOA/integration problems. Unfortuanately it seems, that there is no general clearly defined language to adress the topics. But I think we are on the way to find our common language . So I start with a few (informal) definitions/conventions that I try to follow in my postings to facilitate that process.

                      • Component: Piece of software that provides Services. Often this is referred as service, but here its the implementation of a service
                      • Service: A set of functions that are provided by a component.
                      • Service-Interface: Actual description how you can access the service-functions in software (WSDL-Interface, Java-Interface,...)
                      • Refernce: A set of functions that are needed by the Component from somewhere else.
                      • Reference-Interface: Actual description how the reference-functions are used by the component (WSDL-Interface, Java-Interface,...)

                       

                      The concept of separate Reference-Interfaces is great. I haven't seen this before, but I always thougt something like that is important.

                      So, when a component A calls another component B (A->B), the reference of component A (Reference-A) has to be linked to the service of component B(Service-B). This is easy when Reference-Interface-A equals Service-Interface-B, which is the only way I have seen until Switchyard (and I'm getting exited). In Switchyard it is possible that Reference-Interface-A differs Service-Interface-B:

                       

                       

                      {code:xml}

                          <sca:component name="MyServiceBuilder">

                            <camel:implementation.camel>

                              <camel:java class="org.example.syApp2.MyServiceBuilder"/>

                            </camel:implementation.camel>

                            <sca:service name="MyService">

                              <sca:interface.java interface="org.example.syApp2.MyService"/>

                            </sca:service>

                            <sca:reference name="OrderService">

                              <interface.esb inputType="java:org.example.syApp2.Bean1"/>

                            </sca:reference>

                          </sca:component>

                          <sca:component name="OrderServiceBean">

                            <bean:implementation.bean class="org.example.syApp2.OrderServiceBean"/>

                            <sca:service name="OrderService">

                              <sca:interface.java interface="org.example.syApp2.OrderService"/>

                            </sca:service>

                          </sca:component>

                      {code:xml}

                       

                      Here the Reference-Interface for OrderService  "esb" and the Service-Interface is "java". in order to link the components you need an adapter.

                      • adapter: Concept of transforming one Interface to another Interface. (Maybe one should say to link a Reference-Interface to a Service-Interface.) Also referred as contract-transformation above.
                      • Adapter: Concrete implementation for an adapter in Switchyard. Like org.switchyard.transform.Transformer and suggested above or other internal adapters.
                      • transformer: Concept of transforming messages (arguments of service-functions) from one type to another.
                      • Transformer: Switchyard implementation of a transformer

                       

                      Switchyard of course has adapters, since it uses different interfaces for References an Services. And after all this formalism, I can ask my questions or rephrase the questions above:

                       

                      1. When is it theoretically possible and usefull to construct an adapter?
                      2. How does Switchyard construct its adapters?
                      3. When its theoretically possible to construct an adapter, but Switchyard can't right now, how could Switchyard support it in future?

                       

                      I try to give some some answers (corrections welcome).

                       

                       

                      To Question 1 (Component-A calls Component-B):

                       

                      Identity: The easiest case is when Refence-Interface-A equals Service-Interface-B. The Identity-Adapter. Trivial.

                       

                      Embedding: When we have Java-Interfaces and Interface-B extends Interface-A like:

                      Reference-Interface-B:

                           void process(Order o);

                      Service-Interface-A:

                           void process(Order o);

                           void check(Order o);

                      We have a embedding of the Reference-Interface to Service-Interface. Methods and arguments are mapped by the Identity-Function.

                       

                      1-1-Method: When both interfaces have only one method and the same arguments.

                      Reference-Interface-B:

                           void process(Order o);

                      Service-Interface-A:

                           void orderIt(Order o);

                      Methods are mapped in the only possible way, arguments are mapped by Identity. This reminds on the documenttion for Camel-Endpoints:

                      "operation-name : name of the service operation to be invoked.  This is only used on references and is optional if the target service only has a single operation."

                      Camel Reference-Interfaces are created as Embedding to Service-Interfaces when you give the operation-name and can use a 1-1-Method adapter otherwise.

                       

                      Transformer-based: When the method-names form an embedding and there is a Transformation that mapps the arguments

                      Reference-Interface-B:

                           void process(Order o);

                      Service-Interface-A:

                           void process(Order2 o);

                           void check(Order2 o);

                      Transformer

                           Order2 orderToOrder2(Order o)

                      When process(Order o) is used by component A, the Adapter calls process(orderToOrder2(o)) on component B. We have the Identity on Methods and need an injective transformation from Order to Order2. If there are return-values, one needs a projection on the returning class (this should be investigated in more detail).

                       

                      General Embedding: From the examples above, I think one can that you need the following for an adapter:

                      • An (injective?) map from the Reference-methods to the Service-methods
                      • An (injective?) map from the arguments of the Reference-methods to the arguments of the Service-methods
                      • A (projection?) map for the return-values.

                       

                      As usecase I have the replacement of one Service by a newer Version of the same Service in mind. The adapter has to make sure that the functionallity stays the same for the old interface. This might not be the only usecase and assertions for the mapping might change.

                       

                      I tried to adress this general form in my example.

                      Reference-Interface Ordering:

                           void process(Order o);

                           void check(Order2 o);

                      Service-Interface Ordering2:

                           void orderIt(Order2 o);

                           void check(Order2 o);

                           void cancel(Order2 o);

                      where "orderIt" is just a new name for the "process". A possible Adapter-function is "Ordering adapt(final Ordering2 to){...}" in OrderAdapter.java

                       

                       

                      To Question 2


                      As far as I have seen Switchyard creates adapters by Embedding of method names together with Transformer-based adapters. The Switchyard-Dokumentation is a little unclear so maybe we can look at a Camel example from the dokumentation?

                       

                      {code:xml}

                      <sca:component name="CamelComponent">

                         <implementation.camel xmlns="urn:switchyard-component-camel:config:1.0">

                             <route xmlns="http://camel.apache.org/schema/spring" id="Camel Test Route">

                                 <log message="ItemId [${body}]"/>

                                 <to uri="switchyard://WarehouseService?operationName=hasItem"/>

                                 <log message="Title Name [${body}]"/>

                             </route>

                          </implementation.camel>

                          <sca:service name="OrderService">

                              <sca:interface.java interface="org.switchyard.component.camel.deploy.support.OrderService"/>

                          </sca:service>

                          <sca:reference name="WarehouseService">

                              <sca:interface.java interface="org.switchyard.component.camel.deploy.support.WarehouseService"/>

                          </sca:reference>

                      </sca:component>

                      {code:xml}

                       

                      We have a Reference-Interface WarehouseService which is adressed from switchyard://WarehouseService?operationName=hasItem. In the documentation we have:

                      switchyard://[service-name]?operationName=[operation-name]

                      service-name : name of the SwitchYard service

                      operation-name : name of the service operation to be invoked.  This is only used on references and is optional if the target service only has a single operation.

                       

                      Am I right to assume that with my definitions/convention in case of a reference this should be:

                      service-name : name of the SwitchYard Reference

                      operation-name : name of the operation in the Reference-Interface to be invoked.  This is only used on references and is optional if the target Reference only has a single operation.

                       

                      So in the fist step the camel-endpoint is mapped to the Reference-Interface and then the Reference-Interface is mapped to the Service-Interface?

                       

                       

                      To Question 3


                      The General Embedding can't be done right now with switchyard. I thought maybe one could register Adapters like Transformers. Something like that:

                      {code:xml}

                      <adapters>

                         <adapter.java class="at.objectbay.tests.swe.OrderAdapter"

                                         from="interface.java:at.objectbay.tests.swe.Ordering"

                                         to="interface.java:at.objectbay.tests.swe.Ordering2"/>

                      </adapters>

                      {code:xml}

                       

                      If Switchyard has to map a Reference-Interface to a Service-Interface it just uses the registered Adapter. If none is found, it tries to build an Adapter like it does now.

                      (And now its time to eat something )

                      • 8. Re: Transformer or Adapter
                        Keith Babo Master

                        Erhard,

                         

                        I apologize for not replying earlier to this thread.  I think you're onto something here and I want to dissect it more, but I likely won't have time this week due to JUDCon and JBoss World.  I promise to respond in detail next week and perhaps we can put this into action in SwitchYard 0.6!

                         

                        cheers,

                        keith

                        • 9. Re: Transformer or Adapter
                          Erhard Siegl Novice

                          Hi Keith,

                          It's my fault. I should have come to Boston.

                          Erhard