Sunday 8 August 2021

Api call with long running asynchronous backend with .Net core - Part2

 In the previous part, I explained how to wait for synchronous operation in the backend. In part2 I will explain how to not to wait for the long-running operation, but query the API to get the information if the client needs to wait more or the request is over.

It will work in a way that when you invoke the API it can return the information immediately or it can return custom code 301 with a header "Reply-After" which holds how many time the client should wait before retrying API query. In this case, it will be the responsibility of the client to run the query again to check if the operation is over.

Http header "Reply-After" is explained in the article here

I will post the changes I did in the frontend API and backend API. It has the comments that explain what is the purpose of each function.

Frontend API


/// <summary>
        /// If initial request "long-running-async" didn't complete 
        /// immediatelly, it means that client for some token that he can use to check 
        /// if the job is done or not. The client is checking this by using "long-running-async-check"
        /// method
        /// </summary>
        /// <param name="token"></param>
        /// <returns></returns>
        [HttpGet]
        [Route("long-running-async-check/{token}")]
        public async Task<IActionResult> RunLongOperationAsyncCheck(string token)
        {
            //for the simplicity of the process we will use 
            //get operation of HttpClient object
            var client = _clientFactory.CreateClient();
            return await GetBackendResponse(token, client);

        }
        /// <summary>
        /// Service method to contact the backend and check
        /// if the operation is over or not
        /// </summary>
        /// <param name="token"></param>
        /// <param name="client"></param>
        /// <returns></returns>
        private async Task<IActionResult> GetBackendResponse(string token,HttpClient client)
        {
            var result = await client.GetAsync($"http://localhost:5010/BackendAPi/run-backend-op-async/{token}");
            var resultContent = await result.Content.ReadAsStringAsync();

            //if operation is not over yet, send the response which is asking the 
            //client to retry after specific amount of time
            if (result.StatusCode == HttpStatusCode.MovedPermanently)
            {
                Response.Headers.Add("Retry-After", resultContent);
                Response.StatusCode = 301;

                return new JsonResult(token);
            }
            //otherwise , just return the result
            return Ok(resultContent);
        }

        /// <summary>
        /// initial entry point to start long running operation.
        /// It could be that the operation on the backend is fast 
        /// and in this case the method can return with a correct response.
        /// But it could be that the backend return "Hei, the operation will take some time,
        /// you can use the following token to check the status. You don't need to check every nanosecond,
        /// I will provide you how much time do you need to wait by using Reply-After header"
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        [Route("long-running-async")]
        public async Task<IActionResult> RunLongOperationAsync()
        {
            //for the simplicity of the process we will use 
            //get operation of HttpClient object
            var client = _clientFactory.CreateClient();
            //this will be unique identifier of the long running job
            Guid guid = Guid.NewGuid();
            return await GetBackendResponse(guid.ToString(), client);



        }

Backend API

 /// <summary>
        /// This is a simulation long running process that informs
        /// the client of operation is over or he needs to wait sometine
        /// </summary>
        /// <param name="token"></param>
        /// <returns></returns>
        [HttpGet]
        [Route("run-backend-op-async/{token}")]
        public IActionResult RunBackendOpAsynch(string token)
        {
            //I use the dictionary to simulate the decision making.
            //First time the backend gets the job and it decides 
            //that he needs to wait m he will put the information in the dictionary
            //so it will know that the job is not done yet.
            //Once the job is done , the token will be removed from the dictionary
            var tmp = rnd.Next(1, 5).ToString();
            
            
            //For the purpose of the simulation, if the result of random calculation is 1
            //backwend will assume that the job is done
            if (rnd.Next(1, 4) == 1)
            {
                dict.TryRemove(Guid.Parse(token), out _);
                return Ok($"Result of long running {rnd.Next()}");
            }
            else
            {
                //if random result is not 1, backend will assume that the job is not done yet
                Response.Headers.Add("Retry-After", tmp);
                Response.StatusCode = 301;
                dict.AddOrUpdate(Guid.Parse(token), tmp, (key, oldValue) => tmp);
                return new JsonResult(tmp);
            }

                 
            
            return Ok($"Result of long running {rnd.Next()}");


        }

This time I used Postman to run the test since browsers don't treat status 301 well



As we can see we got the proper result. Postman does the simulation of the client asking to check every X seconds (provided in the Reply-After header) if the job is done or not

No comments:

Post a Comment