«

»

Dec 05

The value of a weakly typed Web Services interface

I’ve been following the discussions on InfoQ about “The Lost Art of Separating Concerns” by Matt Baker. Matt compares and contrasts the relatively architecturally clean MVC model for UI work with the programmatic web services model. For example:

GET /stockQuote?ticker=AAPL

vs.

getRealTimeStockQuote(“AAPL”)

While I generally agree with Matt, I think that he doesn’t do a good job of describing what the benefits of the more generic approach would be. So I thought I would add my two cents. Note that I’m working on a web-services project right now, and I’m in the process of defining exactly this kind of interface, so I’m putting my money where my mouth is, as it were.

First, let’s look at the basic flow of the classic MVC model:

REST1.JPG

The caller makes a request, which is first processed by a generic handler, then a custom handler (or, potentially a series of custom handlers). The handlers call the business logic which works to populate the model. Finally the model is sent to the view, which , by way of the controller, sends visually meaningful information back to the Caller.

The result – the controller accepts the request, the business logic focuses on filling out the model, and the view is responsible for presenting it in a useful way to the customer. Note that there’s very little “typing” going on here – the caller’s request is essentially a hash map with some header information, the View’s response is a long String.

How do we adapt this approach for Web Services? Web Services don’t have a View, I hear you say. Well, that’s true, unless you change the word “View” into “Response Protocol”. In that case, I hope you would agree that the Web Service certainly has a way in which it should respond to a request.

So what?

Well, if you have multiple versions of your web service, you could invoke multiple “Response Protocols” – one for each version.

But with a traditional web service approach – getRealTimeStockQuote(“AAPL”) – it would be very frustrating to handle that.

No it wouldn’t. You just change getRealTimeStockQuote and add a new parameter, the version – getRealTimeStockQuote(“AAPL”, “1.0”)

Hrrm. What if you want to create a variation that takes an array of stocks to receive quotes on?

Well, then you’d create a new method:

getRealTimeStockQuoteArray( [ “AAPL”, “GOOG”], “1.0”);

Really? Because in the Web world, you’d still be able to do that with just one Action, by adding new parameters to the request. You might have to create a new view, but you certainly wouldn’t have to create another method. You might *want* to create a new Action, but you wouldn’t have to.

And that’s just a simple example. Think about a more complex web service, one that takes, say, a more complex object. And then the object changes – you add more information to it, you change and expand its methods. And you have to maintain all of the previous versions. Think about expressing an order for an MP3 player. A web service might evolve as follows over the years:

orderMp3Player(“Brand”, “Size”)

orderMp3Player2(“Brand”, “Size”, “Color”)

orderMp3Player3(“Brand”, “Size”, “Color”, “FM?”)

orderMp3Player4(“Brand”, “Size”, “Color”, “FM?”, “Video?”)

orderMp3Player5(“Brand”, “Size”, “Color”, “FM?”, “Video?”, “Wireless?”)

And this doesn’t even cover all the variations – Form factor (nano vs. mini), the video formats it supports, the battery length. And, most importantly, what about the response? The response message to orderMp3Player() might be significantly different from orderMp3Player5(). So now you’re looking at various versions, or a common handler with multiple output formats, all hand-coded.

How would you handle that (elegantly) in a traditional web service model? I ran into that problem a while back (only with a lot more moving parts), and the only thing I could come up with was to abandon a long parameter chain, and go with an HTTP Request-style hash map for my request, and another for my response.

orderMp3Player( { Map of Features })

This approach dramatically simplified the interface, at the expense of slightly more complex marshalling and unmarshalling of request and response elements at the client. But the interface wouldn’t have to change over time, and I could simplify all of my intermediate code. I could have a generalized receiver look at the parameters, delegate the request out to the appropriate business logic, and then, based on the version the client requested, format the response according to their needs. In essence:

REST2.JPG

Look familiar?

I was very happy with this approach. The concerns with interface maintenance and dealing with customer-specific mutations went away instantly.

But, interestingly enough, I was only tapping about 10% of the power of a weakly-typed API. Because I wasn’t taking advantage of all the other capabilities of the web-based model.

  1. Filters – because the request and response are generically formatted, you can configure various filters at run-time to intercept and manipulate the request and response.
  2. Run-time Handlers – beyond just the filters, because the interface is generic, you can actually replace one version of a web service handler with another without making any code changes.

Talk about separation of concerns! Now I can delegate almost everything that isn’t core business logic out to more-or-less generic filters. And I can dynamically swap in various implementations of my service handlers, without having to build that flexibility into the code. Consider:

REST3.JPG

Now, we’ve successfully delegated out a bunch of the “non-functional” work out to these handlers for orderMp3Player(). Now, if we want to add more features/capabilities to our MP3 Player ordering system, we’re golden.

Except that our boss just told us we’re expanding. In addition to MP3 Players, we’re going to also sell Farm Animals.

Urrg. Now I have to create orderFarmAnimal(), and set up this whole system again.

Well, unless I’m smart, and I switch everything over to the even more generic order() method. I’ll define a parameter in the hashmap to define what kind of “thing” I want to order, and then I’ll do all the rest of the business-specific work in the business logic. I can even “rewire” the MP3 orders, as such:

REST4.JPG

Oh. The boss called again. Now we have to not only handle orders for MP3 players and farm animals, we also have to store videos. (This boss is clearly trend focused).

Crud. The order() method doesn’t make sense in the context of video storage. We need a storeVideo() method.

But, frankly, accepting a video file and accepting an order, while different in business focus and other ways, are both variations on “putting” something on a server, no?

Well, why not go with a generic put() method? Or, if you prefer, post() – I don’t really see the need to treat them particularly differently.

So now we can define the put() operation. We’ll know from the data whether it is an order to process or a video to store. If we need to add more services, we can effectively reuse all of our logging infrastructure, our performance metrics, our authentication, etc. Given enough time, we’ll probably end up replicating Struts or something like it.

Pros and Cons of the Weakly-Typed approach

Pro

Con

Handles change quite elegantly

It is a harder API for the user to use, because it is so generic

Cleanly separates the various layers

Will be slightly more verbose

Allows for run-time changes in flow, structure and behavior, without explicit code

Because the server-side is so easy to change, it is easier for a non-coder to use and adapt it, making it harder to ensure job security with a complex and hard-to-use API

Of these cons, the harder API is the only major concern, in most contexts.

So how do I deal with that?

At the end of the day, this weakly-typed SOA is something of a domain-specific language – the “things you can do with my company’s services” language. Here’s how I’ve dealt with it so far:

1) Create a set of libraries to support the packaging and un-packaging of domain-specific objects into hash maps. Repeat for as many different languages as you can

2) Create tutorials, examples and test suites to help teach the user which parameters should be used when

3) Work hard on the error reporting feedback back to the user. Error message support in your parameter map should be one of the first things you do.

If you do these things, you’ll be miles ahead of almost everyone else on the user-friendliness side, and your API will stand the test of time.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>