WS-Duck Typing
Arjen Poutsma, who is the man behind Spring-WS has written about a more flexible way to do web services which is in-line with my earlier posts on evolving services and transparent web service proxies. The "lets pretend that we're doing RPC and not messaging" approach of most web service frameworks. Arjen's previous project - XFire supports the reflected interface pattern used by .NET and most other Java web service frameworks. The example below shows a simple service that can be exported via the XFire servlet.
public class OrderServiceImpl implements OrderService
{
public Order[] findOrdersForCustomer(Customer customer)
throws OrderException
{
OrderRepository repo=new OrderRepositoryImpl();
Order[] orders=repo.findForCustomer(customer.id);
if (orders.length.equals(0))
{
throw new OrderException("No orders found.");
}
return orders;
}
}
One thing you should notice is that this is a POJO. No special interfaces or base classes need to be implemented. The WSDL for the service is reflected of the service interface and the types accepted and returned by the method. To export the service you'll need to provide an XFire configuration file which is quite similar to the sort of configuration you would need to give a Windows Communication Foundation service.
While XFire has a code-first approach, Spring-WS is geared towards contract first web services. The approach is different from .NET based tools like Christian Weyer's WSCF. Rather than generating a skeleton off a WSDL, Spring-WS lets you implement an XML messaging endpoint. You do this by implementing either the MessageEndpoint or PayloadEndpoint interface. The difference between the two is that a MessageEndpoint consumes the entire message, while the PayloadEndpoint only consumes the message body. No matter which interface you implement, the key difference from the traditional approach is that you consume the actual XML rather than an object graph representing the schema.
public class OrderEndpoint extends AbstractJDomPayloadEndpoint {
private XPath customerIdExpression;
// Note: The OrderService is a domain service,
// not another web service.
private final OrderService orderService;
private final OrderResponseHelper helper;
public OrderEndpoint(OrderService orderService, OrderResponseHelper helper)
{
this.orderService=orderService;
this.helper=helper;
}
public void init() throws JDOMException
{
Namespace namespace=
Namespace.getNamespace("customer",
"http://andersnoras.com/schemas/customer");
customerIdExpression=
XPath.newInstance("//customer:Customer/Id");
customerIdExpression.addNamespace(namespace);
}
protected Element invokeInternal(Element orderRequest)throws Exception
{
String customerId=customerIdExpression.valueOf(orderRequest);
Order[] orders=orderService.findForCustomer(customerId);
// Use Castor, JAXB or similar to marshall the result
return helper.marshallOrders(orders);
}
}
Details like XML marshalling are eluded from this example, but the key implementation pattern is shown.
The Spring-WS approach helps decouple services from the contract. This makes it easier to implement interoperable services because you're no longer bound to the limitations of XML marshalling / serialization. Another benefit is that most versioning, extension and XSD pains disappear because the message is no longer tied to an object model.
With .NET you can achieve something similar by ticking the "access messages" checkbox when you generate a service stub with WSCF. Still you'll be left with a truck-load of generated DTOs, because this is the way ASMX services work. Spring-WS's take on endpoints is something I've missed on several occasions, and I'd really like to see Spring-WS ported to .NET.