2009-10-29

GWT App Architecture Best Practices (slides+text) Part 2

The source is



So, on with the theming. I mentioned that you want to decouple your app. The whole left hand/right hand don't talk situation. The way that we're going to make that happen is the combination of an Event Bus, using the MVP pattern when we have custom widgets to implement, and by using dependency injection, preferably via a tool like Google GIN, which is derived from Google Guice, which all of you --all of you should be attending the Guice tech talks that follow this one, by the way, since you're already blowing off the Wave guys. Guice, Guice, Guice.
Let's go into depth on each of those. Oh, the reason that you're going to do this is because it buys you things. Because your app is nicely encapsulated, you're going to be able to throw away bits of it without the entire thing falling over. You're going to give yourself a spot where you can delay, as long as possible, the slow DOM operations that you might want to be--required to perform. Your application logic will be separated enough from your widget and DOM manipulation logic that the slow things can happen later or never. You're going to find that unit testing is a lot simpler to do.
If I can if it's very clear how I instantiate this particular part of the app, it's going to be ease for me to test that particular part of the app without having to swap in the other 500 parts of the app. And if I do this right, I'm going to have really fast unit tests that I can execute in a JRE rather than in a GWTTestCase. So unit tests that execute in microseconds rather than in the 20-whatever seconds it takes to launch GWTTestCase these days. Which time we are improving, let me say. But it'll never be the same as simply not doing it at all.



So what's an Event Bus?
Let's show you first what an Event Bus is not. You're writing your app in a typical style, and you've probably created yourself
a bunch of model objects in our address book that are probably contact groups and contacts. And they got property lists. They're throwing property change events. Each one has a list of listeners that are watching for changes to the thing. And we'll start adding in bits of our UI. We've got our contact group list UI, I We've got our contact group list UI, which is going to have a listener attached to each one of these group objects. And then we've got the list of contacts within an individual group that's listening, attaching handlers-- event handlers to some of those contact guys. We probably have a display with individual contacts
that the list UI needs to know how to swap in. And the contact list is going to want to edit these things, and these are all going to be listening to each other for update notifications, you know, show yourself, hide yourself, I'm done now.
And then marketing comes in and says, "We don't have enough bread crumbs on this thing, and that Window Title's looking kind of sloppy."
We got kind of a messy world here.
We've got a lot of handler objects that we've instantiated. We've got a lot of things that we can forget to decouple when it's time
to tear things down. It's not a fun world. I won't even talk about testing. It's an excellent profile. So we can reduce this coupling
in a couple of steps.




But first, we'll introduce our Event Bus.
This thing on the side of the app that everybody has access to, where they can listen for the stuff that has happened in the application that might be of interest to them without necessarily needing to know who was responsible for that event in the first place.
So, for example, when the contact list UI wants to tell the world, "Hey, the user thinks that it's time to look at the details of contact X," it can just post that event to the Event Bus. And whatever parts of the UI are responsible for actually showing the user contact display, contact number X can take care of doing that. And it doesn't have to know that it's -- this week, it's the contact list UI that's responsible for doing that, even though next week it'll probably be some other gesture that some UI designer-- respected UI-designing colleague -- came up with. But you see we still have a lot of mess in the middle there. We've got all these property change listeners that I keep clearly implying I don't think you should have.


And what I espouse doing instead is just getting rid of that model core. Not saying that you don't have model objects, just saying that they're not terribly of central importance. Instead, remember that we know that everything that we do is asynchronous. Let's just push that asynchrony right through to the bottom of our application. When the contact display UI is ready to display somebody, it'll tell the RPC service, "Hey, get me contact XYZ. I've been told to display him." With any luck, that's cached, and so my callback fires right away, and I've got him immediatly. When the editing UI needs to mess with that contact, and change his data, he's going to tell the RPC service, "Hey, I've got an update for you. Push that down to the service for me."
When the response comes back in to the RPC service, he will announce to everybody who is interested, "A contact has hanged. "Here is the new copy of that contact. Why don't you all who are interested display that thing?" And everything is nicely isolated from each other.



So let's look at what the code would shape up as for that. You'll remember this showContact method that we wrote earlier. We're going to add a little bit to it.


We're going to start paying attention to just which contact we're looking at at the moment over here in our contact display UI.
So just something as simple as holding on to that Id object so that we know who's important to us.



Before we ever tried to show a contact, we attached ourselves to the listener bus. Sorry, the Event Bus.
We're going to use a GWT class to actually instantiate our Event Bus, because that's simpler than writing our own With GWT 1.6, we revamped our event handling mechanism. And at the heart of each one of our widgets is a handy class called the HandlerManager.
There's no reason that you can't use your own instance of HandlerManager to provide your app-wide event dispatch services. So our contact display has somehow magically gotten its hand on the HandlerManager Event Bus instance for this app.
And he's attaching to it a typical event handler. You've created for yourself a custom Change Event type called ContactChangeEvent. And you're implementing your custom event handling interface. So when a contact change comes in, you're going to look at the new contact object that's hanging off of that event. see if it's the one that you're actually displaying, and if so, do something about it.And a subtlety here is that you don't really know if that's the one and only copy of this contact, if it's one that's been handed to you for your purposes at this moment. The contact goes from being of central importance where you might be attaching handlers to and from it to this kind of ephemeral, disposable thing you can kind of wad up and toss on the floor when you're done.
But don't change it, 'cause other people might be looking.





Continuing with the code, let's move over to the RPC service itself. What we're doing here is we have wrapped the GWT RPC service that we defined with somebody who's implementing the interface itself. In this particular case, we're taking that execute method that our RPC service provides and we're overloading it to provide specialized handling of this UpdateContact type of action.
And the interesting bit is that we are wrapping the callback that the caller gave us with our own callback, which on a successful response is going to update whatever kind of a cache we're maintaining.


It'll fire off the real onSuccess result for the real callback, and then it's going to tell the rest of the application, "Hey, contact changed. You might want to do something about that." It's as simple as that.


So continuing with our decoupling theme.
I keep saying "MVP, MVP, MVP."
And I did not say MVC. Now the odds are that when you got out of the five or ten years you spent doing JSP and Web 1.0 app, you kind of dusted off some desktop knowledge or somebody who had once done that kind of work told you, "You know
what we should really be doing "now that we've got these widgets is model, view, controller."
And it's this kind of triangular thing. And the model throws events. And then the view throws events. And the controller messes with both of them. And I forget what code goes in the controller and what code goes into the view. And no two people will give you the same answer for what MVC actually means. There are a handful of guys who wrote Smalltalk-80, but thev may well be retired.
You also wind up with a problem here that, like I said, some of your code landed in the controller. Some of it landed in the view. You need to test it all. And the code that landed in the view is not going to run real fast. It's messing with the DOM. And you either need to have a pretend DOM there If you're lucky, which we don't provide you-- yet-- or you need to fire up a real DOM. And those tests are just not going to run fast.



So what you want to do instead is use the model, view, presenter pattern. We found ourselves implementing this style kind of by accident, just trying to isolate our code from tests.
And then some people who are brighter than me were reading Martin Fowler's blog and saw that he had already invented this pattern before most of us were professionally active and had called it MVP. So we call it that too.
Then what you're doing is just much simpler layering. Your view class down there at the bottom is the only thing that actually knows anything about DOM elements and widgets.And it's a source of things like KeyboardEvent and ClickEvent and very
browser-specific events. The model at the top, we're familiar with at this point. It's this kind of overglorified bag of setters and getters that we're using because we decided we don't like property change listeners. And that leaves the presenter in the middle
as the only object that has anything that's really worth testing. This is the thing that knows how to take,say, a contact in one end
and take its values and cram them into a bunch of text fields on the other side, listen for changes to those text fields, and interpret them as something-- as values to push back into the model.

 
When it comes test time, we can ignore that silly little collection of DOM widgets that we created and replace it with a bunch of mocks. And in fact, we can even use the GWT MockUtilities package, which I think was also introduced in 1.6, to use something like EasyMock or J Mock-- or actually, there's something called Mockalicious now, which looks interesting--to provide fake test fields, fake this and fake the other, and run them under a JRE.
Although today I'm actually not going to show you doing that, because I found that for the pretend code that I'm putting together, it was easier to do the mocks by hand.



So let's get concrete again.
In our address book, we are doing things like editing contact details. Say we're going to edit somebody's phone number.
So a phone number looks like that, it's got an Id, it's got the telephone number, and it's got the work, home, mobile, babysitter label.

 
The UI probably looks something like this. I'm sure we've all seen that numerous times.




And all of the interesting stuff is here at the presenter layer where we're going to implement a thing called the PhoneEditor, whose job in life is to take a phone object in one end and a phone UI in the other end and make them all go.


So the PhoneEditor probably looks something like this. At least it does when I write it. Oh, the brace is in the wrong place. Damn. It's going to define an interface for the kinds of widgets it wishes it was talking to. And you'll note that I'm describing things like a save button and a cancel button and a number field. But I'm not calling them "buttun". I'm calling them HasClickHandlers and HasValue. These are characteristic interfaces that the GWT widget set is littered with. And if I define my display interface in terms of these characteristic interfaces, it becomes quite easy at test time for me to provide mock instances of those things. A lot of people think that providing this display interface is more trouble than it's worth. And they will tend to give their presenter objects actual knowledge of the specific widgets,
like text field, like button. Not a big deal, because you can, again, use EasyMock or its like to put in fake versions of those in the JRE. But that made the slides too big, so I didn't do that today. So we've got our display interface.

Oh, there's another cheat  that I'm doing in here. I have implied that GWT has a pop-up widget that implements the HasValue interface. It does not. We ran out of time. I think it will have such sooner or later. Maybe even in the 2.0 timeframe, I need to talk to my boss about that. But in the meantime, it is no big thing for you to write your own HasValue wrapper around the list picker as you're going to use it. So full disclosure out of the way. We did. And it didn't take us hardly any time at all.



How are we actually going to use the thing? At some point in the lifetime of our PhoneEditor, we'll be handed our display object, which--don't tell enybody-- it's a composite. We'll hold on to that thing. We'll attach a few handlers to it. OnClick of the save button, I want to call my doSave method. OnClick of my cancel button, I'm gonna do my cancel method.



Sooner or later, somebody's going to tell me to edit a phone number. to the phone instance that I'm editing. And I'll do that simply by holding on to the phone instance that I'm editing. I'm implying a kind of sneaky thing there with that static method on phone. I'm figuring that the instance of phone that I'm editing may not turn out to be the one that I actually want to keep. And to make sure that I'm not going to accidentally mess with the phone object that other parts of the app might be trying to display, I'm going to assume that I defined a kind of a clone style method on my POJO. You can follow this convention or make up something else. This is more or less what we did, except our code was uglier. Once I've got this safe copy of the phone, which I can toss away when I'm done because model objects aren't special anymore, I'll just tell my display to show the number and update the label picker and that kind of thing.


Now let's actually save what the user has done. Again, it's a simple thing to pull the values back out of that display instance and then fire off my nice asynchronous service method. When I hear back that my POJO-- my phone number was updated successfully, I'll tear myself down, whatever teardown means for my PhoneEditor If there were errors, I'll have some error reporting and rendering convention that I will have implemented, and I'll display those things there.
Obviously, I'm glossing over a lot. And you'll call me on that at question time. But until then, I can just get away with this.



So let's talk about dependency injection. I'm sure you've all heard the term. And you've all been meaning to adopt it, and it seems like a good idea, but maybe on my next project. Dependency injection ain't no thing. It is just a pattern. All it means is that instead of having global instances of my services that I reach out for, like GetEventBus.singleton instance, GetRPC.singleton instance. And instead of using a service locator that I kind of cram full of every interesting property of my app and shove it down the throat of any part of the app that might want it, I'm very explicit, preferably via constructor arguments, about just what it is this particular object needs to exist in a useful way. DI is really not at all hard to do manually. And in fact, AdWords does do all of its DI manually, because we didn't have any way to do it automatically at the time that we started the project.
In the interim, there was a GWT developer on another project... this is public. You know the contact manager in the personal profiles that Google is starting to flog these days? The group that put that together were a big fan of the Guice dependency injection framework for Java, and they wrote their own version of it, called GIN And now more and more apps, internally and publicly, are taking advantage of that. You can find both Guice and GIN at the URLs provided there. There are also talks comming up an Guice right after this one, and if you leave this room, I suggest you go to that room. but it's on your little badge-y thing.
I had a picture of Snoop Dogg here for the GIN and Guice thing, but the lawyers wouldn't let me do it. So, sorry.



So what does the code actually look like when we do something like this? Well, let's start at the bottom of it. Let's assume that we have a widget that is a widget in the MVP style that is my contactViewer. And that's kind of the heart of my app. And when the contact viewer is ready to go, it wants to get the root panel and just start to jump into it. So what do I need to do to instantiate my contactViewer? Well, I'm going to look at its constructor arguments. It needs its ContactWidget, probably implementing its display interface, because I wrote it.
It's going to need an instance of that PhoneEditor MVP presenter thingy because it's going to-- it uses that end line. It doesn't go talk to it through the Event Bus. Not everything needs to talk to everything else through the Event Bus. Things that want to deal with each other directly-- there's no reason that they can't have handles onto each other. We still like composition in this world. It needs the RPC service to ask for the contacts, and it needs the Event Bus to listen for changes to the contacts that it cares about. So it's going to get its hand
on the ContactWidget with a simple "new." It's going to get that PhoneEditor with a simple "new" -- ooh. But the PhoneEditor has dependencies too. It's going to need a widget and it too needs its hands on the RPC service. But we're not really using the actual RWT generated
RPC service. We have our wrapping thing around that. So let's instantiate our CachedBatching rpcService, into which we will jam the one that we GWT.created to do the real work.
And then finally, everybody seems to want this Event Bus thing, So we'll instantiate one of those. So that's kind of a lot of code, but it wasn't really hard to look at.

 
Part of the payoff starts to come when new dependencies appear. Marketing has heard about this Google Voice service, and they're absolutely convinced that we should have Google Voice integration with the PhoneEditor. So we're going to add that by instantiating the voice service. We're going to change the PhoneEditor constructor. And nobody else knows about that change. Even though the PhoneEditor is fairly far down in our tiny little stack, the thing that needs a PhoneEditor because it never instantiated it itself doesn't need to know this PhoneEditor now needs the VoiceService. And I didn't have to add a VoiceService argument and a VoiceService argument and a VoiceService argument at every level in my code. But still, you can see that this is going to grow into a lot of kind of grungy code to maintain, and a big list of instantiation to do all at the top.
If you were to use a framework to do it, something like GIN-- talk-- Actually, there's no talk about GIN itself today. I don't want to imply that. But there is a talk about Guice. And GIN is just like Guice, but on the client side. Anyway, if you use GIN correctly, and using it correctly is easier than using it incorrectly,



the code winds up looking like this.
So I highly recommend it.








No comments:

Post a Comment