Objectify: Handle race condition to prevent duplicate account creation

久未见 提交于 2019-12-08 03:27:57

问题


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


回答1:


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:

  • start transaction
  • load entity with the unique PK
  • if there is an entity
    • abort and return duplicate error
  • else
    • create the entity with the unique PK (+ whatever extra work you need)
    • commit the transaction
    • if the commit fails
      • abort and return duplicate error
    • else
      • everything is great!

You probably don't want to make username the primary key of your User entity, so create a separate Username entity and mix in creation of Username with User in the same transaction. Be sure to leave the Username 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.




回答2:


The best way in this case for have strong consistency is retrieve the entity by key.



来源:https://stackoverflow.com/questions/28552875/objectify-handle-race-condition-to-prevent-duplicate-account-creation

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!