Home > Software engineering >  TypeOrm: handle .save() that is called twice at the exact same time in an express API context
TypeOrm: handle .save() that is called twice at the exact same time in an express API context

Time:09-17

I have a getUser function that return a User entity after being saved into the database for caching reasons. Here is the pseudo-code for it:

export const getUser = async (userId: string): Promise<User> => {
  if (userStoredInDatabase) return userStoredInDatabase // <- pseudo code 
   
  // ...if no user stored -> fetch external services to get user name, avatar, ...
  const user = new User()
  user.id = userId // <- PRIMARY KEY
  // ...add data to the user

  return getManager().save(user)
}

I use this function in 2 distinct routes of a simple expressjs API:

app.get('/users/:userId/profile', async (req, res) => {
  const user = await getUser(req.params.userId)
  // ...
})
app.get('/users/:userId/profile-small', async (req, res) => {
  const user = await getUser(req.params.userId)
  // ...
})

So far so good until I came to the problem that my frontend need to fetch /users/:userId/profile and /users/:userId/profile-small at the exact same time to show the 2 profiles. If the user is not yet cached in the database, the .save(user) will be called twice almost at the same time and I will respond for one of the 2 with an error due to an invalid sql insertion as the given user.id already exists.

I know I could just delay one of the request to make it work good enough but I'm not in the favor of this hack.

Do you have any idea how to concurrently .save() a User even if it is called at the same time from 2 different contexts so that TypeOrm knows for one of the 2 calls that user.id already exist and therefore do an update instead of an insert?

CodePudding user response:

Using delay will not solve the prolm since the user can open 3 tabs at the same time. Concurency has to be handled.

Catch if there is a primary key violation (Someone already has stored the user between the get user and persist user code block)

Here is an example:

export const getUser = async (userId: string): Promise<User> => {
  try {

    if (userStoredInDatabase) return userStoredInDatabase // <- pseudo code 
   
    // ...if no user stored -> fetch external services to get user name, avatar, ...
    const user = new User()
    user.id = userId // <- PRIMARY KEY
    // ...add data to the user

    return getManager().save(user)
  } catch (e) {
    const isDuplicatePrimaryKey = //identify it is a duplicate key based on e prop,this may differ depending on the SQL Engine that you use. Debugging will help
    if (isDuplicatePrimaryKey) {
      return await //load user from db
    }
    throw e; // since e is not a duplicate primary key, the issue is somewhere else
  }
}

  • Related