Thursday, May 22, 2008

Groovy as a Service Layer: The Aftermath

A while back I posted about why you would want to use Groovy for your service layer within a service oriented architecture. I spent the last few weeks doing just that with Axis2, and now I can look back with some perspective and confirm some of the original ideas.

My service layer moves incoming requests from untyped primitives (Strings and XML blocks) into typed Java objects consumable by a business facade, and it moves typed Java objects produced by the facade back into untyped primitives (Plain Old XML) consumable by a client. I chose Groovy based on how easy it is to produce and consume XML data within the language.

Groovy as a service
Groovy has been a joy to work with and really exceeded my expectations. The XmlSlurper class took a while learn, but using it to read XML or Axis2 OMElement objects was super easy after the first few times doing it. And generating XML with MarkupBuilder or a custom OMElementBuilder is incredibly trivial compared to the Java equivalent. By the way, writing my own OMElementBuilder turned out to be easy. Extend BuilderSupport and implement four methods. Simply put, my service classes contain much less code than if I'd written them in Java.

My original post suggested that Groovy was a better language because it was dynamically typed, thus supporting untyped data (XML) better. This isn't quite true. It is a better choice because of the XML support, which some statically typed languages also share. Scala has good support for writing and reading XML. It's hard to say anything about type systems without being proven wrong, isn't it?

Asix2 tooling
wsdl2Java is a great tool you point at a WSDL and generate reams of framework code and a single "MyService.java" pojo that will be your service class. At this point, I would take MyService.java and rename it MyService.groovy and away you go! Nothing about using Groovy has made any of the Axis2 tools break. Axis2 is instantiating your service class using reflection, so as long as you produce a class file with the correct public method, then you're good to go. Scala services should work just fine too, as well as JRuby (I'm guessing). HOWEVER! The default "MessageReceiver" in Axis2 invokes your service method using reflection. This means your sweet metaprogramming invokeMethod tricks will not work. That took me a while to figure out.

That being said, I turned all that SOAP cruft off and just used the existing raw XML message receivers. Eventually I threw that out and wrote own. Just another case of Not Invented Here? Possibly. But it was the only way I could get my MessageReceivers created out of Spring (a post for another day, sigh).

REST vs. SOAP
Axis2 support for REST was underwhelming. Under the covers, the REST request is converted into a SOAP envelope for you. So your "MessageReceiver" always sees a SOAP message, even when you're using REST. Also, only POST and GET work. After looking breifly at Grails url mapping and REST support, I would encourage someone to use Grails instead of Axis2. We don't always have the authority to make these decisions though. I know I don't.

Mixing Groovy and Java classes
I never understood the Groovy joint compiler until now... My project has Groovy classes that reference Java classes and Java classes that reference Groovy classes. The joint compiler runs through the Groovy code and generates Java stubs for all your Groovy classes. Then it compiles the Java classes against the stubs so that Java has something to link to. Lastly, it compiles the Groovy code into real classes and replaces the stubs. Nifty, although sometimes you'll have MyClass.groovy and receive a compiler error referencing MyClass.java. That'll confuse you the first time!

I also felt Groovy's optional static type system really helped the Groovy classes integrate into the existing Java codebase. My goal (and constraint) was to write Groovy based service classes and leave the rest in Java. As my Groovy classes grew, and code started being refactored into reused components, I started tightening down the compile time contract of the shared code to make it more usable. My first step was to declare return types on methods. This made the calling code a little easier to work with. On some classes I eventually stripped out the Groovy specific libraries and converted them into pure Java classes (.java extension and all). It was great that I could have the thought, "Oh, I really should have written this in Java" and then actually convert the thing to Java within a few minutes.Although, by and large, my more common thought was, "I wish I could just do this in Groovy" rather than vice versa. But as I said, we don't always have the authority to make these decisions. However, a few of my classes can't be converted to Java because of the reliance on metaprogramming. If you're cautious, you might avoid putting yourself in this situation. I say go for it.

Lastly, I work in an IntelliJ IDEA shop. Having the great Groovy plugin for IDEA helped immensely. This would have all be a lot harder if you were switching IDEs or editors based on language. Being able to step through code from Java to Groovy and back again, as well as navigating the whole project as one unit, made the whole process work a lot better.


Big thanks to everyone who left a comment or contributed on the discussion on the Groovy.mn mailing list. You helped a lot!

No comments: