基于.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('https://www.jb51.net/') + $"/{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接入段统一入口