gwt app architecture best practices
DESCRIPTION
Google Web Toolkit provides the infrastructure you need to build a high performance web application and leaves the architecture open to fit your needs. Learn from others who have gone before. In this session we'll discuss best practices that real web applications are using to achieve high performance event handling, UI creation, and more.Ray RyanWatch a video at http://www.bestechvideos.com/2009/06/29/google-i-o-2009-best-practices-for-architecting-gwt-appTRANSCRIPT
![Page 1: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/1.jpg)
![Page 2: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/2.jpg)
Ray Ryan28 May 2009
GWT App ArchitectureBest Practices
![Page 3: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/3.jpg)
• How to organize a nontrivial GWT application
• Particular focus on client side
• Lessons learned from new AdWords UI
What are we talking about?
![Page 4: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/4.jpg)
If you remember nothing else...
![Page 5: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/5.jpg)
• Get browser history right, and get it right early
If you remember nothing else...
![Page 6: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/6.jpg)
• Get browser history right, and get it right early
If you remember nothing else...
![Page 7: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/7.jpg)
• Get browser history right, and get it right early
• Use an Event Bus to fight spaghetti
If you remember nothing else...
![Page 8: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/8.jpg)
• Get browser history right, and get it right early
• Use an Event Bus to fight spaghetti
If you remember nothing else...
![Page 9: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/9.jpg)
• Get browser history right, and get it right early
• Use an Event Bus to fight spaghetti
• DI + MVP FTW
If you remember nothing else...
![Page 10: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/10.jpg)
• Get browser history right, and get it right early
• Use an Event Bus to fight spaghetti
• DI + MVP FTW
If you remember nothing else...
Dependency Injection plusModel / View / Presenterfor the win!
![Page 11: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/11.jpg)
![Page 12: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/12.jpg)
Demo new AdWords UI
![Page 13: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/13.jpg)
1.Embrace asynchrony
2.Always be decoupling
3.Strive to achieve statelessness
Three major themes
![Page 14: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/14.jpg)
![Page 15: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/15.jpg)
Embrace Asynchrony
![Page 16: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/16.jpg)
• Everything might require an async call sometimes
• So assume it does all the time
Remember what that A in AJAX is for
![Page 17: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/17.jpg)
• Everything might require an async call sometimes
• So assume it does all the time
Remember what that A in AJAX is for
class Contact { String name; List<ContactDetail> details; List<ContactDetail> getContactDetails() { return details; }
String getName() { return name; }}
![Page 18: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/18.jpg)
• Everything might require an async call sometimes
• So assume it does all the time
Remember what that A in AJAX is for
class Contact { String name; List<ContactDetail> details; List<ContactDetail> getContactDetails() { return details; }
String getName() { return name; }}
X
![Page 19: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/19.jpg)
class Contact { String name; ArrayList<ContactDetailId> detailIds;
ArrayList<ContactDetailId> getDetailIds() { return detailIds; }
String getName() { return name; }}
• Everything might require an async call sometimes
• So assume it does all the time
Remember what that A in AJAX is for
✓
![Page 20: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/20.jpg)
• Leverage point for
• Caching
• Batching
• Centralize failure handling • Lays the groundwork for
• GWT.runAsync()
• Undo / Redo
• Gears / HTML5 DB
Command Pattern to make async tolerable
![Page 21: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/21.jpg)
Use Command pattern RPC
/** The name Command is taken */interface Action<T extends Response> { }
interface Response { }
interface ContactsService extends RemoteService { <T extends Response> T execute(Action<T> action);} interface ContactsServiceAsync { <T extends Response> void execute(Action<T> action, AsyncCallback<T> callback);}
![Page 22: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/22.jpg)
Command style RPC exampleWrite an Action…
class GetDetails implements Action<GetDetailsResponse> { private final ArrayList<ContactDetailId> ids;
public GetDetails(ArrayList<ContactDetailId> ids) { this.ids = ids; }
public ArrayList<ContactDetailId> getIds() { return ids; }}
![Page 23: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/23.jpg)
Command style RPC example…and its response…
class GetDetailsResponse implements Response { private final ArrayList<ContactDetail> details;
public GetDetailsResponse(ArrayList<ContactDetail> details) { this.details = details; }
public ArrayList<ContactDetail> getDetails() { return new ArrayList<ContactDetail>(details); }}
![Page 24: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/24.jpg)
Command style RPC example…plus convenience callback
abstract class GotDetails implements AsyncCallback<GetDetailsResponse> {
public void onFailure(Throwable oops) { /* default appwide failure handling */ }
public void onSuccess(GetDetailsResponse result) { got(result.getDetails()); }
public abstract void got(ArrayList<ContactDetail> details);}
![Page 25: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/25.jpg)
Command style RPC exampleMake it go
void showContact(final Contact contact) { service.execute(new GetDetails(contact.getDetailIds()), new GotDetails() { public void got(ArrayList<ContactDetail> details) { renderContact(contact); renderDetails(details); } });}
![Page 26: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/26.jpg)
![Page 27: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/27.jpg)
Always be decoupling
![Page 28: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/28.jpg)
• An event bus
• MVP pattern for your custom widgets
• Dependency injection of app-wide services
Always be decouplingWith the combination of
![Page 29: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/29.jpg)
• An event bus
• MVP pattern for your custom widgets
• Dependency injection of app-wide services
Always be decouplingWith the combination of
Easy rejiggering of the app
You get...
![Page 30: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/30.jpg)
• An event bus
• MVP pattern for your custom widgets
• Dependency injection of app-wide services
Always be decoupling
Easy to defer pokey DOM operations
With the combination of
Easy rejiggering of the app
You get...
![Page 31: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/31.jpg)
• An event bus
• MVP pattern for your custom widgets
• Dependency injection of app-wide services
Always be decoupling
Easy to defer pokey DOM operations
Easy unit testing
With the combination of
Easy rejiggering of the app
You get...
![Page 32: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/32.jpg)
• An event bus
• MVP pattern for your custom widgets
• Dependency injection of app-wide services
Always be decoupling
Easy to defer pokey DOM operations
Fast test execution
Easy unit testing
With the combination of
Easy rejiggering of the app
You get...
![Page 33: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/33.jpg)
![Page 34: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/34.jpg)
Decoupling via event bus
![Page 35: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/35.jpg)
Coupling
![Page 36: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/36.jpg)
Coupling
![Page 37: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/37.jpg)
Coupling
![Page 38: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/38.jpg)
Coupling
![Page 39: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/39.jpg)
Coupling
![Page 40: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/40.jpg)
Coupled
![Page 41: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/41.jpg)
Looser
![Page 42: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/42.jpg)
Loose
![Page 43: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/43.jpg)
EventBusImplement with GWT HandlerManager
void showContact(final Contact contact) { service.execute(new GetDetails(contact.getDetailIds()), new GotDetails() { public void got(ArrayList<ContactDetail> details) { renderContact(contact); renderDetails(details); } } });}
![Page 44: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/44.jpg)
ContactId currentContact;
void showContact(final Contact contact) { if (!currentContactId.equals(contact)) { currentContactId = currentContactId; service.execute(new GetDetails(contact.getDetailIds()), new GotDetails() { public void got(ArrayList<ContactDetail> details) { renderContact(contact); renderDetails(details); } }); } }
EventBusImplement with GWT HandlerManager
![Page 45: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/45.jpg)
EventBus
HandlerManager eventBus; void listenForContactUpdates() { eventBus.addHandler(ContactChangeEvent.TYPE, new ContactChangeEventHandler() { public void onContactChange(ContactChangeEvent event) { Contact contact = event.getContact(); if (currentContactId.equals(contact.getId())) { renderContact(contact); } } });}
Implement with GWT HandlerManager
![Page 46: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/46.jpg)
EventBus
public void execute(final UpdateContact update, final AsyncCallback<GetContactsResponse> cb) { realService.execute(update, new AsyncCallback<UpdateContactResponse>() { public void onFailure(Throwable caught) { cb.onFailure(caught); } public void onSuccess(UpdateContactResponse result) { recache(update.getContact()); cb.onSuccess(result); ContactChangeEvent e = new ContactChangeEvent(update.getContact()); eventBus.fireEvent(e); } });}
Tossing the event
![Page 47: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/47.jpg)
EventBus
public void execute(final UpdateContact update, final AsyncCallback<GetContactsResponse> cb) { realService.execute(update, new AsyncCallback<UpdateContactResponse>() { public void onFailure(Throwable caught) { cb.onFailure(caught); } public void onSuccess(UpdateContactResponse result) { recache(update.getContact()); cb.onSuccess(result); ContactChangeEvent e = new ContactChangeEvent(update.getContact()); eventBus.fireEvent(e); } });}
Tossing the event
![Page 48: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/48.jpg)
![Page 49: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/49.jpg)
Decoupling via MVP
![Page 50: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/50.jpg)
Classic Model View Controller pattern
![Page 51: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/51.jpg)
Classic Model View Controller patternHow 1980s
![Page 52: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/52.jpg)
Classic Model View Controller patternHow 1980s
public domain image http://vintageprintable.com/wordpress/wp-content/uploads/2009/04/deirochelys_reticulariaholbrookv1p07a.jpg
![Page 53: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/53.jpg)
MVP
public domain image http://vintageprintable.com/wordpress/wp-content/uploads/2009/04/deirochelys_reticulariaholbrookv1p07a.jpg
![Page 54: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/54.jpg)
MVP
GWTMockUtilities
public domain image by http://www.freeclipartnow.com/animals/rabbits/Brown-Hare.jpg.html
![Page 55: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/55.jpg)
MVP
![Page 56: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/56.jpg)
MVP
class Phone implements ContactDetail { final ContactDetailId id; String number; String label; // work, home...}
![Page 57: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/57.jpg)
MVP
class Phone implements ContactDetail { final ContactDetailId id; String number; String label; // work, home...}
![Page 58: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/58.jpg)
MVP
class Phone implements ContactDetail { final ContactDetailId id; String number; String label; // work, home...}
class PhoneEditor { interface Display { HasClickHandlers getSaveButton(); ...
![Page 59: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/59.jpg)
Sample Presenter: PhoneEditor
class PhoneEditor { interface Display { HasClickHandlers getSaveButton(); HasClickHandlers getCancelButton(); HasValue<String> getNumberField(); HasValue<String> getLabelPicker(); ...}
Display interface using characteristic interfaces
![Page 60: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/60.jpg)
Sample Presenter: PhoneEditor
void bindDisplay(Display display) { this.display = display;
display.getSaveButton().addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { doSave(); } });
display.getCancelButton().addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { doCancel(); } });}
Binding the display
![Page 61: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/61.jpg)
Sample Presenter: PhoneEditor
void editPhone(Phone phone) { this.phone = Phone.from(phone);
display.getNumberField().setValue(phone.getNumber()); display.getLabelPicker().setValue(phone.getLabel());}
Start editing
![Page 62: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/62.jpg)
Sample Presenter: PhoneEditor
void doSave() { phone.setNumber(display.getNumberField().getValue()); phone.setLabel(display.getLabelPicker().getValue());
service.execute(new UpdatePhone(phone), new UpdatedPhone() { public void updated() { tearDown(); }
public void hadErrors(HashSet<PhoneError> errors) { renderErrors(errors); } });}
Save it
![Page 63: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/63.jpg)
![Page 64: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/64.jpg)
Decoupling via DI
![Page 65: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/65.jpg)
Dependency Injection
• Just a pattern:
o No globals
o No service locator
o Dependencies pushed in, preferably via constructor
• Not hard to do manually
• GIN (client) and Guice (server) can automate it
• http://code.google.com/p/google-guice/
• http://code.google.com/p/google-gin/
![Page 66: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/66.jpg)
DI slice for the PhoneEditor
public void onModuleLoad() { ContactsServiceAsync realService = GWT.create(ContactsServiceAsync.class); CachedBatchingService rpcService = new CachedBatchingService(realService); HandlerManager eventBus = new HandlerManager(null);
PhoneEditWidget phoneEditWidget = new PhoneEditWidget(); PhoneEditor phoneEditor = new PhoneEditor(phoneEditWidget, rpcService);
ContactWidget contactWidget = new ContactWidget(); ContactViewer contactViewer = new ContactViewer(contactWidget, phoneEditor, rpcService, eventBus); contactViewer.go(RootPanel.get());
![Page 67: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/67.jpg)
DI slice for the PhoneEditor
public void onModuleLoad() { ContactsServiceAsync realService = GWT.create(ContactsServiceAsync.class); CachedBatchingService rpcService = new CachedBatchingService(realService); HandlerManager eventBus = new HandlerManager(null);
PhoneEditWidget phoneEditWidget = new PhoneEditWidget(); PhoneEditor phoneEditor = new PhoneEditor(phoneEditWidget, rpcService);
ContactWidget contactWidget = new ContactWidget(); ContactViewer contactViewer = new ContactViewer(contactWidget, phoneEditor, rpcService, eventBus); contactViewer.go(RootPanel.get());
![Page 68: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/68.jpg)
DI slice for the PhoneEditor
public void onModuleLoad() { ContactsServiceAsync realService = GWT.create(ContactsServiceAsync.class); CachedBatchingService rpcService = new CachedBatchingService(realService); HandlerManager eventBus = new HandlerManager(null);
PhoneEditWidget phoneEditWidget = new PhoneEditWidget(); PhoneEditor phoneEditor = new PhoneEditor(phoneEditWidget, rpcService);
ContactWidget contactWidget = new ContactWidget(); ContactViewer contactViewer = new ContactViewer(contactWidget, phoneEditor, rpcService, eventBus); contactViewer.go(RootPanel.get());
![Page 69: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/69.jpg)
DI slice for the PhoneEditor
public void onModuleLoad() { ContactsServiceAsync realService = GWT.create(ContactsServiceAsync.class); CachedBatchingService rpcService = new CachedBatchingService(realService); HandlerManager eventBus = new HandlerManager(null);
PhoneEditWidget phoneEditWidget = new PhoneEditWidget(); PhoneEditor phoneEditor = new PhoneEditor(phoneEditWidget, rpcService);
ContactWidget contactWidget = new ContactWidget(); ContactViewer contactViewer = new ContactViewer(contactWidget, phoneEditor, rpcService, eventBus); contactViewer.go(RootPanel.get());
![Page 70: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/70.jpg)
DI slice for the PhoneEditor
public void onModuleLoad() { ContactsServiceAsync realService = GWT.create(ContactsServiceAsync.class); CachedBatchingService rpcService = new CachedBatchingService(realService); HandlerManager eventBus = new HandlerManager(null);
PhoneEditWidget phoneEditWidget = new PhoneEditWidget(); PhoneEditor phoneEditor = new PhoneEditor(phoneEditWidget, rpcService);
ContactWidget contactWidget = new ContactWidget(); ContactViewer contactViewer = new ContactViewer(contactWidget, phoneEditor, rpcService, eventBus); contactViewer.go(RootPanel.get());
![Page 71: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/71.jpg)
DI slice for the PhoneEditor
public void onModuleLoad() { ContactsServiceAsync realService = GWT.create(ContactsServiceAsync.class); CachedBatchingService rpcService = new CachedBatchingService(realService); HandlerManager eventBus = new HandlerManager(null);
PhoneEditWidget phoneEditWidget = new PhoneEditWidget(); PhoneEditor phoneEditor = new PhoneEditor(phoneEditWidget, rpcService);
ContactWidget contactWidget = new ContactWidget(); ContactViewer contactViewer = new ContactViewer(contactWidget, phoneEditor, rpcService, eventBus); contactViewer.go(RootPanel.get());
![Page 72: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/72.jpg)
DI slice for the PhoneEditor
public void onModuleLoad() { ContactsServiceAsync realService = GWT.create(ContactsServiceAsync.class); CachedBatchingService rpcService = new CachedBatchingService(realService); HandlerManager eventBus = new HandlerManager(null);
PhoneEditWidget phoneEditWidget = new PhoneEditWidget(); PhoneEditor phoneEditor = new PhoneEditor(phoneEditWidget, rpcService);
ContactWidget contactWidget = new ContactWidget(); ContactViewer contactViewer = new ContactViewer(contactWidget, phoneEditor, rpcService, eventBus); contactViewer.go(RootPanel.get());
![Page 73: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/73.jpg)
DI slice for the PhoneEditor
public void onModuleLoad() { ContactsServiceAsync realService = GWT.create(ContactsServiceAsync.class); CachedBatchingService rpcService = new CachedBatchingService(realService); HandlerManager eventBus = new HandlerManager(null);
PhoneEditWidget phoneEditWidget = new PhoneEditWidget(); PhoneEditor phoneEditor = new PhoneEditor(phoneEditWidget, rpcService);
ContactWidget contactWidget = new ContactWidget(); ContactViewer contactViewer = new ContactViewer(contactWidget, phoneEditor, rpcService, eventBus); contactViewer.go(RootPanel.get());
![Page 74: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/74.jpg)
DI slice for the PhoneEditor
public void onModuleLoad() { ContactsServiceAsync realService = GWT.create(ContactsServiceAsync.class); CachedBatchingService rpcService = new CachedBatchingService(realService); GVoiceService voiceService = GWT.create(GVoiceService.class); HandlerManager eventBus = new HandlerManager(null);
PhoneEditWidget phoneEditWidget = new PhoneEditWidget(); PhoneEditor phoneEditor = new PhoneEditor(phoneEditWidget, rpcService, voiceService);
ContactWidget contactWidget = new ContactWidget(); ContactViewer contactViewer = new ContactViewer(contactWidget, phoneEditor, rpcService, eventBus); contactViewer.go(RootPanel.get());
![Page 75: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/75.jpg)
DI slice for the PhoneEditor
public void onModuleLoad() { MyGinjector factory = GWT.create(MyGinjector.class);
factory.createContactViewer().go(RootPanel.get());}
![Page 76: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/76.jpg)
![Page 77: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/77.jpg)
Decoupling payoff: fast tests
![Page 78: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/78.jpg)
Test the PhoneEditor
class MockContactsService implements ContactsServiceAsync { Action<?> lastAction; AsyncCallback<?> lastCallback;
public <T extends Response> void execute(Action<T> action, AsyncCallback<T> callback) { lastAction = action; lastCallback = callback; }}
Define some mocks
![Page 79: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/79.jpg)
Test the PhoneEditorDefine some mocks
class MockClickEvent extends ClickEvent { }
class MockHasClickHandlers implements HasClickHandlers { ClickHandler lastClickHandler;
public HandlerRegistration addClickHandler( ClickHandler handler) { lastClickHandler = handler;
return new HandlerRegistration() { public void removeHandler() { } }; } public void fireEvent(GwtEvent<?> event) { }}
![Page 80: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/80.jpg)
Test the PhoneEditorDefine some mocks
class MockHasValue<T> implements HasValue<T> { T lastValue;
public T getValue() { return lastValue; }
public void setValue(T value) { this.lastValue = value; }
public void setValue(T value, boolean fireEvents) { setValue(value); } ...
![Page 81: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/81.jpg)
Test the PhoneEditorDefine some mocks
class MockPhoneEditorDisplay implements PhoneEditor.Display { MockHasClickHandlers save = new MockHasClickHandlers(); HasClickHandlers cancel = new MockHasClickHandlers(); HasValue<String> labelPicker = new MockHasValue<String>(); HasValue<String> numberField = new MockHasValue<String>();
public HasClickHandlers getCancelButton() { return cancel; } public HasClickHandlers getSaveButton() { return save; } public HasValue<String> getLabelPicker() { return labelPicker; } public HasValue<String> getNumberField() { return numberField; }}
![Page 82: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/82.jpg)
Test the PhoneEditorSet up the test...
public void testSave() { MockContactsService service = new MockContactsService(); MockPhoneEditorDisplay display = new MockPhoneEditorDisplay(); // Build before and after values ContactDetailId id = new ContactDetailId();
Phone before = new Phone(id); before.setLabel("Home"); before.setNumber("123 456 7890");
Phone expected = Phone.from(before); expected.setLabel("Work"); expected.setNumber("098 765 4321");
![Page 83: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/83.jpg)
Test the PhoneEditor...and run it
PhoneEditor editor = new PhoneEditor(display, service); editor.editPhone(before); display.labelPicker.setValue("Work"); display.numberField.setValue("098 765 4321"); display.save.lastClickHandler.onClick(new MockClickEvent()); // Verify UpdatePhone action = (UpdatePhone) service.lastAction; assertEquals(new UpdatePhone(expected), action); Phone actual = action.getPhone(); assertEquals(expected.getLabel(), actual.getLabel()); assertEquals(expected.getNumber(), actual.getNumber());}
![Page 84: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/84.jpg)
![Page 85: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/85.jpg)
Strive to achieve statelessness
![Page 86: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/86.jpg)
• The browser embodies the session
• Server effectively stateless (except for caching)
• User should not notice server restart
• On AppEngine, MemCache works well with this attitude
o "Values can expire from the memcache at any time"
Disposable servers
![Page 87: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/87.jpg)
• Use GWT History right, and get it right early
• Back button, refresh as a feature, not a catastrophe
• Use Place abstraction
o Layer above Historyo Announce place change via event bus
Disposable clients
![Page 88: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/88.jpg)
Recall
![Page 89: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/89.jpg)
+Place
![Page 90: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/90.jpg)
![Page 91: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/91.jpg)
Q & A
![Page 92: GWT App Architecture Best Practices](https://reader034.vdocuments.mx/reader034/viewer/2022051411/546a0ab2af79593b558b4f4a/html5/thumbnails/92.jpg)