I'm working on a multi-user application where I'm using my own authentication / account scheme, but am running into trouble structuring accounts in GAE storage. Here's where I started and got tripped up:
- I'd like each user be a root level entity, and have all of the entities for that user be child entities, since transactions are almost always done on a single user.
- I'd like the username for users to be their email address, which they can change at any time, but each user should have a system-assigned integer ID that never changes.
Given this, I started creating Account entities at the root level, each with an "email" property containing their email address. My first problem came immediately, when trying to create an Account as follows:
public boolean createAccount(final Account ac) {
final DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
final Transaction txn = ds.beginTransaction();
try {
// see if an account with the same email already exists
final Query q = new Query("Account");
q.addFilter("email", Query.FilterOperator.EQUAL, ac.getEmail());
final PreparedQuery pq = ds.prepare(txn, q);
final boolean bCreated;
if (0 < pq.countEntities(FetchOptions.Builder.withLimit(1))) {
bCreated = false;
} else {
ds.put(EntityFactory.create(ac)); // EntityFactory just creates an entity of kind "Account" with the right fields set
bCreated = true;
}
txn.commit();
return bCreated;
} finally {
if (txn.isActive())
txn.rollback();
}
}
The problem is, I can't query and update root level entities in a transaction, because that query doesn't contain an ancestor query, so there's no way for me to atomically check that an Account with a given email address doesn't exist, then create the account. I get "java.lang.IllegalArgumentException: Only ancestor queries are allowed inside transactions.". How do I solve this, given the following restrictions?
- I don't want the email address to be the key for the entity, because then a user's email address can't change without copying their entire entity tree.
- I don't want users to have a separate username from their email address that's used as the entity key, as they tend to forget usernames, so I find their email address to be convenient.
- I don't want to create a dummy parent entity that all Accounts are under, because that would force all users to be in the same entity group, which would make transaction concurrency poor.
Any thoughts?