Sencha har sagt at de for neste versjon av ExtGWT kommer til å nærme seg standardene som Google bruker for GWT. Tidligere har man i ExtGWT måtte gjøre en del spesialtilpasninger for å få dataflyt til å fungere optimalt. Vi har forsøkt å foreberede kodebasen vår i påvente av ExtGWT 3, og en sentral bit her er RequestFactory, som både reduserer mengden data som overføres mellom klient og server, samt gjør at vi ikke trenger å skrive like mye kode for å endre på data. Disse endringene spenner seg over både server og klient side av kodebasen.
Spring og RequestFactory
Bruker foreløpig servlet angitt i web.xml. Denne er uavhengig av hvilken RequestContext du bruker, men hver implementasjon av disse er ikke Spring-kompatible. Derfor står dette som et punkt på listen over ting som må utbedres.
ExtGWT og RequestFactory
Sencha sier selv at main i ExtGWT 3 skal bevege seg mot en bedre integrasjon mot GWT. Dette innebærer å bruke de rammeverk som allerede eksisterer i GWT. Deriblant finner vi RequestFactory (introdusert i GWT 2.1), som er et rammeverk for å minimere trafikk over nettlinjen. Dette gjøres ved å bare sende endringer gjort i modellen i stedet for hele modellen. En grunnleggende introduksjon er tilgjengelig her:
http://code.google.com/webtoolkit/doc/latest/DevGuideRequestFactory.html
Det første som slår meg når jeg leser denne dokumentasjonen, er at Google har brutt alle skikker for god programmering når de lagde dette rammeverket. Alle metoder for å hente ut data fra databasen ligger her som statiske metoder på entiteten. Det at disse metodene er statiske stopper for integrasjon mot Spring/Guice eller lignende. Det at de er plassert på entiteten gjør at koden ikke er separert i kodelag som gjør videre utvikling lettere. Dette har blitt gjort litt lettere i GWT 2.1.1, med en god del forbedringer for RequestFactory:
http://code.google.com/p/google-web-toolkit/wiki/RequestFactory_2_1_1
Man kan her dra ut implementasjon av uthenting av data fra entiteter ved hjelp av locators. Disse trenger heller ikke lenger være statiske, noe som letter implementasjon mot Spring ganske mye. Derimot er det veldig lite dokumentasjon tilgjengelig enda. Hverken Google eller bloggere har begynte skrive om det forbedrede rammeverket enda. Det har derfor vært en lang prosess med prøving og feiling for å integrere RequestFactory i vårt system.
RequestFactory GIN provider
-
public class ContactRequestFactoryProvider implements
-
Provider<ContactRequestFactory> {
-
-
@Inject
-
private CoreGinjector coreGinjector;
-
-
@Override
-
public final ContactRequestFactory get() {
-
ContactRequestFactory contactRequestFactory =
-
GWT.create(ContactRequestFactory.class);
-
contactRequestFactory.initialize(coreGinjector.getEventBus(),
-
coreGinjector.getRequestTransport());
-
return contactRequestFactory;
-
}
-
}
For oppretting av RequestFactory-objekter brukes en GIN-provider. Resultatet av denne er igjen bundet inn som en singleton gjennom GIN-konfigurasjonen. Denne initieres ved å bruke vår globale eventbus og en tilpasset transport-metode. Grunnen til at vi trenger en tilpasset transport-metode er at vi henter data fra en annen web-context enn den klienten ligger på.
RequestFactory
-
public interface ContactRequestFactory extends RequestFactory {
-
ContactRequest contactRequest();
-
}
Factory i seg selv henviser bare til en request implementasjon.
Contact request
-
@Service(value = ContactAdapter.class, locator = InstanceServiceLocator.class)
-
public interface ContactRequest extends RequestContext {
-
-
Request<ContactProxy> get(String id);
-
-
Request<Void> persist(ContactProxy contact);
-
-
Request<Void> remove(ContactProxy contact);
-
}
Her defineres de metodene man ønsker å bruke for å kommunisere fra klient til server. Dette er de metodene som er spesifikt for kontakt-modulen. Klassen er i seg selv annotert som en GWT-service, der implementasjonen av disse metodene ligger i ContactAdapter-klassen (trenger ikke lenger være entitets-klassen i GWT 2.1.1), mens ContactLocator (locators er nytt i GWT 2.1.1) tar seg av oppdateringer av entiteter.
Contact RequestFactory adapter
-
@Component
-
public class ContactAdapter {
-
-
private static ContactDao contactDao;
-
-
public ContactAdapter() {
-
}
-
-
@Autowired
-
public ContactAdapter(final ContactDao contactDao) {
-
ContactAdapter.contactDao = contactDao;
-
}
-
-
public final Contact get(final String id) {
-
Scanner scanner = new Scanner(id);
-
scanner.useLocale(Locale.ROOT);
-
if (scanner.hasNextLong()) {
-
return contactDao.get(scanner.nextLong());
-
}
-
return null;
-
}
-
-
public final void persist(final Contact contact) {
-
contactDao.saveOrUpdate(contact);
-
}
-
-
public final void remove(final Contact contact) {
-
contactDao.delete(contact);
-
}
-
}
Her implementeres de metodene som er definert i request-interfacet. Klassen er tagget med @Component slik at Spring finner denne klassen gjennom auto-scanning. Vi slipper dermed spesifikk konfigurasjon per modul. En kontakt-DAO blir gitt fra Spring sin kontekst. Denne lagres som en statisk variabel. Grunnen til dette er at RequestFactory oppretter instanser av dette objektet uavhengig av Spring. Ved oppstart blir DAO overlevert fra Spring, og alle instanser av denne adapteren vil ha tilgang til gjeldende DAO.
Instance ServiceLocator
-
public class InstanceServiceLocator implements ServiceLocator {
-
-
private static final Log LOGGER = LogFactory
-
.getLog(InstanceServiceLocator.class);
-
-
@Override
-
public final Object getInstance(final Class<?> clazz) {
-
try {
-
Object newInstance = clazz.newInstance();
-
return newInstance;
-
} catch (InstantiationException ex) {
-
LOGGER.fatal("Failed to create instance", ex);
-
} catch (IllegalAccessException ex) {
-
LOGGER.fatal("Failed to create instance", ex);
-
}
-
return null;
-
}
-
}
Dette er en veldig enkel ServiceLocator som ganske enkelt oppretter nye instanser av den klassen man etterspør. Det er mulig at vi i senere tid vil trenge å hente ut data fra Spring sin context i stedet for å lage objekter utenfor.
Entity proxy
-
@ProxyFor(value = Contact.class, locator = ContactLocator.class)
-
public interface ContactProxy extends BaseEntityProxy {
-
-
String getAddress();
-
-
String getFirstName();
-
-
String getLastName();
-
-
void setAddress(String address);
-
-
void setFirstName(String firstName);
-
-
void setLastName(String lastName);
-
}
Entitets-proxy-er blir implementert som autobeans på klientsiden. Disse er en speiling av entiteter på server-siden. Man må i proxy angi navn på metoder basert på Java Bean-standarden. Dette vil vanligvis være en speiling av de metode-navnene man finner i entiteten. Dette for at RequestFactory skal kunne hente data entiteten før de dras over til klienten. Dersom disse ikke stemmer overens får man feil ved første forsøk på uthenting av data. Merk også at vi her peker til en locator. Denne er ikke av typen ServiceLocator, som plasseres på en request.
-
@Entity
-
public class Contact extends AbstractDocumentEntity implements Serializable {
-
-
private String firstName;
-
private String lastName;
-
private String address;
-
-
public String getAddress() {
-
return address;
-
}
-
-
public void setAddress(String address) {
-
this.address = address;
-
}
-
-
public String getFirstName() {
-
return firstName;
-
}
-
-
public void setFirstName(String firstName) {
-
this.firstName = firstName;
-
}
-
-
public String getLastName() {
-
return lastName;
-
}
-
-
public void setLastName(String lastName) {
-
this.lastName = lastName;
-
}
-
}
Dette er et forenklet utdrag av entiteten som autobean proxy peker til. Merk at metode-kallene i proxy kaller til feltene i klassen, ikke metodene. Begge klasser følger samme regelsett for navngiving av getters/setters.
Contact RequestFactory locator
-
public abstract class DocumentEntityLocator<T extends AbstractDocumentEntity>
-
extends Locator<T, Long> {
-
-
private static final Log LOGGER = LogFactory.getLog(DocumentEntityLocator.class);
-
-
public DocumentEntityLocator() {
-
}
-
-
public abstract BaseDao<T> getDao();
-
-
@Override
-
public final T create(final Class<? extends T> clazz) {
-
try {
-
Class<? extends T> entityClass = getEntityClass(clazz);
-
if (entityClass != null) {
-
T newInstance = entityClass.newInstance();
-
getDao().saveOrUpdate(newInstance);
-
return newInstance;
-
}
-
} catch (InstantiationException ex) {
-
LOGGER.fatal("Failed to create instance", ex);
-
} catch (IllegalAccessException ex) {
-
LOGGER.fatal("Failed to create instance", ex);
-
}
-
return null;
-
}
-
-
@Override
-
public final T find(final Class<? extends T> clazz, final Long id) {
-
return getDao().get(id);
-
}
-
-
@Override
-
public final Class<T> getDomainType() {
-
return getDao().getDomainType();
-
}
-
-
@Override
-
public final Long getId(final T domainObject) {
-
return domainObject.getId();
-
}
-
-
@Override
-
public final Class<Long> getIdType() {
-
return Long.TYPE;
-
}
-
-
@Override
-
public final Object getVersion(final T domainObject) {
-
return domainObject.getRevisionNumber();
-
}
-
-
@SuppressWarnings("unchecked")
-
private <X extends Object> Class<X> getEntityClass(
-
final Class<? extends Object> clazz) {
-
Entity entity = clazz.getAnnotation(Entity.class);
-
if (entity != null) {
-
return (Class<X>) clazz;
-
}
-
-
Class<?> superclass = clazz.getSuperclass();
-
if (superclass != null) {
-
return getEntityClass(superclass);
-
}
-
-
return null;
-
}
-
}
En locator er bindeledded mellom din modell-struktur og RequestFactory. Her definerer du hvordan man henter ut en entitet fra en id. I tillegg definers hvordan man finner id, versjon og type fra en entitet. DAO hentes her ut gjennom en abstrakt metode.
-
@Component
-
public class ContactLocator extends DocumentEntityLocator<Contact> {
-
-
private static final Log LOGGER = LogFactory.getLog(ContactLocator.class);
-
private static ContactDao contactDao;
-
-
public ContactLocator() {
-
}
-
-
@Autowired
-
public ContactLocator(final ContactDao contactDao) {
-
ContactLocator.contactDao = contactDao;
-
}
-
-
@Override
-
public final BaseDao<Contact> getDao() {
-
return ContactLocator.contactDao;
-
}
-
}
Dette er en implementasjon av en locator for en gitt modul. Denne klassen er annotert med @Component for at Spring skal behandle den ved oppstart. Den statiske kontakt DAO blir da satt. Denne må være statisk da det er RequestFactory som i ettertid vil lage nye instanser av denne, og er som kjent ikke fullstendig Spring-kompatibel. Denne gis så til den abstrakte klassen via implementeringen av DAO-uthenting. Dette gjør det raskt og enkelt å sette opp nye moduler.
Videre plan er å gjøre et forsøk på å utvide RequestFactory på serverside slik at vi ikke er så avhengige av statiske variabler for Spring-Beans. Dette vil gjøre at koden er bedre rustet på endringer i fremtiden.
|