Saturday, May 4, 2002


open location theLink -- I finally found the right generic URL/item opener in AppleScript.
7:28:42 PM  blips[]    
New Copyright Crimes
Yale Law Top Ten New Copyright Crimes.
7:22:50 PM  blips[]    
Verbosity and Web Services, again. Does Dave Winer really get it?
Forward: a few hours ago, after getting home from dinner with friends, I was reading more on Web Services, and found this article on Wrapping Web Service APIs on ORN by Stephan Figgins. It somehow led me back to an old Scripting News post that somewhat rankled me when I first read it. I'm finally posting my response. At 2AM, saturday morning.

Does Dave Winer get Web Services? This posting talks about the overhead involved in publishing a web service via .NET. I admit, my experience with Radio and Frontier is still very small. But, at least with Radio, you have to write your Web Services script in a special folder, Web Services. How is that not overhead? Now, you're writing a single script adapter to another part of the system in order to publish it on the web services channel. This may make sense in an environment based on pure scripting, but what about real persistent objects that are already on the web - objects that are both Data and Behavior? Zope, and Bobo before it, have been built on this concept from day one.

Many a year ago, Amos Lattier wrote an article called The Bobo Way. I'm going to quote his article here, because (as I've said before), this sounds like familiar ground:

For example if your app keeps track of company employees, you will probably want a separate Employee object for each employee. That way you can access these objects with URLs like:

/Employees/amos/set_menu?lunch=spam

This URL assumes that Employees is a mapping object that contains Employee objects. Employees["amos"] is an Employee object that represents the amos employee. The above URL calls amos' set_menu method with lunch=spam for arguments:

Employees["amos"].set_menu(lunch=spam)
Ed: this could also be Employees.amos.set_menu(lunch=spam)

For contrast lets look at a non-bobo way of doing the same thing:

/set_menu.py?empid=amos&meal=lunch&food=spam

It seems to me like an awful lot of XML-RPC interfaces are not much better than this last example. They're usually a single function that is handed some sort of key to operate on another object with, rather than going to that object directly. It defeats polymorphism, it defeats generalization, and ultimately it can lead to a large and unwieldy adapter layer, tucked away in a Web Services directory somewhere.

There are merits to this design. You are basically writing an Adapter pattern of sorts, mapping a Web Service friendly interface into whatever the internal system really has. And, if that's the only gateway into your system from the outside, you have some level of security by limiting the amount of access to a collection of XML-RPC dedicated functions instead of opening up access to every potential object.

But there are merits to the other design too. In theory, you can program Zope the same way on the inside as on the outside, so long as you have the credential to do so. You have instances of classes out there, on the web, already. .NET seems to be following this route somewhat. Doing Web Services by this route is scarier. Why? Security for one reason. And, you just don't want to turn every single method on the object to be callable over the web, or - at the very least - you want to attach conditions. This is what Microsoft is doing. Let's look at how a simple 'return hello' class for Zope looks, disregarding any Zope OFS framework stuff (making the class addable through the web interface). Putting an instance of this class in a Zope tree would yield the same results over XML-RPC as calls to this .NET example would:

from Security import ClassSecurityInfo
from Persistence import Persistent
from Globals import InitializeClass
 
class Hello(Persistent):
    security = ClassSecurityInfo()      #Used to do declarative security
 
    security.declareObjectPublic()      #Make these objects inherently public
 
    security.declarePublic('sayHello')  #And make this method public
    def sayHello(self, name):
       """ Say hello to 'name' """
       return "Hello %s" % name
InitializeClass(Hello)                  #This processes the security declarations

Now, it's about the same overhead as the Microsoft ASP.NET example. note: There's more overhead involved with Zope itself in order to fit the Hello class into the Zope framework, but this is purely an example. This could have been easily written as a Python Script object, where the security settings could be set through a web UI.. Why do all this? Because web services as API's are ultimately programmatically exposed. There are two ways this is done:

  1. Declarations, either in code or configuration (ie, xml) files.
  2. Writing separate handlers, and only exposing those to the web
The first is the Zope way, and apparently the .NET way. At some point, you're taking code that exists already and saying "yes, this is a web service".

The second seems to be the Radio way, where you write a script that calls into other existing code, and say "this script is the web service".

Sometimes, the amount of overhead is equal. But I think relegating all Web Services to being scripts and functions in a special folder or singleton object weakens their potential. Likewise, not all existing web-published code is ready to be made into a Web Service. Both ways are valid. But, there's got to be a better unification. Dave needs to understand the declarative way. It may look more verbose on a silly little Hello World example, but I have access to thousands of methods exposed via XML-RPC that are done so automatically via Zope's similar security declarations. That's a lot more than I'd have access to if I have to write those methods in a special place.

So, here's another verbosity example. Let's say there's the following bit of code in a Blog Entry class:

class BlogEntry(...):
   security.declareProtected('Edit Entries', 'editPost')
   def editPost(self, content, publish=0):
      """ Set the content of the post, and publishes it if publish is true """
      self._content = content
      if publish:
          self._publishedContent = content
      return "OK"      #simple return value
And, for simplicities sake, lets say we had a flat structure for all entries to make URL's easier, and we have an entry identified as '123'. So, to edit it via URL. Hmm, this is almost REST-like:

.../Posts/123/editPost?content=boy+howdy&publish:int=1

And do to it via BCI (ZPublisher.Client):

myPost = ZPublisher.Object('.../Posts/123', username='bob', password='bobby')
myPost.editPost(content='Boy howdy!', publish=1)

And XML-RPC in Python. Note that in order to do this, due to Zope's built in security, you either have to have a hacked XML-RPC library locally that generates authentication headers, of have RPCAuth by Nathan Sain (which I haven't had time to test yet) installed on Zope:

myPost = xmlrpclib.Server('.../Posts/123')
myPost.editPost('Boy Howdy!', 1))

These are very similar, but ZPublisher.Client was doing this long before XML-RPC. Why? Because Zope was already doing distributed objects (of sorts) on the web, translating HTTP requests into object calls. It only makes sense that there be some simple client lib.

Now, to implement this as a proper Blogger API, and do it the way that Radio style web services are done, instead of necessarily having all of those security declarations on the BlogPost class itself, I would instead have to write this:

def editPost(appkey, postid, username, password, content, publish):
  """ Lookup the post by its ID, verify that the user can access it,
      and set its content and published flag """
  thePost = Posts[postid]
  ## security assertions based on username/password would go in here, somehow.
  thePost.editPost(content=content, publish=publish)
  return 1
Now, this isn't too bad. But, it's a similar amount of overhead to turn an otherwise already existing published web method as a Web Service. Huh.

As people start exposing ever more valuable resources as Web Services, or even REST, it's no wonder complications arise. As we move beyond the 'getStateName' example and start getting into exchanging business data (Microsoft's heavily advertised 1 degree of separation), more care must be taken. I don't want everything I write automatically turned into a Web Service. I don't necessarily want to have to write extra adapters either.

To sum up and say it all over again, the way that Radio seems to promote for Web Services is easy. And simple. But you're effectively writing scripted adapters and bridges into other code, something that could turn out to be a maintenance nightmare. The way that Zope has promoted for years (even though it's not a terrific Web Services player, sadly) says that "Objects and their methods are already published on the web. No gateway scripts needed.", but with that comes the price of maintaining security declarations similar to C#.NET's ASPX's declarative Web Service calls. Dave calls the latter too much overhead. I say it's the other way around. It should be easy to write Web Services, but you should have to think about what you publish. Those declarations aren't much worse anyways than the long signatures verbose languages like Java can make you write (protected final synchronous int Bob()).
2:07:25 AM  blips[]