Another implementation method of microservice based on. Net core

Time:2019-11-2

Preface

Based on the microservices of. Net core, many online introductions are based on the same web API and accessed through HTTP request, but this is not in line with our usage habits[ GetService<IOrderService>().SaveOrder(orderInfo)]If you are struggling with this, this article may be a reference

background

The original project is based on the traditional three-tier mode to organize code logic. With the passage of time, the logic of each module in the project is intertwined, interdependent and difficult to maintain. Therefore, we need to introduce a new mechanism to try to change this situation. After examining some microservice frameworks such as Java spring cloud / doublo, C ᦇ WCF / webapi / asp.net core, we finally choose net Cor E + Ocelot micro service mode. After discussion, the final expected project results are as follows

But the original project team members have been used to the coding form based on interface services, so that you can rewrite and define all the interfaces you need to define in the form of HTTP interface. At the same time, when the client calls, you need to use the familiar form likeXXService.YYMethod(args1, args2) Directly use “.” to get the internal members, and replace with “let them write directly”HttpClient.Post("url/XX/YY",”args1=11&args2=22”)It is really a very painful thing to access the remote interface

Question raise

Based on the above, how to simplify this call form through a mode, so that when you call, you don’t need to care whether the service is local (local class library depends) or remote, just use it in the normal way, as for whether you use the local service directly or send remote requests through http, this is all handed over to the framework for processing. For the convenience of narration, this paper assumes that the sales For example, the order and user service. The sales order service provides an interface for creating an order. After the order is created successfully, the user service is called to update the user score. The UML reference is as follows

Problem transformation

  • On the client side, the interface agent is generated through the interface disclosed by the microservice, that is, the information required by the interface [interface name / method name and parameters required by the method] is packaged as HTTP request to send a request to the remote service
  • In the microservice HTTP access section, we can define a unified entry. When the server receives the request, it will parse out the interface name / method name and parameter information, and create the corresponding implementation class to execute the interface request, and return the return value to the client through HTTP
  • Finally, the client passes AppRuntims.Instance.GetService<IOrderService>().SaveOrder(orderInfo) Form access remote service to create order
  • Data is transmitted in JSON format

Solution and Implementation

To facilitate processing, we define an empty interface iapiservice to identify the service interface

Remote services client agent

public class RemoteServiceProxy : IApiService
{
 Public string address {get; set;} // service address private apiactionresult posthttprequest (string interfaceid, string methodid, params object [] P)
 {
 ApiActionResult apiRetult = null;
 using (var httpClient = new HttpClient())
 {
  Var param = new arraylist(); // wrapper parameter

  foreach (var t in p)
  {
  if (t == null)
  {
   param.Add(null);
  }
  else
  {
   var ns = t.GetType().Namespace;
   param.Add(ns != null && ns.Equals("System") ? t : JsonConvert.SerializeObject(t));
  }
  }
  var postContentStr = JsonConvert.SerializeObject(param);
  HttpContent httpContent = new StringContent(postContentStr);
  if (CurrentUserId != Guid.Empty)
  {
  httpContent.Headers.Add("UserId", CurrentUserId.ToString());
  }
  httpContent.Headers.Add("EnterpriseId", EnterpriseId.ToString());
  httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");

  var url = Address.TrimEnd('/') + $"/{interfaceId}/{methodId}";
  AppRuntimes.Instance.Loger.Debug($"httpRequest:{url},data:{postContentStr}");

  Var response = httpclient.postasync (URL, httpcontent). Result; // submit the request

  if (!response.IsSuccessStatusCode)
  {
  AppRuntimes.Instance.Loger.Error($"httpRequest error:{url},statuscode:{response.StatusCode}");
  Throw new icvipexception ("network exception or service response failure");
  }
  var responseStr = response.Content.ReadAsStringAsync().Result;
  AppRuntimes.Instance.Loger.Debug($"httpRequest response:{responseStr}");

  apiRetult = JsonConvert.DeserializeObject<ApiActionResult>(responseStr);
 }
 if (!apiRetult.IsSuccess)
 {
  Throw new businessexception (apiresult. Message - "service request failed");
 }
 return apiRetult;
 }

 //Method proxy with return value
 public T Invoke<T>(string interfaceId, string methodId, params object[] param)
 {
 T rs = default(T);

 var apiRetult = PostHttpRequest(interfaceId, methodId, param);

 try
 {
  if (typeof(T).Namespace == "System")
  {
  rs = (T)TypeConvertUtil.BasicTypeConvert(typeof(T), apiRetult.Data);
  }
  else
  {
  rs = JsonConvert.DeserializeObject<T>(Convert.ToString(apiRetult.Data));
  }
 }
 catch (Exception ex)
 {
  Appruntimes. Instance. Logger. Error ("data conversion failed", ex);
  throw;
 }
 return rs;
 }

 //Agent without return value
 public void InvokeWithoutReturn(string interfaceId, string methodId, params object[] param)
 {
 PostHttpRequest(interfaceId, methodId, param);
 }
}

Remote Server HTTP access section unified access

[Route("api/svc/{interfaceId}/{methodId}"), Produces("application/json")]
public async Task<ApiActionResult> Process(string interfaceId, string methodId)
{
 Stopwatch stopwatch = new Stopwatch();
 stopwatch.Start();
 ApiActionResult result = null;
 string reqParam = string.Empty;
 try
 {
 using (var reader = new StreamReader(Request.Body, Encoding.UTF8))
 {
  reqParam = await reader.ReadToEndAsync();
 }
 AppRuntimes.Instance.Loger.Debug($"recive client request:api/svc/{interfaceId}/{methodId},data:{reqParam}");

 ArrayList param = null;
 if (!string.IsNullOrWhiteSpace(reqParam))
 {
  //Analytic parameters
  param = JsonConvert.DeserializeObject<ArrayList>(reqParam);
 } 
 //Transfer to local service processing center for processing
 var data = LocalServiceExector.Exec(interfaceId, methodId, param);
 result = ApiActionResult.Success(data);
 }
 Catch businessexception Ex) // business exception
 {
 result = ApiActionResult.Error(ex.Message);
 }
 catch (Exception ex)
 {
 //Business exception
 if (ex.InnerException is BusinessException)
 {
  result = ApiActionResult.Error(ex.InnerException.Message);
 }
 else
 {
  Appruntimes. Instance. Logger. Error ($"exception {interfaceid} - {methodid}, data: {reqparam}", ex) in calling service ";
  Result = apiactionresult.fail ("service exception");
 }
 }
 finally
 {
 stopwatch.Stop();
 Appruntimes. Instance. Logger. Debug ($"process client request end: API / SVC / {interfaceid} / {methodid}, time consuming [{stopwatch. Elassedmilliseconds}] Ms.);
 }
 //result.Message = AppRuntimes.Instance.GetCfgVal("ServerName") + " " + result.Message;
 result.Message = result.Message;
 return result;
}

Through the interface name and method name, the local service center finds out the specific method to implement the class and executes it with the passed parameters. PS: because it involves the reflection to obtain the specific method, it does not support the method overload with the same number of parameters temporarily. It only supports the method overload with different number of parameters

public static object Exec(string interfaceId, string methodId, ArrayList param)
{
 var svcMethodInfo = GetInstanceAndMethod(interfaceId, methodId, param.Count);
 var currentMethodParameters = new ArrayList();

 for (var i = 0; i < svcMethodInfo.Paramters.Length; i++)
 {
 var tempParamter = svcMethodInfo.Paramters[i];

 if (param[i] == null)
 {
  currentMethodParameters.Add(null);
 }
 else
 {
  if (!tempParamter.ParameterType.Namespace.Equals("System") || tempParamter.ParameterType.Name == "Byte[]")
  {
  currentMethodParameters.Add(JsonConvert.DeserializeObject(Convert.ToString(param[i]), tempParamter.ParameterType)
  }
  else
  {
  currentMethodParameters.Add(TypeConvertUtil.BasicTypeConvert(tempParamter.ParameterType, param[i]));
  }
 }
 }

 return svcMethodInfo.Invoke(currentMethodParameters.ToArray());
}

private static InstanceMethodInfo GetInstanceAndMethod(string interfaceId, string methodId, int paramCount)
{
 var methodKey = $"{interfaceId}_{methodId}_{paramCount}";
 if (methodCache.ContainsKey(methodKey))
 {
 return methodCache[methodKey];
 }
 InstanceMethodInfo temp = null;

 var svcType = ServiceFactory.GetSvcType(interfaceId, true);
 if (svcType == null)
 {
 Throw new icvipexception ($"service implementation of API interface not found: {interfaceid}");
 }
 var methods = svcType.GetMethods().Where(t => t.Name == methodId).ToList();
 if (methods.IsNullEmpty())
 {
 Throw new businessexception ($"cannot find the specified method in the service implementation of API interface [{interfaceid}]: {methodid}];
 }
 var method = methods.FirstOrDefault(t => t.GetParameters().Length == paramCount);
 if (method == null)
 {
 Throw new icvipexception ($"in the service implementation [{svctype. Fullname}] of [{interfaceid}] in API interface, the number of parameters of method [{methodid}] does not match");
 }
 var paramtersTypes = method.GetParameters();

 object instance = null;
 try
 {
 instance = Activator.CreateInstance(svcType);
 }
 catch (Exception ex)
 {
 Throw new businessexception ($"exception occurred in instantiated service [{svctype}], please confirm whether it contains a parameterless constructor", ex);
 }
 temp = new InstanceMethodInfo()
 {
 Instance = instance,
 InstanceType = svcType,
 Key = methodKey,
 Method = method,
 Paramters = paramtersTypes
 };
 if (!methodCache.ContainsKey(methodKey))
 {
 lock (_syncAddMethodCacheLocker)
 {
  if (!methodCache.ContainsKey(methodKey))
  {
  methodCache.Add(methodKey, temp);
  }
 }
 }
 return temp;

Service configuration, indicating the remote address of the specific service. When the unconfigured service defaults to the local service


[
 {
 "ServiceId": "XZL.Api.IOrderService",
 "Address": "http://localhost:8801/api/svc"
 },
 {
 "ServiceId": "XZL.Api.IUserService",
 "Address": "http://localhost:8802/api/svc"
 } 
]

AppRuntime.Instance.GetService<TService>()Implementation.

private static List<(string typeName, Type svcType)> svcTypeDic;
private static ConcurrentDictionary<string, Object> svcInstance = new ConcurrentDictionary<string, object>();
 
public static TService GetService<TService>()
 {
 var serviceId = typeof(TService).FullName;

 //Read service configuration
 var serviceInfo = ServiceConfonfig.Instance.GetServiceInfo(serviceId);
 if (serviceInfo == null)
 {
  return (TService)Activator.CreateInstance(GetSvcType(serviceId));
 }
 else
 { 
  var rs = GetService<TService>(serviceId + (serviceInfo.IsRemote ? "|Remote" : ""), serviceInfo.IsSingle);
  if (rs != null && rs is RemoteServiceProxy)
  {
  var temp = rs as RemoteServiceProxy;
  Temp. Address = serviceinfo. Address; // specify the service address
  }
  return rs;
 }
 }
public static TService GetService<TService>(string interfaceId, bool isSingle)
{
 //Service is not a singleton mode
 if (!isSingle)
 {
 return (TService)Activator.CreateInstance(GetSvcType(interfaceId));
 }

 object obj = null;
 if (svcInstance.TryGetValue(interfaceId, out obj) && obj != null)
 {
 return (TService)obj;
 }
 var svcType = GetSvcType(interfaceId);

 if (svcType == null)
 {
 Throw new icvipexception ($"proxy class for [{interfaceid}] not found in system");
 }
 obj = Activator.CreateInstance(svcType);

 svcInstance.TryAdd(interfaceId, obj);
 return (TService)obj;
}

//Get the implementation class of the service
public static Type GetSvcType(string interfaceId, bool? isLocal = null)
{
 if (!_loaded)
 {
 LoadServiceType();
 }
 Type rs = null;
 var tempKey = interfaceId;

 var temp = svcTypeDic.Where(x => x.typeName == tempKey).ToList();

 if (temp == null || temp.Count == 0)
 {
 return rs;
 }

 if (isLocal.HasValue)
 {
 if (isLocal.Value)
 {
  rs = temp.FirstOrDefault(t => !typeof(RemoteServiceProxy).IsAssignableFrom(t.svcType)).svcType;
 }
 else
 {
  rs = temp.FirstOrDefault(t => typeof(RemoteServiceProxy).IsAssignableFrom(t.svcType)).svcType;
 }
 }
 else
 {
 rs = temp[0].svcType;
 }
 return rs;
}

For performance impact, we can cache all current API service types when the program starts

public static void LoadServiceType()
 {
 if (_loaded)
 {
  return;
 }
 lock (_sync)
 {
  if (_loaded)
  {
  return;
  } 
  try
  {
  svcTypeDic = new List<(string typeName, Type svcType)>();
  var path = AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory;
  var dir = new DirectoryInfo(path);
  var files = dir.GetFiles("XZL*.dll");
  foreach (var file in files)
  { 
   var types = LoadAssemblyFromFile(file);
   svcTypeDic.AddRange(types);
  } 
  _loaded = true;
  }
  catch
  {
  _loaded = false;
  }
 }
 }

//Load the API service implementation in the specified file
private static List<(string typeName, Type svcType)> LoadAssemblyFromFile(FileInfo file)
{
 var lst = new List<(string typeName, Type svcType)>();
 if (file.Extension != ".dll" && file.Extension != ".exe")
 {
 return lst;
 }
 try
 {
 var types = Assembly.Load(file.Name.Substring(0, file.Name.Length - 4))
   .GetTypes()
   .Where(c => c.IsClass && !c.IsAbstract && c.IsPublic);
 foreach (Type type in types)
 {
  //Client agent base class
  if (type == typeof(RemoteServiceProxy))
  {
  continue;
  }

  if (!typeof(IApiService).IsAssignableFrom(type))
  {
  continue;
  }

  //Binding existing class
  lst.Add((type.FullName, type));

  foreach (var interfaceType in type.GetInterfaces())
  {
  if (!typeof(IApiService).IsAssignableFrom(interfaceType))
  {
   continue;
  } 
 //Binding interface and actual implementation class
  lst.Add((interfaceType.FullName, type)); 
  }
 }
 }
 catch
 {
 }

 return lst;
}

Specific API remote service proxy example


public class UserServiceProxy : RemoteServiceProxy, IUserService
 {
 private string serviceId = typeof(IUserService).FullName;

 public void IncreaseScore(int userId,int score)
 {
  return InvokeWithoutReturn(serviceId, nameof(IncreaseScore), userId,score);
 }
 public UserInfo GetUserById(int userId)
 {
  return Invoke<UserInfo >(serviceId, nameof(GetUserById), userId);
 }
}

epilogue

After the above transformation, we can easily pass through theAppRuntime.Instance.GetService<TService>().MethodXX()It is transparent to the caller to access the remote service without feeling, whether the service is deployed remotely or locally in the form of DLL dependency. The seamless docking is an inherent habit of everyone

PS: but after this transformation, there is another question left: when a client calls a remote service, it needs to manually create a service proxy (inherited from the remoteserviceproxy). Although each proxy is very convenient to write, it is only a simple two sentences mentioned in the article, but it is complicated after all. Is there a way to dynamically generate the client proxy according to the remote API interface? The answer is Yes, because this article is longer, I will continue in the next part

Attach the link to the dynamically compiled article: https://www.jb51.net/article/144101.htm

Well, the above is the whole content of this article. I hope that the content of this article has some reference learning value for your study or work. If you have any questions, you can leave a message and exchange. Thank you for your support for developepaer.