2009-10-29

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

The source is
http://www.youtube.com/watch?v=PDuhR18-EdM









Now why are we doing all of this stuff? What's the punch line?
Besides having the app isolated from itself for that purposes, we're particularly happy because this gives us easy to write and fast tests. So to write a test in this style, we need our mock objects. We're not using EasyMock here, so we're just implementing these things ourselves. We're going to make stuff that looks like this. I'm going to have a mock version of my ContactsService. And it's going to have these fields here, the lastAction and the lastCallback, and I'm going to fill those at obvious times. When somebody gives me a pretend execute this object, execute this action call, I'll hold on to it so that my test code can take a peek. Throughout the code examples here, I've been leaving off things On this slide, that's not pretend. I probably would have package access I probably would have package access
or even public access to the fields on this mock object because there's not a lot to be gained. By having accessor discipline, I wind up wuth more concise code


We're going to need a few more mock objects as we're working through here. So I'm goung to have to have a nice fake version of that ClickEvent. Because for reasons that I don't remember, we decided you shouldn't be able to do new ClickEvent. But let's not worry about that. We have a mock version of the thing that has ClickHandlers.
This is our pretend button. It's got the same notion of the last ClickHandler that was added to me. I'll hold on to it to that people can find it. When I'm told to... Does this do anything else interesting? Nope. Yeah, in fact, the fireEvent, it doesn't even do anything. Ilt's a no-op.
Our tests don't tend to do that.



Similarly, we had our HasValue thing. We've got a mock version of that that'll hold on to the last value that was set into it. It also, because I have access to... Never mind. That's not a point worth making.





And then finally, we'll put together a bigger, more interesting composite mock object, the thing that implements our PhoneEditorDisplay. So where we would have needed  phone editing widget, remember that our PhoneEditor presenter wants a phone edit widget.
We gave it a nice instance of MockPhoneEditorDisplay, which is made up of MockHasClickHandlers and some MockHasValues. Oops. Oh, well. Some fake accessor methods for those, and again, nice visible fields so that the test can play with it. So that's a lot of work
that we had to do for what's going to be a test for three lines of code.
On the other hand, the three lines of code will probably be more interesting in real life when I need to test, say, "Did I warn the user that they're about to abandon their changes when they navigate away from this page? And when I have tests for the cancel path and for the save path, all of these guys are eminently reusable. In fact you might be asking yourself, "Why doesn't the GWT team provide me "with a prerolled MockHasValues and MockClickHandlers?" And yeah, we should probably do that.



So on to the test itself. We're going to put together our mock world. We've got our fake service. We've got our fake display. We're creating our fake phone object that we wanted to be editing. And we're setting-actually,
it's a real phone object that we wanted to be editing.
And we're setting up another one for how we expect the "after" phone object to look that we're going to send across the wire for comparison purposes.


So on to the test itself. We're going to put together our mock world. We've got our fake service. We've got our fake display. We're creating our fake phone object that we wanted to be editing. And we're setting-actually,
it's a real phone object that we wanted to be editing.
And we're setting up another one for how we expect the "after" phone object to look that we're going to send across the wire for comparison purposes. We're going to instantiate the PhoneEditor, the thing that we actually want to test here. We're going to tell it.
Go edit this phone number,"just like we would tell it in our real code. And then we're going to sneak into our mock objects. We're going to tell the labelPicker, "Okey, the user typed "Work". And we're going to tell our number field, "This is the phone number
she provided." And then we're going to tell the fake save button, "Do a click for me now, okay?" That should have been enough to kick off our presenter object, the PhoneEditor. And now we're going to verify all of that. We are going to sneak into our service.
We're going to see what action it actually requested. We're going to make sure that it was an instance of Update Phone, based on this expected...
Oh. That'll work, actually. It should've been UpdatePhone before,  but they're equivalent because they have the same contactId is the idea. And I did run this test. It compiles and passes. Buh-buh-buh-buh-buh.
And then we get out of the action. They phone object that was actually sent across the wire, we compare the interesting bits of that with what we expected to see, and our test works.






Don't do that anymore. You've got a browser now. You're an AJAX developer. The browser provides all the server state, all the server session you actually need. So that means that when it comes time for rolling updates and servers are falling over and new ones are taking their place, or it means that at your desk, when you need to restart you server because you rejiggered some of the business logic code, what's going on in your browser doesn't even need to notice that that happened. Maybe some calls will be a little bit slower because the cache fell off. But otherwise you're fine.
If you're developing an App Engine app, you've got the Memcache package that's available for you which works really, really well for this kind of thing. So we've got disposable servers.  We similarly want to have disposable clients. I'm afraid I'm going to gloss over this more than it deserves, but the punch line here is, as I said at the beginning, you want to get your history right. As your user is doing things that feel like navigating around your app-- you remember in the AdWords demo, I was clicking around the tree and clicking on-- navigating
into different parts of the app Each one of those places in your app wants to be embodied by a GWT History token, so that as the user hits that back button, she'll return to where she was. If she hits the forth button, she goes on. If she keeps a bookmark, that bookmark actually works, and she returns to the space here she was in the app. If you've done that right, you've got a disposable client. When the user hits F5, or you copy a URL from one browser to another, it just works. Your development life is happier. Your users are happier. That said, the History library is in the traditional GWT way.  It is a bit low-level. Making every bit of your app knowing about History tokens, and knowing to listen for OnHistory token change, and to try to push History tokens if they want to change is sort of nasty. And you want to come up with some kind of an abstraction to isolate the bits of your app from that kind of knowledge. What we did in AdWords was we came up with a package called Place. It's not open sourced. It wound up being very ad-specific. But the pattern itself is pretty straightforward. It pretty much winds up being, just like I have command objects, action objects to embody operations, I've got place objects to embody addresses within my app. They're kind of incarnate bookmarks and each place object knows how to talk to the History service, to push tokens, and read tokens and that kind of a thing. And it's all integrated into the Event Bus. So you remember this picture, where all of my bits of my app are talking to each other.
To introduce place management into this, I'll create something called the place service, which is the only part of the app that's actually responsible for knowing about the GWT History library. So when a bookmark is pasted in, when the back button is hit, when the forth button is hit, place service notices about that. And it announces to the rest of the app, "The user has changed places. Here's the new place that they're going." Maybe it represents a text ad in AdWords. And the bits of the app that need to swap themselves in or swap themselves out
can listen for those more semantic objects, the place changes, and go on their merry way. I've actually run out of things to talk about. Like I said, it always ran long before

=====================================

if I have multiple tables and I have an object that's interested in events from one of the tables and you want to use this Event Bus approach to communicate that, how do I know which table it is without being directly connected to them? Is there-have you guys solved that problem? Without knowing the object itself. Loosely coupling.

Ryan: Yeah. I'm not following the question.

man: Maybe it's too complex.

Ryan: If you want to come up afterward, we can hash it out.

man: I just thought of-- the other question I had-- So I went through the whole property change listeners mess. And I'm looking forward to getting rid of them. But how do you deal with efficiency? Like, now you get an event that this object changed. And you have to update the UI to reflect it. But you don't know exactly-- if you knew, like, this one field changed, you can just go and change one field or move the corner over. But now all you know is something changed. And how do you get around...

Ryan: Well, I know this... I know the something that it is that is actually interesting to me. And you've got more efficiency now, because instead of havings this one-to-one mapping of everything that wants to listen to something and what they're listening to, I've got a lot fewer instances of handler. The execution speed, I don't think, has turned out to be such a big deal. Especially if I'm good about only listening while it makes sense for me to listen. So if I'm the tree of contacts, just, you know, then I'm displaying all of them, well, every single contact event is going to be of interest to me. If I'm the editing UI that's only displaying a particular contact at the moment, it's very unlikely, that some other contact changed, because I'm only one application. I'm not, like, some kind of server handling all of the stuff

man: And what if there is... Our app has, a lot of times, more than one view on the same object in different ways.

Ryan: But you're still-- you're usually not going to have hundreds of views, right? There have-- two or three views, and those two or three views will be listening and maybe will hear some irrelevant stuff. I think that that'll all wash out.

man: All right. Thanks. -

Ryan: Hi.

man: Yeah, my question is, you talked about the importance of being able to bookmark or refresh pages. So going with contact example, you know, it's not just, you know, good enough that I'm on, you know, hit the back button and it knows I was at the contact page. You know, I want to hit the back button and know I was viewing, you know John Doe And then I want to hit the back button again and know, you know, it was Jane Smith. So, you know, I'm kind of wondering, like, in the HTML or in the Web 1.0 or JSP world, you would pass URL parameters. You know, with a question mark Id.

Ryan: That's pretty much how the History library's implemented. Except everything winds up after the hash mark rather than after the question mark. So what you wind up doing is I've got a subclass of place per type of object that I want to be-- that my location is relevant to. So in the AdWords case, we wind up with campaign places, ad group places, and stuff like that. These place objects know enough about the History management system to provide a History token string that includes the ad group Id and the campaign Id. And so boom, it's right back there in the URL

man: And so then does the place interact with the Event Bus, so when it notices...

Ryan: Yes.

man: That a new Id was passed, it triggers an event?

Ryan: Exactly.

man: And notifies pages?

Ryan: So we can go back to our pretty picture here. So the user hits the back button or the forth button. The History event hits the place service, which is where the code that actually implements the History client interface lives. The place service instantiates a new little place object, which...let's see. The place service might actually talk to your RPC service to, you know, say, "Get me this contact" that is relevant to the URL that I just had." And then it will announce to the world, "Hi, everybody, we're at a new place. And this is the contact object that I found at that place." Nobody else has to know about the fact that it happened to live on the URL or whatever else went on.

man: And then, you know, around you Event Bus, how-- I'm just kind of interested in how many different things that subscribe to a single event, like, you know-- let's say I have a tiered navigation, you know, maybe. Or I have top navigation and side navigation. So kind of pages within pages with, you know-- or in terms of GWT, like a panel within a panel. You know, maybe I have, you know, bunch of tabs at the top. And I have a contact tab and I have tabs on the left. I mean, does- do you try and just subscribe to the event in one place and then top down drive, like, "Hey, this was changed," or do you have multiple places--

Ryan: No, it.. People did start talking directly to the Event Bus all of the time for everything. And that was kind of retrograde. If I've just got-- It's like. in the PhoneEditor example. The PhoneEditor... Or in the dependency injection example. We created our contact display and we gave it an instance of PhoneEditor display. PhoneEditor didn't have to know about navigation changes within the app because it had a really tight relationship whith the contact display object that needs to know how to edit phone numbers. There's no reason for that to go crazy with it there. So I don't have a pat answer for you. But I think it's the... the top-level bits of the app that need to appear and disappear are probably the ones listening to, for example, place changes. Maybe other types of events-- you know, contact editing or whatever, maybe there you do want to be a bit finer grained. But you kind of... It becomes a feel thing. I don't really have a simple answer.

man: So have you guys thought about just taking a simple app like this and putting it out there as an example?

Ryan: Yeah, absolutely.

man: 'Cause, I mean, you know, what's out there, I think it was the sandbox or the--

Ryan: Yeah, um--

man: It's nice, but it's not really real-world scenarious.

Ryan: I have been reliably informed that I will be doing that.

man: Well, thanks. Appreciate it. [audience chuckles]

man: Hi. Quick question. I started recently using GWT, and I really like it. My question is more about the History button in general, since you've talked about different design paradigms and patterns. Is History button really, in your view, still a useful thing? Or not History button. The back button, I'm sorry, I meant.

Ryan: Uh, sure.

man: Because now we're talking more and more rich applications. You know, fancy applications. More like desktop applications. You really--you know, once you add-- you may want let them delete it, but not necessarily delete by going back, for example. So--

Ryan: I wouldn't-- You don't want to let them delete by going back. But...especially if you're doing an application like this, where at the end of the day, it's a big bag of objects and I want to navigate around the space that that big bag of objects defines, your users are used to clicking on the thing and then hitting the back button to get back to the place that they were before. I'm certainly not suggesting that you encourage-- that the back button becomes your undo mechanism. I think you'll go insane. But you can't stop your user from hitting that back button. And it's a pretty nice way to work your way through a space.

man: What we did in our app is basically we would stop having the back button at all. So as a result, user get used to using in a different way. More like they're used to using a rich application.

Ryan: I've never had the luxury of working in an app where I was allowed to take the back button away. So..but I wouldn't want to. I mean, it's... There's a reason that the Web browsers got shaped the way they did-- it feels really good. If you're -- I mean -- not every app is this kind of an app. I don't know if the back button really makes sense for Lombardi, where they're, you know, doing more-- I'm doing a big diagramming thing. But for an app like this, I think it's pretty natural. And it's not something to hide from.

man: Thank you.

man: When you build the UI in GWT, it's very developer-centric, usualy. It's basically Swing. It feels like that when I'm building a UI. But usually, UI designers want to make it with mark-up. So there was some talk a while ago about a declarative UI. I may have missed it if that came out. But what do you use for AdWords and are there--

Ryan: We use declarative UI.

man: Yeah?

Ryan: The code's there. It exists. It's called UiBinder now. Its documentation is all public, to make it painfully obvious that we haven't actually released the code itself. We are trying to actually get it out there, and it's just been a question of too few engineers being pulled in too many dimensions. But yeah, UiBinder is used by AdWords. Wave uses it. And getting those guys to ship has kept managing to trump actually open sourcing the thing. It will go out. There's also, in AdWords, there's some code that we rolled to try to-- I implied a lot of Glue there. Of...here's my phone object, here's my string display, copy the string to here, copy the string back, and, you know, that kind of stuff. We managed to generate some amount of that code in AdWords. We weren't super thrilled-- or I wasn't super thrilled with what we came up with. But we've got notions there too of frameworks we'd like to provide to kind of minimize that need. But I don't have particular promises or particular dates to say. We're aware of it as an issue. And we'll get there.

man: Hi Thank you for your talk.

Ryan: Thank you.

man: So the application that we work on, we've done something similar to this with something that roughly translates to the Event Bus. But we don't have all the other constituent pieces talking to the services. Instead, they-- there's, like, this back and forth propagation of events. And can you comment on why that would be--

Ryan: If it's working for you, I wouldn't tell you to change it just for the sake of changing. We..this worked nicely for us because it... I think we have no more events than we actually need traveling around the place, and... Again, you know, for the testing and for configuration purposes, the contact list can stand on its own without having to know who, actually it will be calling into display contacts and that kind of a thing. If you don't have a problem, ther's no reason for you to find a solution. So...

man: All right, thank you.

Ryan: Yeah. Hi.

man: Hi. One of the classic patterns that people use while writing UI code, especially in JavaScript libraries like YUI and other things like that, is having data objects i.e. beings, that they can then take into a widget that is able to take that data object and using a reflection-like mechanism of one sort or another display things into the widget. Manipulate the data, potentially even edit it then spew it back out. There's nothing like that today in GWT, and I'm suspecting very strongly that you guys internally at Google have some way of dealing with this for the large apps that you have. And what are your plans in the future with respect to this?

Ryan: We do have kind of part of that in AdWords. Other teams... AdWords is the... maybe the only app, ore one of the only apps at Google, that's actually this kind of classical, J2EE-style app. And so we don't tend to have broad solutions for those types of things. We have the solution that AdWords rolled for their particular needs. You've probably seen some borderline flame fests on the GWT contributors list about data binding and the need for doing something about this. The approach that we have taken is where you would have used reflection, we will decorate bits of our Ul with annotations, with the name of the particular property - of the model object that that part of the UI wants to display, and we can generate the code that would have been reflection-based for-this text field is tied to the name property. Oh, this thing looks like a JavaBean and it has a GetName method. I'll GetName, I'll call SetName at appropriate times. We want to make that better to generate more of that code than we're generating right now. And there's also similar problems that are coming up on the server side with AppEngine and the ORM objects that you're probably persisting through JDO. A lot of people have found that they've created these crafty little objects that get all nice and persistent for them on the server side, and then they try to serialize them for GWT RPC, and that doesn't work, and how do I get around that without making separate copies and calling set and get and get and set back and forth? I think there's going to be a similar solution to that, but we don't-- We haven't quite come upon it yet. But we are thinking about it actively. I'm sorry, Fred, I was just told So thanks, everybody.







34 comments:

  1. 目標是什麼不重要,目標能產生什麼樣的效果才重要........................................

    ReplyDelete
  2. 金銀愈加磨鍊,愈加光亮,人生愈加考驗,生命愈加光輝。 ............................................................

    ReplyDelete
  3. 來問個安,誰不支持這個部落格,我咬他. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

    ReplyDelete
  4. 向著星球長驅直進的人,反比踟躕在峽路上的人,更容易達到目的。............................................................

    ReplyDelete
  5. 旁觀自己的悲傷是解脫,主觀自己的悲傷是更加悲傷................................................

    ReplyDelete
  6. 我愛那些使自己的德行成為自己的目標或命定的人................................................

    ReplyDelete