IntroThis is going to be a long post. The first part is a ramble and background info on dynamic languages and other stuff. If you want to read about the wicket/groovy thing, skip ahead. Also, I'm currently interviewing in NY. Shameless plug at the end...Its been a strange time for me, technology wise. Well, all over the place, but certainly on the technical front. I'm about to go live with my big project of months past. Its based on JSF/Facelets and Hibernate. I've been a huge Java fan for quite some time, so new front end technologies, along with the solid ORM tool we've come to love seemed like a natural fit. I'd heard a little about these new fangled dynamic technologies, but it all sounded like a lot of hype to me.
About a month ago I spent a day interviewing at a company call Cyrus Innovation. This was a huge eye opener. They actual follow agile techniques, including actual pair programming all the time, which is something I've never seen. I've heard a lot about it, but in most cases, management didn't have the will to actually do it. I, as a manager, thought it was kind of a crazy concept, but having seen it, I think it works. I also learned that unit testing is actually important. Seeing full unit testing in practice makes that clear. I also saw a group of people who actually thought Ruby and Rails is a revolutionary platform. That, I just didn't get.
Its easy to call it hype, but the people at Cyrus are very intelligent, and they are very, very efficient, so hype would burn out pretty quick if it didn't deliver. So why were they so big on it?
See, here's the thing. I actually don't dislike Ruby or Rails. What I don't like is the concept of dumping all of your existing code (and all of the world's open source code, and the non-open source stuff, if you like that sort of thing). It seems a little drastic to just toss everything out. Right? I've been reading lots of blogs and opinions, and the pro-dynamic camp have the opinion that languages age and die out, and new ones come and take their place. This is logical, as its what's happened in the past. However, as you hear in the omnipresent financial services commercials, "past performance does not predict future results". I just think we have too much value in existing platforms to simply toss it out and start over. On top of that, I don't think dynamic languages serve all purposes, and I certainly don't agree with the productivity improvement claims in all cases.
So, with all that in mind, there are a few options:
JRuby would be one, but there's some work to get it functional with Rails (as far as I know). It also seems a little reactionary for my taste. Not that there's anything wrong with it. Jython is another entry into this category. Both have the issue of a foreign syntax, at least for a Java person. I also just get the impression that neither is really going to pull ahead.
You know where I'm going. Groovy rules. At least for the Java platform, it seems like the obvious choice. You could argue that it doesn't have all of the features of Ruby, or maybe isn't quite so "dynamic", but you'd really have to wow me with something to get me to think it would make a difference. Groovy is well suited for the JVM, and it only took a little while to love it. If you add on top of that Grails support for Rails-like ORM, but on top of, and compatible with, standard Hibernate, and I'm sold.
In fact, I think Java would be best served to be largely left alone, and have Java and Groovy co-exist. Java is a solid platform and well suited to more system level stuff. Groovy isn't as good on performance, but is easier to code and test with. You can call classes back and forth, seamlessly, and use either flavor of language as the situation dictated. I think Groovy is a fantastic technology and all I'm worried about is that it'll get buried industry momentum alone. (I also think the name should be changed. I was on an interview yesterday and mentioned it. Doesn't sound professional, and the reaction reflected that. Just my $.02)
Anyway, yeah, Groovy is good. Right before I got into Groovy, I started looking at a technology called Wicket. After working with JSF for almost a year, trying Wicket was like that movie scene where the clouds part and this big ray of light hits you in the face. I just had this feeling while JSF'ing that certain things were harder than they needed to be. Well, I was right, and the Wicket people figured it out.
Wicket and GroovyTechnically, you can use Wicket and Groovy with no changes. You can just compile your groovy classes into java classes, and that's it. There is, in fact,
an existing library for wicket/groovy integration, but from what I can tell its just providing a classloader for groovy classes. If you're compiling, you don't really need it.
There is a problem, though. You *can* code Wicket pages with Groovy, but its painful. Groovy doesn't allow for inner classes, and certainly not the anonymous kind. Wicket can get by with name subclasses, but you'd be doing a whole lot of extra coding. Groovy is supposed to be cutting that down. So, I started looking for a way to smooth this out a bit. Since Groovy is dynamic, I figured there must be a way to do better.
I first tried to look at enhancing Wicket's library, like Groovy does with the GDK and standard Java library. I could add closure support to the standard extension points, and this would solve the problem for Groovy. Sounds nice, but there are a few problems:
- The first critical issue; I don't think you can use the MetaClass functionality when calling from outside Groovy. At least not directly. This will take a Groovy expert to verify, but here's what I think is going on. If you call an object that is 100% Groovy from Java, it makes the call through the MetaClass proxy. If you call a Java class from Groovy, it'll use the global Registry MetaClass for that Java type. If you extend a Java class in Groovy, and call it from Groovy, that object will get its own MetaClass. However, if you extend a Java class in Groovy, and call it again from Java, you're calling the methods directly again. No MetaClass proxy. If that is not correct, let me know, but that's what it looked like to me. The rest of the app is coming from Wicket, which is coming from Java, and wouldn't have access to the dynamic features until you've completely crossed the blood/brain barrier (and you wouldn't have).
- Even if you got around that, some Wicket classes are abstract. Simply intercepting calls and handed them to closures would be pointless. You'd have to implement a complex scheme to handle that.
- Another issue that I had to deal with anyway, Closures aren't Serializable. Hanging on to a reference in your objects doesn't work with Wicket.
To put it lightly, the start was painful. So, I kept digging away, and pretty soon I stumbled on the answer. Builders.
It was really the SwingBuilder that tipped me off. One of the first things you notice when starting with Wicket, assuming you've had some Swing, is the similar object model relationship. Its tree-like. Lots of 'add(component)' calls. So, we could just build the tree, add closures where needed, and we'd be off. Sounds nice.
Pretty quickly I ran into problems #2 and #3 above, compounded by a frustrating attempt to use the MetaClass to do a lot of the work for me. In the end, here's what I came up with.
- The name of each builder "function" node will correspond to a Wicket component. The builder will look through various packages for a component matching that name. A string is provided for the key. Pretty much every component needs one, so that was a no-brainer.
- Most components correspond to the [String] and [String, IModel] constructors. Some, however, do not. It would've been easier to just figure out the whole model and implement what was needed for each component type that strayed from the standard, but we want to play nice and support sub-classes and all that. Plus, there are special circumstances where you can use shortcuts to save you time. So, I built a facility to register custom component builder factories. You register a factory, and that class and its sub-classes will attempt to use it. You can override it for a sub-class, so you're not locked in. Also, if you have a custom component library, you can add it to the configuration in your application start up.
- The builder naturally follows the tree model, so in simpler cases the code cleans up significantly. As an example, the following Java:
item.add(new Label("id"));item.add(new Label("name"));item.add(new Link("setItem"){ @Override public void onClick() { form.setPersistentObject(item.getModelObject()); }});turned into...
label("id")label("name")link("setItem", onClick:{ form.setPersistentObject(item.getModelObject()) })
- The subclassing issue was ultimately handled with closures. How that works is a bit complicated, and to be perfectly honest, I'm not sure if the solution is elegant and ugly. But it works.
In a nutshell, that's it. Keep your builder names the same as the component class names, and keep the component reference page (not yet created) handy, and you should be ok. This project is still FAR from prime time, but I think if the kinks can be worked out, it'll make Wicket development on Groovy much more compelling. At least I'm going to use it ;)
Something very important to keep in mind. The syntax looks like I'm substituting Java for a whole different language, and would be rigid and cause problems as you go forward. However, inside the builder you can use regular Java/Groovy code, and directly create Components with regular constructors, and add them manually. All of this while still in the builder tree. Because of this, you get the 80%-90% easy case with the builder, and can still seamlessly do the painful 10% that doesn't fit right.
An Example Page
Let's take a quick look. I made a demo application with a very simple data object, using databinder and hibernate. This should give you a quick visual overview on what the wicket/groovy builder can do. I'm using screen shots because posting code in the blog was difficult. Plus, this gives you a good idea of what it'll look like in the editor (which is where you'll see it anyway, right?)
Java VersionGroovy VersionIf you take a look you'll notice the Groovy file looks similar, but just has less clutter. That, in a nutshell, is a lot of what the dynamic language stuff is about. I didn't "get it" for a long time, but I do now. The great thing about Groovy is that its type optional, so if you want your types in there, you can have them. Now all we need is some better IDE support, and I think Groovy will be (groovy).
DetailsIf you know a lot about Wicket and/or Groovy, and you have some time, I'd like to lay out the details of my implementation. Or, at least start. Hopefully I can get some feedback as I'm a beginner in both technologies. Also, I started this on Saturday, and I had been out pretty late the night before. The code is a little on the messy side right now, but I think the general design is functional.
Here's how things work. We match up components based on name. Right now the matching facility is pretty flat. It looks through the configured packages for the name, and uses the first one found. After that, the name is cached. It will also look in the same package as the page. This allows you to override a class right in your groovy file and use it. The configuration needs to be more robust. Maybe you only want certain parts of the app to use certain figurations? For today, there are bigger problems, but this is on the list.
WicketBuilder builder = new WicketBuilder(page)
builder.label("pageTitleHeader", model:new PropertyModel(this, "name")) The first line creates the builder. You supply a context argument. The second line is the root level call, to 'label' in this case. It does a simple match to the Wicket 'Label' component. Next is the list of arguments to 'label'. The first is the main one, which is the Wicket key value. After that, you supply [name:value] pairs. In this case, 'model' is the name, and a new 'PropertyModel' object is the value. Anyway...
So, you've found the component you want. Now you need to look in the configuration for a ComponentBuilder factory for that particular component class. The structure is in a class called 'ClassHierarchyTree'. I'm sure there are plenty of similar implementations, but I wanted something quick and dirty. It works like a tree map. You can add Class definitions as key values, and associate them with data objects. When you go to look something up, you pass it a Class object. It'll pull the data associated with that Class, or the closest superclass. Pretty simple.
Ok, so you have a factory class. This factory will get the attributes you set for the object and use them to initialize an instance. If you are missing something, it should throw an exception and bomb out with a reasonable message (cleaning the messages up is a todo). In the example above, the factory will call the standard constructor for Label with the String key, then call 'setModel' with the model argument.
Things get interesting when you supply a closure as an argument:
link("setItem", onClick:{ form.setPersistentObject(item.getModelObject()) })Here, 'link' matches with the Link component. 'setItem' is the key, similar to the Label above. However, 'onClick' has a closure as an argument. What happens now is the factory class builds a list of all closure arguments, then actually generates a new sub-class of the component. This sub-class will override all relevant methods, and delegate to the closure at runtime. If a method isn't found for the named closure, it will throw an exception and the page will fail (say, if we'd typed 'inClick' instead of 'onClick').
Rigth now there is no caching going on, so each time this happens a new class is generated dynamically. It works, but we could cache classes with the same signature. That's on the todo list.
My big concern is how I'm handling closures. Closure instances aren't Serializable, so keeping them in Components doesn't work. However, Closures correspond to Classes under the hood. So, I actually stuff a reference to the Class representing the closure into the Component instance. It seems to work ok. There are some issues I had to deal with. First, local variable references would be worthless. Luckily, the Closure has a different constructor in these situations. If local references are found when the Closure class is set, it will fail with a useful message.
So, anyway, sub-classes. The beauty is that abstract classes get concrete implementations as a result. It all sort of works. I'm sure there's a big issue in there somewhere, but tonight I feel pretty good about the whole thing.
I'm going to
upload a zip of the code for now in case you'd like to take a peek. Hopefully the sourceforge project will be up soon, but in the meantime, this should do.
Wrap UpIf you're still with me here, congrats. Here's my immediate plan. I've posted a sourceforge project request. When that comes back, I'll add everything there. I'm working on a big project right now that I've been mildly neglecting to get this functional, but will start using this on that project. Just the back end pages, of course. I'd like to find somebody with serious Groovy and/or Wicket understanding who would be interested in taking a look and seeing if there's anything glaringly horrible going on. If anybody wants to help, obviously let me know.
Off the top of my head the todo's are as follows:
- Stabilize configuration and come up with a decent demo app
- Do some real unit tests (there are some, but they're pretty light)
- Explore integrating GORM from Grails, or the other way, using this as an alternate view layer in grails. I have very little grails understanding, although the GORM stuff is pretty sweet, and I'd love to hook it into this for an easy win
- Make sure there aren't big issues with the way Closures and dynamic classes are being generated. I'm not sure how a clustered environment would react
- Implement the extension stuff. Right now its just the standards and a little of databinder
- Cache generated classes
- Build some simple CRUD generation tools. Similar to Grails scaffolding, but far less robust. Just point at a domain class and build a list display and edit form
That's it for now. I'm wiped.
EmploymentIf you're still with me, I'd like to put out a call for employment opportunity. I've been taking my time finding my next career step. Its been a couple months of interviewing now and I'd like to make a decision soon.
See my page for resume, other work, etc.
More to come...