1

In an application I'm developing we have integrated with a third party api. One of the use cases consists of a user of our system filling out a form and submitting it. This results in us mapping the form to an api request model and posting it to the third party api. This starts an external process, the details of which aren't relevant. We do get a response with a unique ID for the created process though, and when we get this, we create a business entity that models the started process so we can track and update it, etc. It needs to be noted that each user (account) can only start this process once (this is enforced by checking if the account has any associated process).

The problem we're having is that if a user double taps submit or reposts, there is a chance that they will invoke the start of another process before we've had time to get the response from the third-party api and coupled the process entity to the user account. The third-party api does not care if two requests are identical.

We're looking for a neat way to sort of "lock" the account so it cannot be used to send more requests to the third-party until the use-case is complete and we get a non-successful response. One very hacky way to accomplish it would be to as soon as the use case is initiated, save a "dummy" entity with some gibberish string as it's id in the database and associate it to the account. When we get our response from the third-party api, we can delete the dummy and proceed as normal.

Similarly we could allow for creation of the process entities with no id from the third-party, and then we update it when we get a successful response. This feels slightly cleaner, but it would be nice if we could avoid loosening the constraints on the entity (this ID should never change and in the model it's considered to be a constant unique identifier). This could very well be me being pedantic, but if there are other paths I would like to explore them.

If it's relevant, the user submits the form as a HTTP POST, we process it server-side in an ASP.NET application using Entity Framework as an ORM and we post the request to the third-party using a client that internally uses a standard .NET HttpClient.

It might also be of note that even if we do detect that a duplicate has been created, we cannot simply delete the row and notify the third-party (without considerable business overhead)

sara
  • 2,549
  • 15
  • 23

2 Answers2

1

In my company we had a similar problem before, as some POSTs were getting twice in very close time proximity.

If you have only one webserver, you can use internal locking like a HashTable or a Set with user ids that are pending to process, all of this synchronized with a semaphore or locks.

It gets tricky when there are multiple webservers and each POST can arrive to a different server. In this case, you need some sort of central authority that acts as a lock. The database approach is fine, but you'll have lot's of IO and bad performance. We used memcached for this. Simply insert a key with the user id and some gibberish data (a 1 for example). If there is a key already, then memcache add method will return error, meaning the same user sent a POST before.

Depending on your business logic you can let that key expire by itself (setting some sensitive time-to-live), or you can delete it when the process finishes.

Fermin Silva
  • 141
  • 3
  • I'll probably experiment with these suggestions over the coming days (no telling when it's judged to be "enough time" to worry about it though, since our temporary hack "solves the problem". I'll try to get back to you though! – sara Apr 01 '16 at 11:50
0

You issue is to prevent same service to be invoked in same time twice, I had this issue before, simply I create method take methodName as a parameter and get method by reflection and check if this method is already running or not.

  public void DoWork(string methodName) 
  {  
  var action = this.GetType().GetMethod(methodName); 
  JobStatus value = null;   
  _jobs.TryGetValue(methodName, out value); 
  if (value != null && value.IsRunning)  
  { // Do Nothing } 
  else { 
  if (value == null) 
      value = new JobStatus  
      { SyncObject = new object() }; 
  lock (value.SyncObject) 
  { 
      if (!value.IsRunning)  
       { 
           value.IsRunning = true;
           _jobs.TryAdd(methodName, value); 

             action.Invoke(this, null); 
           } 
        } 
  }

Jobs is a Dictionary contains all services running and JobStatus is simple model

Ahmed
  • 545
  • 1
  • 4
  • 11
  • I don't want to prevent simultaneous invocations generally though, I want to prevent requests being sent to the third-party for a certain account starting immediately from the moment the form is submitted, as opposed to when we got the response and saved the results in the database. this solution looks even messier than my proposed solutions imo, not to mention that it's both harder to maintain and since it uses reflection it's hard to refactor or tweak without breaking stuff silently. – sara Mar 31 '16 at 18:39
  • you don't have to use reflection, make method take Func as a parameter and invoke it, regarding to specific user, you can add userId in Model and check with both ID && isRunning – Ahmed Mar 31 '16 at 18:42