博主曾经在「声明式RESTful客户端WebApiClient在项目中的应用」这篇博客中,介绍过.NET平台下的“Retrofit”——WebApiClient,它是一种声明式的RESTful客户端,通过动态代理来生成Http调用过程代码,而调用方只需要定义一个接口,并使用相关“注解”对接口进行修饰即可,类似的实现还有Refit,是一种比HttpWebRequest、HttpClient和RestSharp更为优雅的接口调用方式。在今天这篇博客中,我想聊聊WebApiClient中动态路由的实现与使用。
一个典型的WebApiClient使用流程如下,首先定义一个接口,并使用“注解”对接口进行修饰:
1 | public interface ISinoiovApiClient : IHttpApiClient |
接下来,调用就变得非常简单:
1 | var config = new HttpApiConfig () { HttpHost = new Uri (baseUrl) }; |
有多简单呢?简单到调用的时候我们只需要给一个baseUrl就可以了!然而,如果你真这么想的话,就太天真了!虽然现在是一个遍地都是微服务和容器的时代,可是因为RESTful风格本身的约束力并不强,实际使用中难免会出现以下情况:
1 | //测试环境 |
是的,你猜对了,实际运作过程中,测试环境和正式环境不单单会使用不同的域名,可能还会使用不同的路由,虽然,理论上两个环境的程序应该完全一样,应该使用相同的路由。这样子就让我们有一点尴尬,因为我们的路由是写在特性(Attribute)里的,这玩意儿的实例化是附着在对应的类上面的,并且在整个运行时期间是不允许修改的。所谓“兵来将挡水来土掩”,接下来,我们来考虑如何解决这个问题。
使用[Uri]
第一种思路是给接口加一个Url参数,此时,调整接口方法声明如下:
1 | /// <summary> |
这种方式可以解决问题,可我使用WebApiClient的原因之一,就是我不喜欢在客户端(调用方)维护这些地址。作为一个ApiCaller,在微服务架构流行以来,接口越来越多,逐渐呈现出爆炸式增加的趋势。当我作为一个后端工程师的时候,编写接口是件非常惬意的事情。可当我为了”全栈工程师”的虚名,去做一个面无表情的ApiCaller的时候,我是不情愿去配置这些Url的,有本事你把配置中心搭起来啊!所以,道理我都懂,But,我拒绝!
使用{foobar}
第二种思路是同样是给接口增加一个片段参数,此时,调整接口方法声明如下:
1 | /// <summary> |
这种方式和第一种方式原理一致,无非是需要配置的参数从多个变成一个。我个人更喜欢这种方式,为什么呢?可能我认为专业的Api接口会有版本的概念,类似于:
1 | //版本号路由 |
这样,我们就在无形中解决了一类问题,对于第二种形式,版本号以查询参数的方式出现,我们选择在过滤器中AddUrlQuery()
或者使用[PathQuery]
来解决。如果让我选择,我一定会选择这种方式,因为它更优雅一点吗?不,因为我懒,写程序的终究目的就是为了不写代码,就好像一个程序试图去杀死它自己的进程。
使用服务发现
第三种思路,我承认有一点赌的成份,你猜对接客户的接口的时候,会不会提供服务发现这套基础设施给你?可如果在自己的项目里有服务发现,还需要再配置每个服务的Url吗?这样想是不是觉得还不错,的确,我们在微服务架构里引入WebApiClient这种类Retrofit的库,本质上还是为了弱化服务的界限感,如果我调用一个服务和调用本地方法的体验一样,那么,这是什么呢?不用怀疑,这就是RPC(大雾)。这里,我实现了一个简单的示例:
1 | //通过Consul获取可用地址 |
当然,我说了这有赌的成份,前提是这些服务在Consul中提前注册,这一点相信大家都知道啦!WebApiClient的作者提供了类似扩展:WebApiClient.Extensions.DiscoveryClient,该扩展基于Steeltoe打造,感兴趣的朋友,可以前去了解一下。