原文:基于.net core 微服务的另类实现
基于.net core 的微服务,网上很多介绍都是千篇一律基于类似webapi,通过http请求形式进行访问,但这并不符合大家使用习惯.如何像形如[ GetService<IOrderService>().SaveOrder(orderInfo)]的方式, 调用远程的服务,如果你正在为此苦恼, 本文或许是一种参考.
背景
原项目基于传统三层模式组织代码逻辑,随着时间的推移,项目内各模块逻辑互相交织,互相依赖,维护起来较为困难.为此我们需要引入一种新的机制来尝试改变这个现状,在考察了 Java spring cloud/doubbo, c# wcf/webapi/asp.net core 等一些微服务框架后,我们最终选择了基于 .net core + Ocelot 微服务方式. 经过讨论大家最终期望的项目结果大致如下所示.
但原项目团队成员已经习惯了基于接口服务的这种编码形式, 让大家将需要定义的接口全部以http 接口形式重写定义一遍, 同时客户端调用的时候, 需要将原来熟悉的形如 XXService.YYMethod(args1, args2) 直接使用通过 "."出内部成员,替换为让其直接写 HttpClient.Post("url/XX/YY",”args1=11&args2=22”)的形式访问远程接口,确实是一件十分痛苦的事情.
问题提出
基于以上, 如何通过一种模式来简化这种调用形式, 继而使大家在调用的时候不需要关心该服务是在本地(本地类库依赖)还是远程, 只需要按照常规方式使用即可, 至于是直接使用本地服务还是通过http发送远程请求,这个都交给框架处理.为了方便叙述, 本文假定以销售订单和用户服务为例. 销售订单服务对外提供一个创建订单的接口.订单创建成功后, 调用用户服务更新用户积分.UML参考如下.问题转化
- 在客户端,通过微服务对外公开的接口,生成接口代理, 即将接口需要的信息[接口名/方法名及该方法需要的参数]包装成http请求向远程服务发起请求.
- 在微服务http接入段, 我们可以定义一个统一的入口,当服务端收到请求后,解析出接口名/方法名及参数信息,并创建对应的实现类,从而执行接口请求,并将返回值通过http返回给客户端.
- 最后,客户端通过类似 AppRuntims.Instance.GetService<IOrderService>().SaveOrder(orderInfo) 形式访问远程服务创建订单.
- 数据以json格式传输.
解决方案及实现
为了便于处理,我们定义了一个空接口IApiService,用来标识服务接口.
远程服务客户端代理
public class RemoteServiceProxy : IApiService{ ???public string Address { get; set; } ?//服务地址
???private ApiActionResult PostHttpRequest(string interfaceId, string methodId, params object[] p) ???{ ???????ApiActionResult apiRetult = null; ???????using (var httpClient = new HttpClient()) ???????{ ???????????var param = new ArrayList(); //包装参数 ???????????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; //提交请求 ???????????if (!response.IsSuccessStatusCode) ???????????{ ???????????????AppRuntimes.Instance.Loger.Error($"httpRequest error:{url},statuscode:{response.StatusCode}"); ???????????????throw new ICVIPException("网络异常或服务响应失败"); ???????????} ???????????var responseStr = response.Content.ReadAsStringAsync().Result; ???????????AppRuntimes.Instance.Loger.Debug($"httpRequest response:{responseStr}"); ???????????apiRetult = JsonConvert.DeserializeObject<ApiActionResult>(responseStr); ???????} ???????if (!apiRetult.IsSuccess) ???????{ ???????????throw new BusinessException(apiRetult.Message ?? "服务请求失败"); ???????} ???????return apiRetult; ???} ???//有返回值的方法代理 ???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.Loger.Error("数据转化失败", ex); ???????????throw; ???????} ???????return rs; ???} ???//没有返回值的代理 ???public void InvokeWithoutReturn(string interfaceId, string methodId, params object[] param) ???{ ???????PostHttpRequest(interfaceId, methodId, param); ???}}远程服务端http接入段统一入口
[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)) ???????{ ??????????//解析参数 ???????????param = JsonConvert.DeserializeObject<ArrayList>(reqParam); ???????} ????????//转交本地服务处理中心处理 ???????var data = LocalServiceExector.Exec(interfaceId, methodId, param); ???????result = ApiActionResult.Success(data); ???} ???catch ?BusinessException ex) //业务异常 ???{ ???????result = ApiActionResult.Error(ex.Message); ???} ???catch (Exception ex) ???{ ???????//业务异常 ???????if (ex.InnerException is BusinessException) ???????{ ???????????result = ApiActionResult.Error(ex.InnerException.Message); ???????} ???????else ???????{ ???????????AppRuntimes.Instance.Loger.Error($"调用服务发生异常{interfaceId}-{methodId},data:{reqParam}", ex); ???????????result = ApiActionResult.Fail("服务发生异常"); ???????} ???} ???finally ???{ ???????stopwatch.Stop(); ???????AppRuntimes.Instance.Loger.Debug($"process client request end:api/svc/{interfaceId}/{methodId},耗时[ {stopwatch.ElapsedMilliseconds} ]毫秒"); ???} ???//result.Message = AppRuntimes.Instance.GetCfgVal("ServerName") + " " + result.Message; ???result.Message = result.Message; ???return result;}
本地服务中心通过接口名和方法名,找出具体的实现类的方法,并使用传递的参数执行,ps:因为涉及到反射获取具体的方法,暂不支持相同参数个数的方法重载.仅支持不同参数个数的方法重载.
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($"找不到API接口的服务实现:{interfaceId}"); ???} ???var methods = svcType.GetMethods().Where(t => t.Name == methodId).ToList(); ???if (methods.IsNullEmpty()) ???{ ???????throw new BusinessException($"在API接口[{interfaceId}]的服务实现中[{svcType.FullName}]找不到指定的方法:{methodId}"); ???} ???var method = methods.FirstOrDefault(t => t.GetParameters().Length == paramCount); ???if (method == null) ???{ ???????throw new ICVIPException($"在API接口中[{interfaceId}]的服务实现[{svcType.FullName}]中,方法[{methodId}]参数个数不匹配"); ???} ???var paramtersTypes = method.GetParameters(); ???object instance = null; ???try ???{ ???????instance = Activator.CreateInstance(svcType); ???} ???catch (Exception ex) ???{ ???????throw new BusinessException($"在实例化服务[{svcType}]发生异常,请确认其是否包含一个无参的构造函数", 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;
服务配置,指示具体的服务的远程地址,当未配置的服务默认为本地服务.
[ ?{ ???"ServiceId": "XZL.Api.IOrderService", ???"Address": "http://localhost:8801/api/svc" ?}, ?{ ???"ServiceId": "XZL.Api.IUserService", ???"Address": "http://localhost:8802/api/svc" ?} ]
AppRuntime.Instance.GetService<TService>()的实现.
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; ????//读取服务配置 ????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; ????//指定服务地址 ????????} ????????return rs; ????} }public static TService GetService<TService>(string interfaceId, bool isSingle){ ???//服务非单例模式 ???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($"系统中未找到[{interfaceId}]的代理类"); ???} ???obj = Activator.CreateInstance(svcType); ???svcInstance.TryAdd(interfaceId, obj); ???return (TService)obj;}//获取服务的实现类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;}
为了性能影响,我们在程序启动的时候可以将当前所有的ApiService类型缓存.
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; ????????} ????} }//加载指定文件中的ApiService实现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) ???????{ ??????????//客户端代理基类 ???????????if (type == typeof(RemoteServiceProxy)) ???????????{ ???????????????continue; ???????????} ???????????if (!typeof(IApiService).IsAssignableFrom(type)) ???????????{ ???????????????continue; ???????????} ??????????//绑定现类 ???????????lst.Add((type.FullName, type)); ???????????foreach (var interfaceType in type.GetInterfaces()) ???????????{ ???????????????if (!typeof(IApiService).IsAssignableFrom(interfaceType)) ???????????????{ ???????????????????continue; ???????????????} ????//绑定接口与实际实现类 ???????????????lst.Add((interfaceType.FullName, type)); ?????????????} ???????} ???} ???catch ???{ ???} ???return lst;}
- 具体api远程服务代理示例
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); ???????}}
结语
经过以上改造后, 我们便可很方便的通过形如 AppRuntime.Instance.GetService<TService>().MethodXX()无感的访问远程服务, 服务是部署在远程还是在本地以dll依赖形式存在,这个便对调用者透明了.无缝的对接上了大家固有习惯.
PS: 但是此番改造后, 遗留下来了另外一个问题: 客户端调用远程服务,需要手动创建一个服务代理( 从 RemoteServiceProxy 继承),虽然每个代理很方便写,只是文中提到的简单两句话,但终究显得繁琐, 是否有一种方式能够根据远程api接口动态的生成这个客户端代理呢? 答案是肯定的,因本文较长了,留在下篇再续
附上动态编译文章链接. https://www.cnblogs.com/xie-zhonglai/p/dynamic_compilation_netstandard.html.
基于.net core 微服务的另类实现
原文地址:https://www.cnblogs.com/lonelyxmas/p/10226914.html