views:

294

answers:

2

My User object that I want to create and store in the datastore has an email, and a username. How do I make sure when creating my User object that another User object doesn't also have either the same email or the same username?

If I just do a query to see if any other users have already used the username or the email, then there could be a race condition.

UPDATE: The solution I'm currently considering is to use the MemCache to implement a locking mechanism. I would acquire 2 locks before trying to store the User object in the datastore. First a lock that locks based on email, then another that locks based on username.

Since creating new User objects only happens at user registration time, and it's even rarer that two people try to use either the same username or the same email, I think it's okay to take the performance hit of locking.

I'm thinking of using the MemCache locking code that is here: http://appengine-cookbook.appspot.com/recipe/mutex-using-memcache-api/

What do you guys think?

+1  A: 

Try storing your User with their email as the key_name. This can be done in one simple step:

MyUser.get_or_insert(email)

Getting your MyUser by an email is also easy:

MyUser.get_by_key_name(email)

See this similar question: http://stackoverflow.com/questions/2504748/add-properties-to-users-google-app-engine

So that solves the problem of two users with the same email. To do the same for usernames, perform the "get users with this username" query and the "insert a user with this username" in a transaction (this is what get_or_insert() does behind the scenes).

You can catch a TransactionFailedError to find cases when another user "takes" that username during your transaction.

You definitely don't want to use a memcache mutex, since that while loop waiting for the lock to free up can spend a lot of your memcache API call quota.

Jason Hall
Are you sure I can perform the "get users with this username" query and "insert a user with this username" in the same transaction? You can only operate on one entity group per transaction. And to do a query you have to have an ancestor filter on it. http://code.google.com/appengine/docs/python/datastore/transactions.html#What_Can_Be_Done_In_a_Transaction
Kyle
You can query for MyUsers and insert a MyUser in a transaction, you just can't query for OtherThings and insert a MyUser in a transaction. What may be sticky is, I don't know if you can do `get_or_insert()` inside a transaction, since it itself is a transaction. In this case you'll need to query for username _and_ email availability before inserting a MyUser.
Jason Hall
What would the ancestor filter that I put for the query be? None of my `User` objects have a common ancestor. And it would be bad to make all of my `User` objects have a common ancestor, thus making them be in the same entity group.
Kyle
@Jason Spines is right: You cannot do a query in a transaction unless it has an ancestor filter, and none applies here.
Nick Johnson
So there's just no way to do this in a transaction? What's the best way to make sure two users don't end up with the same email or username?
Jason Hall
+1  A: 

If you use the JPA impl of datastore, you just have to set the annotation

@Column(unique=true) Field field

This way the db will reject your insert/update.. i guess it's implemented by gdatastore, it's not a big matter for me to give a "technical error" to the user on such a specific case... but anyway you could catch a constraintviolation exception. I guess JDO also have this.

But actually i don't even know if you'r using Python or Java...

In Java as far as i know you can do a select request in a transaction...

About transactions you should also check what is transaction isolation and how it works on GAE...

Sebastien Lorber