Applying Single Responsibility Principle when calling a remote Web API
Introduction
A requirement of a certain client was as follows
- Call a third-party web API from an ASP.NET MVC Controller
- The calling of the web API should work as follows
- Check if the token sent with the call is valid
- if the token is not valid, generate a new token and update a database field where token is stored
- send the token with the call to authorize the request operation
This requirement was asked by the customer shortly after completing a course talking about software architecture and design principles. So it was the first practical implementing of the principles learned
Implementation
- The design of the solution tried to stick to the Single Responsibility Principle.
- So when splitting the requirements above into business classes, it resulted in
- The responsibility of the controller action method is to call the API only.It has nothing to do with
- Creating an http client object
- Check if the authorization parameters are passed
- The responsibility of the class calling the API only is to call the web API only .It has nothing to do with
- checking for the token
- Check for token expiration, generating and storing tokens
- So the above responsibilities will be moved to a third class
- The responsibility of the controller action method is to call the API only.It has nothing to do with
- So the outline of the classes involved was as follows (code details are omitted for
- brevity
- coding standards
- differences among companies and individuals
- Web API belongs to the client or third-party
- The Main API Caller class
public abstract class MainAPICaller { private TokenManager tokenManager; protected async Task PrepareToken() //this method was placed here //It may be called as needed(i.e If the web api caller needs to be authorized) { tokenManager = new TokenManager(); tokenManager.ReadTokenValueFromDb(); if (await tokenManager.CheckIfTokenExpired()) { await tokenManager.GenerateNewToken(); } Token = tokenManager.Token; } protected string _apiUrl = ""; protected string Token = ""; //each class implements its call to the API //providing parameters and request params as needed public abstract Task
CallAPI(); public HttpResponseMessage response { get; set; } } } - a sample class implementing the main API caller class
public class SampeAPICaller : API.MainAPICaller { public SampeAPICaller() { _apiUrl = ".."; } public string Number { get; set; }//the parameters forming the query string go here public override async Task
CallAPI() { //this line need not to be called if there are not special authorization requirements await base.PrepareToken(); var httpClient = new HttpClient(); httpClient.DefaultRequestHeaders.Add("Accept", "application/json"); var json = JsonConvert.SerializeObject(new { Number = Number }); HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, _apiUrl + "?Number="+Number); //this line need not to be called if there are not special authorization requirements request.Headers.Authorization = new AuthenticationHeaderValue("Token", Token); this.response = await httpClient.SendAsync(request); var responeData = await response.Content.ReadAsStringAsync(); return responeData; } } - Invoking the api caller in the controller class
public class RegistrarsController : Controller { public async Task
CallTelephoneVerifyFunction(string Number) { TelephoneCheckAPICaller telephoneCheckAPICaller = new TelephoneCheckAPICaller(); telephoneCheckAPICaller.Number = Number; string responeData; try { responeData = await telephoneCheckAPICaller.CallAPI(); return Json(new { success = telephoneCheckAPICaller.response, data = responeData }); } catch (Exception ex) { return Json(new { success = false, data = ex.Message }); } } }
You can note the following from the code
- if a certain web API does not have any authorization requirement, it should only ignore calling preparetoken method defined in the abstract class. Nothing changed in the code of the MVC Controller
- if a certain web api is moved to another url,just need to change url property in the caller class.Nothing changed in the code of the MVC Controller
So the components appear to be loosely coupled as the principle aims
Comments
Post a Comment