Tato aplikace byla vytvořena pro Google Online Dílnu - 2009/10 GUG.CZ jako soutěžní aplikace.
V lednu 2010 byla vypuštěna verze 2.0, která má stejné funkční vlastnosti jako původní soutěžní verze 1.0, ale používá DAO vrstvu generovanou přímo pro GAE low-level Datastore API (verze 1.0 používala high-level GAE/JDO API).
Vaším úkolem pro tuto online dílnu bude napsat jednoduchou webovou aplikaci na platformě Google App Engine. Aplikace se bude jmenovat Todoizer, a bude umožňovat přihlášenému uživateli zapisování úkolů včetně tagování a vyhledávání.
Byla vytvořena nadstavba nad JDO - AuDAO s implementací pro GAE/JDO. AuDAO je nástroj pro generování Java DAO a DTO tříd, který usnadňuje práci vývojářům. DB model včetně relací a číselníků se nadefinuje pomocí jazyka XML a celá DAO vrstva pro přístup do DB se nechá nástrojem AuDAO vygenerovat.
Doposud tento nástroj sloužil jen jako generátor kódu pro relační databáze. Nyní jsme jako POC (Proof of Concept) ukázali, že tento generátor lze použít i pro JDO / GAE datastore. Čistě teoreticky je tedy možné, aby vývojář vyvinul aplikaci, která by byla zcela portabilní mezi GAE a legacy systémem (s Java aplikačním/webovým serverem).
Samozřejmě nelze říct, že AuDAO je všemocný nástroj, který vývojáře úplně odfiltruje od JDO / GAE prostředí. Naopak je nutné znát především omezení daná v GAE pro transakce a entity groupy (pomocí AuDAO lze nyní namodelovat pouze entity groupy se dvěma úrovněmi - parent + závislé třídy). Stejně tak lze namítnout, že převodem mezi vrstvou DAO a JDO dochází ke zbytečným operacím, které by se neprováděly při přímém volání JDO.
Rozhodně velkým přínosem je generování samotných PersistenceCapable tříd a jejich "lehčích" DTO obrazů. Tyto "lehké" DTO třídy lze pak použít např. pro AJAX operace, aniž by bylo potřeba hlídat stav persistentních objektů (transient,detached,..) a řešit lazy-loading. Koneckonců podobný přístup lze najít zde.
A jako ukázku uveďme popis entity Tags:
<table name="tags"> <columns> <column name="tag_id"> <type>long</type> <auto/> <pk/> </column> <column name="tag_name"> <type max-length="100">String</type> <not-null/> </column> </columns> <indexes> <index name="inx_tag_name"> <unique/> <columns> <column name="tag_name"/> </columns> </index> </indexes> </table>a vygenerovaná persistentní třída:
/**
* This is a DTO class.
*
* @author generated
*/
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class TagImpl extends AbstractDto {
////////////////////////////////////////////////////////////////////////////
// Attributes
////////////////////////////////////////////////////////////////////////////
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key tagId;
@Persistent
private String tagName;
...
a vygenerované metody pro práci s entitou:
/**
* Finds a record identified by its primary key.
* @return the record found or null
*/
public Tag findByPrimaryKey( long tagId );
/**
* Finds a record.
*/
public Tag findByTagName( String tagName );
/**
* Inserts a new record.
* @return the generated primary key - tagId
*/
public long insert( Tag dto ) throws DaoException;
Byla vytvořena implementace AuDAO pro GAE low-level API.
Stále je nutné znát omezení GAE pro transakce a entity groupy (pomocí AuDAO lze nyní namodelovat i entity groupy s více než dvěma úrovněmi - parent + závislé třídy). Mezi vrstvou DAO a GAE API už nedochází ke zbytečným operacím jako mezi u GAE/JDO implementace.
<table name="tags">
<columns>
<column name="tag_name"> <!-- primary key - we need get(name)/insert in tx -->
<type max-length="100">String</type>
<pk/>
</column>
<column name="tag_id"> <!-- unique reference key used by other entities -->
<type>long</type>
<auto/>
<not-null/>
</column>
</columns>
<indexes>
<index name="inx_tag_id">
<unique/>
<columns>
<column name="tag_id"/>
</columns>
</index>
</indexes>
<methods>
<find name="byTagName">
<pk/>
</find>
</methods>
</table>
a vygenerovaná DAO implementeace pro GAE:
/**
* Finds a record.
*/
public Tag findByTagId( long tagId ) {
Query _query = getQuery();
_query.addFilter( "tagId", Query.FilterOperator.EQUAL, tagId );
return findOne( _query, "tagId = :1", 0, tagId);
}
/**
* Finds a record identified by its primary key.
*/
public Tag findByTagName( String tagName ) {
Entity _ent = entityGet( new KeyFactory.Builder( "Tag", tagName ).getKey());
return _ent != null ? fetch( null, _ent ) : null;
}
/**
* Inserts a new record.
*/
public void insert( Tag dto ) throws DaoException {
checkNull( "tagName", dto.getTagName());
Entity ent = new Entity( new KeyFactory.Builder( "Tag", dto.getTagName()).getKey());
{
if ( dto.getTagId() == null ) {
dto.setTagId( ds.allocateIds( "Tag", 1 ).getStart().getId());
}
ent.setProperty( "tagId", dto.getTagId());
}
entityPut( ent, dto, "insert" );
}
protected Tag fetch( Tag dto, Entity ent ) {
if ( dto == null ) dto = new Tag();
dto.setTagName( ent.getKey().getName());
dto.setTagId( getLong( ent, "tagId" ));
return dto;
}
Toto je hlavní výkonná vrstva - algoritmy načítání a ukládání se nacházejí zde. Pro možnost nasazení v aplikačním serveru je pro každou implementační třídu vytvořen abstraktní Java interface s anotacemi (TxRequired). To nám dovolí v další vrstvě provést proxy management a zajistit tak správné uvolnění prostředků a transakčnost operací (opět na úkor výkonnosti - ale co bychom nedali za čistě napsanou aplikaci, že ?)
Opět malá ukázka: metoda v interface:
/**
* Gets existing or creates a new tag.
* @return always non-null value
*/
@TxRequired
public Tag getOrCreateTag( String tagname ) throws DaoException;
a její implementace pomocí DAO vrstvy - jak je vidět GAE API / JDO zůstává skryto:
/**
* Gets existing or creates a new tag.
* @return always non-null value
*/
public Tag getOrCreateTag( String tagname ) throws DaoException {
TagDao dao = getTagDao();
Tag ret = dao.findByTagName( tagname );
if (ret == null) {
ret = new Tag();
ret.setTagName( tagname );
dao.insert( ret );
}
return ret;
}
V této vrstvě by se teoreticky měly pouze proxovat požadavky z JSP stránek nebo AJAX requestů přímo do core vrstvy. Bohužel kvůli omezením na GAE - transakce a entity groupy - je někdy potřeba jeden request rozložit na více volání do core vrstvy.
Přístup z této vrstvy do vrstvy core je zajištěn pomocí proxy tříd. Proxy framework zajišťuje spravné spuštění a dokončení transakce a uzavírání prostředků (PersistenceManager - resp. DatastoreService pro verzi 2).
Jedná se o JavaScript vygenerovaný pomocí GWT. Jedna zajímavost: pro správnou serializaci je potřeba zajistit, aby soubory *.gwt.rpc byly dostupné pro serverovou část jako Java resource - ve WEB-INF/appengine-web.xml:
<resource-files> <include path="**.gwt.rpc" /> </resource-files>