Quickstart Walkthrough - m1app

 

Overview

The m1app quickstart demonstrates a number of key features available in SwitchYard Milestone 1.  The example used is a trivial order service which accepts an order as input and generates an order acknowledgment as output.  Over the course of the walkthrough, the application containing the service is evolved to consume an inventory service, transform inputs and outputs to/from XML, and finally expose the order service to external clients as a SOAP-based web service.  The walkthrough is staged in a series of steps to demonstrate how SwitchYard provides an iterative development environment for service-oriented applications.  With each step we are developing and testing new functionality in the application, avoiding the pain and suffering that accompanies "big bang" integration testing once the application is done.

 

NOTE: If you plan on creating an application in your IDE to follow these steps, you'll want to use the SwitchYard Application archetype [link] to create a Maven project as a starting point.  For the sake of brevity, this walkthrough does not cover all of the supporting classes and intermediate steps required.  You may want to grab a copy of the finished m1app [link] as a reference to fill in the gaps.

 

Step 1 : Create an Order Service

 

Creating an Order Service with the Bean component is simply a matter of creating a Java interface to define the service contract and another Java class to implement the contract.

 

package org.switchyard.quickstarts.m1app;

public interface OrderService {
    OrderAck submitOrder(Order order);  
}

 

@Service(OrderService.class)
public class OrderServiceBean implements OrderService {

    @Override
    public OrderAck submitOrder(Order order) {
        // Create an order ack
        return new OrderAck()
            .setOrderId(order.getOrderId())
            .setAccepted(true)
            .setStatus("Order Accepted");
    }
}

 

That's all there is to it!  Our service takes an order in and spits an order acknowledgment out.  Building your application at this point will result in a generated SwitchYard configuration showing a single OrderService implemented by your OrderServiceBean.

 

<switchyard xmlns="urn:switchyard-config:switchyard:1.0">
    <composite xmlns="http://docs.oasis-open.org/ns/opencsa/sca/200912" name="m1app">
        <component name="OrderService">
            <service name="OrderService">
                <interface.java interface="org.switchyard.quickstarts.m1app.OrderService"/>
            </service>
            <implementation.bean xmlns="urn:switchyard-component-bean:config:1.0" class="org.switchyard.quickstarts.m1app.OrderServiceBean"/>
        </component>
    </composite>
</switchyard>

 

The generated config is a runtime detail and is only shown to demonstrate that SwitchYard introspects the project workspace and generates configuration so the user doesn't have to.

 

Step 2 : Unit test the Order Service

Our Order Service is pretty dumb, but it still pays to test.  We will now write a test using the unit testing support in SwitchYard.

 

public class OrderServiceTest extends SwitchYardCDITestCase {
    @Test
    public void testOrderAccepted() throws Exception {
        Order testOrder = new Order()
            .setOrderId("ORDER01")
            .setItemId("BUTTER")
            .setQuantity(100);

        OrderAck testAck = newInvoker("OrderService")
            .operation("submitOrder")
            .sendInOut(testOrder)
            .getContent(OrderAck.class);

        Assert.assertTrue(testAck.isAccepted());
    }
}

 

The nice thing about this test is that is abstracts away all the SwitchYard runtime details and focuses on the invocation contract for the service.  Here's what happens behind the scenes that you don't see:

  • Weld SE is initialized and scans the project for CDI Beans
  • A SwitchYard runtime and service domain are created
  • The SwitchYard CDI Extension picks up all @Service beans and registers them in the SwitchYard service domain

 

At this point, everything is setup and all you have to do is test the target service.  You can do that with low-level message exchange APIs in SwitchYard Core, or you can use the handy-dandy Invoker API from our testing package.  The end result is a concise test method that tests our service in the same way it would be invoked in our target deployment environment.

 

 

Step 3 : Add an Inventory Service

One thing we can discover via testing is that our Order Service is a bit lacking in the validation department.  For example, the following orders would all generate an OrderAck with an accepted status:

new Order().setItemId("THERE_IS_NO_WAY_YOU_HAVE_THIS_ITEM_99")
new Order().setQuantity(Long.MAX_VALUE)

 

We clearly need to perform an inventory check.  We could embed the logic directly in our Order Service, or we could do the right thing and create an Inventory Service.

 

public interface InventoryService {
    Item lookupItem(String itemId) throws ItemNotFoundException;
}

 

@Service(InventoryService.class)
public class InventoryServiceBean implements InventoryService {

    @Override
    public Item lookupItem(String itemId) throws ItemNotFoundException {
        Item item = _inventory.get(itemId);
        if (item == null) {
            throw new ItemNotFoundException("We don't got any " + itemId);
        }

        return item;
    }
}

 

That should do the trick.  Remember that for Bean-based services the service contract is defined by a Java interface, in much the same way that a web service contract is defined by a WSDL portType. Whether the interface type is Java or WSDL, SwitchYard converts it into a uniform internal representation which contains the following information:

  • The input message type
  • An optional output message type
  • An optional fault type

 

This information plays an important role in the transformer support in SwitchYard.  More on that in a bit.  First, let's test.

 

Step 4 : Unit test the Inventory Service

Same deal as the OrderService, but we are also going to test the fault path.

 

public class InventoryServiceTest extends SwitchYardCDITestCase {

    @Test
    public void testItemLookupNonexistent() throws Exception {
        final String ITEM_ID = "GUNS";
        try {
            // This should generate a fault because the ITEM_ID is not found
            newInvoker("InventoryService")
                .operation("lookupItem")
                .sendInOut(ITEM_ID)
                .getContent(Item.class);
            // Looks like we didn't fault, so fail
            Assert.fail("Invalid itemId accepted: " + ITEM_ID);
        } catch (InvocationFaultException ifEx) {
            Assert.assertTrue(ifEx.isType(ItemNotFoundException.class));
        } 
    }
}

Note that we are asserting that the fault type matches the type we expected from our service contract.

 

 

 

Step 5 : Update Order Service to consume Inventory Service

Now it's time to wire our Inventory Service into our Order Service.  With the Bean component, this can be done using the standard @Inject annotation along with an @Reference annotation.  While we are injecting a Bean service into another Bean service in this case, it's important to note that the syntax does not change no matter where injected service is implemented.  So InventoryService could actually be a remote service exposed over JMS, a BPEL process, a Camel route, etc.

 

@Service(OrderService.class)
public class OrderServiceBean implements OrderService {

    @Inject @Reference
    private InventoryService _inventory;

    @Override
    public OrderAck submitOrder(Order order) {
        // Create an order ack
        OrderAck orderAck = new OrderAck().setOrderId(order.getOrderId());
        // Check the inventory
        try {
            Item orderItem = _inventory.lookupItem(order.getItemId());
            // Check quantity on hand and generate the ack
            if (orderItem.getQuantity() >= order.getQuantity()) {
                orderAck.setAccepted(true).setStatus("Order Accepted");
            } else {
                orderAck.setAccepted(false).setStatus("Insufficient Quantity");
            }
        } catch (ItemNotFoundException infEx) {
            orderAck.setAccepted(false).setStatus("Item Not Available");
        }
        return orderAck;
    }

}

 

At this point, it's worth taking another peak at the generated SwitchYard configuration to see how the runtime views this application.  Note that each service is discovered and the dependency on InventoryService is also detected.

 

<switchyard xmlns="urn:switchyard-config:switchyard:1.0">
    <composite xmlns="http://docs.oasis-open.org/ns/opencsa/sca/200912" name="m1app">
        <component name="InventoryService">
            <service name="InventoryService">
                <interface.java interface="org.switchyard.quickstarts.m1app.InventoryService"/>
            </service>
            <implementation.bean xmlns="urn:switchyard-component-bean:config:1.0" class="org.switchyard.quickstarts.m1app.InventoryServiceBean"/>
        </component>
        <component name="OrderService">
            <service name="OrderService">
                <interface.java interface="org.switchyard.quickstarts.m1app.OrderService"/>
            </service>
            <implementation.bean xmlns="urn:switchyard-component-bean:config:1.0" class="org.switchyard.quickstarts.m1app.OrderServiceBean"/>
            <reference name="InventoryService">
                <interface.java interface="org.switchyard.quickstarts.m1app.InventoryService"/>
            </reference>
        </component>
    </composite>
</switchyard>

 

Step 6 : Map Order Service to XML

As it turns out, not all consumers of our Order Service will be using the Java type system for input and output messages.  It would be nice if we could expose our Order Service over SOAP, which means we'll be dealing with XML input and output.  We could change our Java service contract to accept XML directly in the form of an DOM Element, but that adds a lot of processing overhead to our service implementation.  Another option is to introduce an orchestration component to execute a transformation and then trigger our service, but writing that extra logic is pain and it's extra stuff to maintain outside of your service implementation.  Or maybe you do none of that.  Let's just define a transformation and let SwitchYard work it's magic.

 

public class TransformOrder_XML_Java extends BaseTransformer<Element, Order> {
    // Message types being transformed
    public static final QName FROM_TYPE = 
        new QName("urn:switchyard-quickstarts:m1app:1.0", "submitOrder");
    public static final QName TO_TYPE = 
        new QName("java:org.switchyard.quickstarts.m1app.Order");

    public TransformOrder_XML_Java() {
        super(FROM_TYPE, TO_TYPE);
    }

    @Override
    public Order transform(Element from) {
       return new Order()
           .setOrderId(getElementValue(from, ORDER_ID))
           .setItemId(getElementValue(from, ITEM_ID))
           .setQuantity(Integer.valueOf(getElementValue(from, QUANTITY)));
    }

    ... snip ...
}

 

The code above defines a transformation from an XML type (which eventually comes from a WSDL) to a Java type.  The Transformer implementation is packaged with the application and automatically discovered and registered with the SwitchYard runtime. During a message exchange, SwitchYard compares the invocation contract of the consumer and provider to determine if transformation is necessary.  It's easy to define and easy to test.

 

public class TypeTransformationTest extends SwitchYardCDITestCase {

    @Test
    public void testTransformXMLtoJava() throws Exception {

        OrderAck orderAck = newInvoker("OrderService")
            .operation("submitOrder")
            .inputType(TransformOrder_XML_Java.FROM_TYPE)
            .sendInOut(loadXML(ORDER_XML).getDocumentElement())  // <-- XML content set here
            .getContent(OrderAck.class);

        Assert.assertTrue(orderAck.isAccepted());

    }

    ... snip ...
}

 

This test invokes our Order Service with an XML payload and verifies that transformation occurred based on the fact that the order was understood and accepted.

 

We also provide a Smooks-based Transformer implementation that eliminates the need to implement the Transformer interface and lets loose the mighty power of Smooks.

 

Step 7 : Add a SOAP Binding for OrderService

At this point, we're ready to expose our Order Service to external consumers through a SOAP endpoint.  As mentioned at the top of the article, this is where typical SOA application testing begins.  Of course, we have tested every step of our application services along the way, so we know that anything that goes wrong is at the SOAP layer.  To enable a SOAP endpoint for a SwitchYard service you simply declare a service binding in the SwitchYard configuration:

 

    <sca:composite name="m1app">
        <sca:service name="OrderService" promote="OrderService">
            <soap:binding.soap>
                <soap:serverPort>18001</soap:serverPort>
                <soap:wsdl>wsdl/OrderService.wsdl</soap:wsdl>
            </soap:binding.soap>
        </sca:service>
    </sca:composite>

 

This is the first configuration that the user has actually had to write in this example.  At some point in the future, this will be masked by tooling and you'll never have to touch the raw XML.  That said, it's interesting to note that user-supplied configuration in META-INF/switchyard.xml is automatically merged with the configuration we discover through introspection and packaged with the end application.

 

That's the end of the m1app quickstart.  We would love to hear your feedback.  Please drop by the forums and let us know what you think.