0

i wrote a rest api that connects to google firestore as a backend. I have a users collection and want to ensure that each user document has a unique username. My first approach was to query the database for a matching document with same username and if it is found, then the user has to choose another one (on registration).

Here is my corresponding code (golang)

func (service *Service) CreateUser(ctx context.Context, user domains.User) (domains.User, error) {
    var err error

    err = domains.ValidateNewUser(user)
    if err != nil {
        return user, err
    }

    // Check if email already exists
    unique, err := service.CheckEmailUniqueness(ctx, user.Email)
    if err != nil {
        return user, err
    } else if !unique {
        return user, ErrEmailExists
    }

    // Check if user already exists
    unique, err = service.CheckUsernameUniqueness(ctx, user.Username)
    if err != nil {
        return user, err
    } else if !unique {
        return user, ErrUsernameExists
    }

    docRef, _, err := service.Client.Collection("users").Add(ctx, user)
    if err != nil {
        return user, ErrCreatingUser
    }

    user.UserID = docRef.ID

    return user, nil
}
func (service *Service) CheckUsernameUniqueness(ctx context.Context, username string) (bool, error) {
    iter := service.Client.Collection("users").Where("username", "==", username).Documents(ctx)

    usernameExists := false
    for {
        _, err := iter.Next()
        if err == iterator.Done {
            break
        }
        if err != nil {
            return false, err
        }
        usernameExists = true
        break
    }

    return !usernameExists, nil
}

But how can you prevent race conditions in this case? Like when 2 different users want to take the same username and their requests are processed by two different instances of my rest server. I am very inexperienced with firestore and NoSQL in general, so pls excuse me if i dont understand anything crucial.

xk2tm5ah5c
  • 55
  • 5
  • 1
    Look into use of transactions in Firestore to ensure that a set of operations are atomic. – Doug Stevenson Dec 02 '19 at 17:28
  • As far as i understand transactions are only helpful if you want to prevent to write to the same document at the same time. But thats not the case in my use case. I want to create unique users (so different documents with unique usernames). – xk2tm5ah5c Dec 02 '19 at 17:46
  • 1
    Transaction can be used for read-then-write operations. In a transaction, you can read a document in order to ensure that it doesn't exist before creating it. In that sense, a user can "reserve" their username without worrying about another user claiming it outside the transaction (as long as they are all using the transaction to guard access). – Doug Stevenson Dec 02 '19 at 17:51
  • Cool! good to know, thank you – xk2tm5ah5c Dec 02 '19 at 17:56

1 Answers1

0

Transactions are the answer, but you can't do queries inside of transactions, only fetching by ID https://stackoverflow.com/a/50071736/4458510

A possible solution would be to make the ID the email/username, however if a user wants to change their email then the ID would have to change and that bad since other things will probably reference that user.

A common pattern is to have another collection for the enforcement of uniqueness. You would then interact with both the 'users' collection and the 'user-emails' collection within the transaction

Alex
  • 5,141
  • 12
  • 26