I'm using Google App Engine and Datastore with objectify.
I'm trying to create an account for a user (Google user), so I need to check if that users exist, and if not, create an account for that user, but I'm facing the fact that sometimes the account is created twice if I spam the createAccount API method
@ApiMethod(name = "account.google.create")
public Account createGoogleAccount(final User user) throws OAuthRequestException {
if (user == null) {
throw new OAuthRequestException("createAccount: OAuthRequestException<User is not authenticated>");
}
Account alreadyExisting = RObjectifyService.getObjectify().load().type(Account.class).filter("accountId.GOOGLE", user.getUserId()).filter("email", user.getEmail()).first().now();
if (alreadyExisting != null) {
throw new OAuthRequestException("createAccount: OAuthRequestException<Account already exist>");
}
return RObjectifyService.getObjectify().transactNew(new Work<Account>() {
@Override
public Account run() {
Account account = AccountProvider.createAccountFromGoogleProvider(user);
RObjectifyService.save(account);
return account;
}
});
}
I read that I should use transactions but I can't because if I do this in the transaction:
RObjectifyService.getObjectify().load().type(Account.class).filter("accountId.GOOGLE", user.getUserId()).filter("email", user.getEmail()).first().now()
I get an error "Only ancestor queries are allowed inside transactions", but I don't see another way to do it
Is this the right way to do it?
Thanks
You need a transaction and you need an entity whose primary key is the value you are trying to make unique (ie the username).
The pattern is a little tricky. There is some discussion of it here. The basic idea in pseudocode is:
You probably don't want to make
username
the primary key of yourUser
entity, so create a separateUsername
entity and mix in creation ofUsername
withUser
in the same transaction. Be sure to leave theUsername
entity around; that's what guarantees uniqueness.This problem (uniqueness) is actually one of the more technically challenging problems in a massively distributed system like the GAE datastore. It's simple to solve in a traditional RDBMS only if the traditional RDBMS is a single-master system, with the resulting impact on scalability and fault tolerance. GAE gives you the necessary primitives to enforce clusterwide uniqueness; they just aren't super easy to use.
The best way in this case for have strong consistency is retrieve the entity by key.