blob: 664cb8096f76fb692c46b058deff9d6fc5cb5888 [file] [log] [blame]
<!DOCTYPE html>
<html class="writer-html5" lang="en" >
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="shortcut icon" href="../../img/favicon.ico" />
<title>2.0.0 新特性介绍: 弱类型契约 - ServiceComb Java Chassis 开发指南</title>
<link rel="stylesheet" href="../../css/theme.css" />
<link rel="stylesheet" href="../../css/theme_extra.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.5.0/styles/github.min.css" />
<script>
// Current page data
var mkdocs_page_name = "2.0.0 \u65b0\u7279\u6027\u4ecb\u7ecd\uff1a \u5f31\u7c7b\u578b\u5951\u7ea6";
var mkdocs_page_input_path = "featured-topics/features/weak-type-contrast.md";
var mkdocs_page_url = null;
</script>
<script src="../../js/jquery-3.6.0.min.js" defer></script>
<!--[if lt IE 9]>
<script src="../../js/html5shiv.min.js"></script>
<![endif]-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.5.0/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
</head>
<body class="wy-body-for-nav" role="document">
<div class="wy-grid-for-nav">
<nav data-toggle="wy-nav-shift" class="wy-nav-side stickynav">
<div class="wy-side-scroll">
<div class="wy-side-nav-search">
<a href="../../index.html" class="icon icon-home"> ServiceComb Java Chassis 开发指南
</a>
</div>
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="Navigation menu">
<ul>
<li class="toctree-l1"><a class="reference internal" href="../../toc.html">目录</a>
</li>
</ul>
<ul>
<li class="toctree-l1"><a class="reference internal" href="../../index.html">概述</a>
</li>
</ul>
<ul>
<li class="toctree-l1"><a class="reference internal" href="../../start/catalog.html">快速入门</a>
</li>
</ul>
<ul>
<li class="toctree-l1"><a class="reference internal" href="../../start/design.html">设计选型参考</a>
</li>
</ul>
<ul>
<li class="toctree-l1"><a class="reference internal" href="../../build-provider/definition/service-definition.html">微服务定义</a>
</li>
</ul>
<ul>
<li class="toctree-l1"><a class="reference internal" href="../../build-provider/catalog.html">开发服务提供者</a>
</li>
</ul>
<ul>
<li class="toctree-l1"><a class="reference internal" href="../../build-consumer/catalog.html">开发服务消费者</a>
</li>
</ul>
<ul>
<li class="toctree-l1"><a class="reference internal" href="../../general-development/catalog.html">通用功能开发</a>
</li>
</ul>
<p class="caption"><span class="caption-text">多样化的通信协议功能参考</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="../../transports/introduction.html">多协议介绍</a>
</li>
<li class="toctree-l1"><a class="reference internal" href="../../transports/rest-over-servlet.html">REST over Servlet</a>
</li>
<li class="toctree-l1"><a class="reference internal" href="../../transports/rest-over-vertx.html">REST over Vertx</a>
</li>
<li class="toctree-l1"><a class="reference internal" href="../../transports/http2.html">REST over HTTP2</a>
</li>
<li class="toctree-l1"><a class="reference internal" href="../../transports/highway-rpc.html">Highway</a>
</li>
</ul>
<p class="caption"><span class="caption-text">多样化的服务注册与发现功能参考</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="../../registry/introduction.html">注册发现说明</a>
</li>
<li class="toctree-l1"><a class="reference internal" href="../../registry/service-center.html">使用服务中心</a>
</li>
<li class="toctree-l1"><a class="reference internal" href="../../registry/local-registry.html">本地注册发现</a>
</li>
<li class="toctree-l1"><a class="reference internal" href="../../registry/distributed.html">去中心化注册发现</a>
</li>
</ul>
<p class="caption"><span class="caption-text">管理服务配置</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="../../config/general-config.html">通用配置说明</a>
</li>
<li class="toctree-l1"><a class="reference internal" href="../../config/read-config.html">在程序中读取配置信息</a>
</li>
</ul>
<p class="caption"><span class="caption-text">服务治理功能参考</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="../../references-handlers/intruduction.html">处理链介绍</a>
</li>
<li class="toctree-l1"><a class="reference internal" href="../../references-handlers/loadbalance.html">负载均衡</a>
</li>
<li class="toctree-l1"><a class="reference internal" href="../../references-handlers/ratelimit.html">限流</a>
</li>
<li class="toctree-l1"><a class="reference internal" href="../../references-handlers/router.html">灰度发布</a>
</li>
<li class="toctree-l1"><a class="reference internal" href="../../references-handlers/fault-injection.html">故障注入</a>
</li>
<li class="toctree-l1"><a class="reference internal" href="../../references-handlers/governance.html">流量特征治理</a>
</li>
<li class="toctree-l1"><a class="reference internal" href="../../references-handlers/fail-retry.html">快速失败和重试</a>
</li>
</ul>
<p class="caption"><span class="caption-text">网关功能参考</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="../../edge/open-service.html">介绍</a>
</li>
<li class="toctree-l1"><a class="reference internal" href="../../edge/by-servicecomb-sdk.html">使用 Edge Service 做网关</a>
</li>
<li class="toctree-l1"><a class="reference internal" href="../../edge/zuul.html">使用 `zuul` 和 `spring cloud gateway` 做网关</a>
</li>
<li class="toctree-l1"><a class="reference internal" href="../../edge/nginx.html">nginx 网关简单介绍</a>
</li>
</ul>
<p class="caption"><span class="caption-text">安全特性参考</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="../../references-handlers/publickey.html">公钥认证</a>
</li>
<li class="toctree-l1"><a class="reference internal" href="../../security/tls.html">使用TLS通信</a>
</li>
<li class="toctree-l1"><a class="reference internal" href="../../security/shi-yong-rsa-ren-zheng.html">使用RSA认证</a>
</li>
</ul>
<p class="caption"><span class="caption-text">服务打包和运行</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="../../packaging/standalone.html">以standalone模式打包</a>
</li>
<li class="toctree-l1"><a class="reference internal" href="../../packaging/web-container.html">以WEB容器模式打包</a>
</li>
</ul>
<p class="caption"><span class="caption-text">专题文章</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="../../using-java-chassis-in-spring-boot/using-java-chassis-in-spring-boot.html">在Spring Boot中使用java chassis</a>
</li>
<li class="toctree-l1"><a class="reference internal" href="../features.html">新功能介绍系列文章</a>
</li>
<li class="toctree-l1"><a class="reference internal" href="../compatibility.html">兼容问题和兼容性策略</a>
</li>
<li class="toctree-l1"><a class="reference internal" href="../upgrading.html">升级指导系列文章</a>
</li>
<li class="toctree-l1"><a class="reference internal" href="../performance.html">性能问题分析和调优</a>
</li>
</ul>
<p class="caption"><span class="caption-text">常用配置项参考</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="../../config-reference/rest-transport-client.html">REST Transport Client 配置项</a>
</li>
<li class="toctree-l1"><a class="reference internal" href="../../config-reference/config-center-client.html">Config Center Client 配置项</a>
</li>
<li class="toctree-l1"><a class="reference internal" href="../../config-reference/service-center-client.html">Service Center Client 配置项</a>
</li>
<li class="toctree-l1"><a class="reference internal" href="../../config-reference/kie-client.html">ServiceComb Kie Client 配置项</a>
</li>
</ul>
<p class="caption"><span class="caption-text">常见问题</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="../../question-and-answer/faq.html">FAQ</a>
</li>
<li class="toctree-l1"><a class="reference internal" href="../../question-and-answer/question_answer.html">Q & A</a>
</li>
<li class="toctree-l1"><a class="reference internal" href="../../question-and-answer/interface-compatibility.html">微服务接口兼容常见问题</a>
</li>
</ul>
</div>
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
<nav class="wy-nav-top" role="navigation" aria-label="Mobile navigation menu">
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="../../index.html">ServiceComb Java Chassis 开发指南</a>
</nav>
<div class="wy-nav-content">
<div class="rst-content"><div role="navigation" aria-label="breadcrumbs navigation">
<ul class="wy-breadcrumbs">
<li><a href="../../index.html" class="icon icon-home" alt="Docs"></a> &raquo;</li>
<li>2.0.0 新特性介绍: 弱类型契约</li>
<li class="wy-breadcrumbs-aside">
</li>
</ul>
<hr/>
</div>
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
<div class="section" itemprop="articleBody">
<h1 id="200">2.0.0 新特性介绍: 弱类型契约</h1>
<p>"以契约为中心" 是 Java Chassis 的核心设计理念。 “契约” 扮演着用户与开发者,开发者与开发者之间的沟通桥梁。
举几个简单的例子。</p>
<p>开发者发布了一个微服务A,同时发布了一份契约文件:</p>
<pre><code class="language-yaml">swagger: '2.0'
info:
title: rest api
version: 1.0.0
basePath: /controller
produces:
- application/json
paths:
/add:
get:
operationId: add
parameters:
- name: a
in: query
required: true
type: integer
format: int32
- name: b
in: query
required: true
type: integer
format: int32
responses:
&quot;200&quot;:
description: add numer
schema:
type: integer
format: int32
</code></pre>
<p>微服务A的用户可以基于这份契约,使用浏览器访问<code>add</code>接口,也可以在自己的微服务B中,使用<code>RestTemplate</code>
调用<code>add</code>接口,用户不需要知道微服务A的实现细节,也不需要依赖任何微服务A提供的接口,微服务B保持完全
与微服务A独立。只要契约不变,微服务B就可以保持不变。</p>
<p>作为微服务A的其他开发者,可以基于契约开发治理功能,不需要知道<code>add</code>接口的实现细节。比如可以给<code>add</code>
接口增加流量控制 <code>servicecomb.flowcontrol.Provider.qps.limit.[ServiceA].[Controller].[add]=1000</code>
限制其流量为1000 TPS;还可以基于 <code>add</code> 接口的参数做灰度发布,在灰度发布代码里面可能会存在下面的代码片段:
<code>invocation.getSwaggerArgument("a")</code> 来获取参数值。</p>
<p>"以契约为中心"是保持微服务"独立性"非常重要的手段,“独立开发、独立交付”是微服务被引入,提升软件工程能力
最重要的价值。</p>
<p>本文重点介绍2.0.0版本引入的“弱类型契约”,以及它与之前“强类型契约”之间的差别与联系。</p>
<h2 id="vs">弱类型契约 vs 强类型契约</h2>
<p>弱类型契约和强类型契约的共同点都是"以契约为中心",因此弱类型契约并没有改变 Java Chassis 的整体设计理念,
而是在实现层面发生了一些变化,进而影响到部分开发体验。2.0.0之前的版本是强类型契约。产生强类型契约和一些技术背景
有关系,讨论的起点可以从 Java Chassis Highway 的 ProtoBuffer 讲起。 </p>
<p>熟悉 ProtoBuffer 的开发者都知道, ProtoBuffer 主要被用于 gRPC, 其他处理 ProtoBuffer 编解码的库还有 ProtoStuff。
gRPC 需要写 IDL 文件, 然后根据 IDL 生成运行时的代码,在gRPC的运行过程中,处理编解码的类在编译时间就已经确定,无法
更改。ProtoStuff 提供的类库比 gRPC 更加灵活,对于同样的 IDL 文件,可以在运行时指定不同的类进行序列化和反序列化。
无论如何,在一个微服务里面,如果存在一个契约(IDL文件),那么必须在编译时就确定这个契约对应的类是什么。这个类可以
有一个,也可以有多个,但是必须在编译的时候确定类的类型。</p>
<p>强类型契约可以定义为:在一个微服务里面,契约必须存在一个或者多个编译时确定的类型与它对应。在2.0.0版本之前,这个对应
的类型是在契约文件里面指定的,比如<code>x-java-interface: org.apache.servicecomb.demo.controller.Controller</code> 。 开发者
可能注意到在消费端代码,可以不依赖任何提供端的类,这是因为消费端的代码会根据契约描述,采用 <code>javassist</code> 工具动态生成
一个类型与契约对应,不同版本的契约需要生成不一样的类型,如果灰度环境版本过多,就可能导致内存过大的问题,特别是在边缘
(Edge Service)服务里面。</p>
<p>强类型契约的一个外在体现就是下面的代码:</p>
<pre><code class="language-java">Person result = restTemplate.postForObject(&quot;/getPerson&quot;, null, Person.class);
</code></pre>
<p>可能抛出 <code>ClassCastException</code> 异常。 因为 <code>getPerson</code> 的返回值的类型在编译时已经确定, 如果返回值 Person 类型
与编译时的类型不一样,就会报告 <code>ClassCastException</code> 异常。</p>
<p>了解了强类型契约后,弱类型契约的定义就很明显了: 在一个微服务里面,契约可以存在一个或者多个编译时确定的类型与它对应,
也可以不存在编译时确定的类型与它对应。 引入弱类型契约后,下面的代码:</p>
<pre><code class="language-java">Person1 result = restTemplate.postForObject(&quot;/getPerson&quot;, null, Person1.class);
Person2 result = restTemplate.postForObject(&quot;/getPerson&quot;, null, Person2.class);
</code></pre>
<p>都不会报告 <code>ClassCastException</code> 异常,只需要 <code>Person1</code><code>Person2</code> 的定义都能够和契约对应。 由此可以看出,弱类型
契约在使用方式上更加灵活,去掉了动态创建类的过程,降低了内存占用,缩短了微服务启动时间。</p>
<h2 id="_1">利用弱类型契约增强写代码的灵活性</h2>
<p>弱类型契约不要求提供者与消费者使用一样的类型,在代码书写上提供了很大的方便。比如提供者有如下接口:</p>
<pre><code class="language-java">@RestSchema(schemaId = &quot;weakSpringmvc&quot;)
@RequestMapping(path = &quot;/weakSpringmvc&quot;, produces = MediaType.APPLICATION_JSON_VALUE)
public class WeakSpringmvc {
@GetMapping(path = &quot;/diffNames&quot;)
@ApiOperation(value = &quot;differentName&quot;, nickname = &quot;differentName&quot;)
public int diffNames(@RequestParam(&quot;x&quot;) int a, @RequestParam(&quot;y&quot;) int b) {
return a * 2 + b;
}
@GetMapping(path = &quot;/genericParams&quot;)
@ApiOperation(value = &quot;genericParams&quot;, nickname = &quot;genericParams&quot;)
public List&lt;List&lt;String&gt;&gt; genericParams(@RequestParam(&quot;code&quot;) int code, @RequestBody List&lt;List&lt;String&gt;&gt; names) {
return names;
}
@GetMapping(path = &quot;/genericParamsModel&quot;)
@ApiOperation(value = &quot;genericParamsModel&quot;, nickname = &quot;genericParamsModel&quot;)
public GenericsModel genericParamsModel(@RequestParam(&quot;code&quot;) int code, @RequestBody GenericsModel model) {
return model;
}
@GetMapping(path = &quot;/specialNameModel&quot;)
@ApiOperation(value = &quot;specialNameModel&quot;, nickname = &quot;specialNameModel&quot;)
public SpecialNameModel specialNameModel(@RequestParam(&quot;code&quot;) int code, @RequestBody SpecialNameModel model) {
return model;
}
}
</code></pre>
<p>而消费者只需要访问其中一个接口diffNames,只需要定义一个非常简单的接口:</p>
<pre><code class="language-java">interface DiffNames {
int differentName(int x, int y);
}
@RpcReference(microserviceName = &quot;springmvc&quot;, schemaId = &quot;weakSpringmvc&quot;)
private DiffNames diffNames;
</code></pre>
<p>其中接口名称是和契约里面的接口名称一致 <code>differentName</code>, 而不是和服务端的代码一致 <code>diffNames</code>。 契约包含 <code>x</code>
<code>y</code> 两个参数, 并且契约的参数是顺序无关的, 下面的代码也是可以访问同一个接口的:</p>
<pre><code class="language-java">interface DiffNames2 {
int differentName(int y, int x);
}
@RpcReference(microserviceName = &quot;springmvc&quot;, schemaId = &quot;weakSpringmvc&quot;)
private DiffNames2 diffNames2;
</code></pre>
<p>需要注意的是,2.0.0 版本要求保留参数名称, 在编译代码的时候,需要加上 -parameters 编译参数。</p>
<p>开发者还可以通过 RestTemplate 调用这个接口:</p>
<pre><code class="language-java">restTemplate.getForObject(&quot;cse://springmvc/weakSpringmvc/diffNames?x=2&amp;y=3&quot;, Integer.class)
</code></pre>
<p>或者采用 <code>InvokerUtils</code> 调用: </p>
<pre><code class="language-java">Map&lt;String, Object&gt; args = new HashMap&lt;&gt;();
args.put(&quot;x&quot;, 2);
args.put(&quot;y&quot;, 3);
InvokerUtils.syncInvoke(&quot;springmvc&quot;, &quot;weakSpringmvc&quot;, &quot;differentName&quot;, args);
</code></pre>
<h2 id="_2">弱类型契约的治理功能</h2>
<p>弱类型契约在 <code>Invocation</code> 里面提供了独立的方法,让开发者开发治理功能更加容易。 </p>
<pre><code class="language-java"> public Map&lt;String, Object&gt; getInvocationArguments() {
return this.invocationArguments;
}
public Map&lt;String, Object&gt; getSwaggerArguments() {
return this.swaggerArguments;
}
public Object getInvocationArgument(String name) {
return this.invocationArguments.get(name);
}
public Object getSwaggerArgument(String name) {
return this.swaggerArguments.get(name);
}
</code></pre>
<p><code>getSwaggerArguments</code> 始终获取的是和契约对应的参数,如果契约为 <code>x</code><code>y</code> 两个 query 参数, 那么得到的参数就是
包含 <code>x</code><code>y</code> 的 Map 。 <code>getInvocationArgument</code> 获取的是实际类型参数, 在服务提供者,这个参数列表的个数和类型
和实际的 Method 的列表对应, 比如可能包含 <code>InvocationContext</code> , <code>HttpServletRequest</code> 等注入参数。 还有很多情况,
契约参数和类型参数不对应,比如聚合参数的情况,契约参数是多个 query 参数, 而类型参数是一个 POJO; 再比如 POJO 接口
定义的时候, 类型参数可能是多个, 而契约参数只有一个 body 参数。 在服务消费者,如果用户采用 POJO 方式调用服务提供者,
两个接口的返回的值与服务提供者类似,存在语义差别;如果采用 RestTemplate 或者 InvokerUtils 调用, 那么两个接口返回的
内容一样,都是契约参数。在边缘服务, 两个接口返回的都是契约参数。 这种行为体现了弱类型契约的语义: 是否存在编译时
类型与契约对应。</p>
<h2 id="_3">弱类型契约带来的一些变更</h2>
<p>总体而言,对于 <code>REST</code> 通信模式, 弱类型契约不仅增强了写代码的灵活性, 还完整保留了强类型契约的写代码方式,几乎不
存在用户需要感知的变更。 对于 <code>HIGHWAY</code> 通信模式, 由于底层采用 ProtoBuffer 编码, 而 ProtoBuffer 天然就是一种
强类型契约的编解码过程, java-chassis 为了支持弱类型契约, 做了大量努力, 在一些边界条件处理上与弱类型契约存在
变更,两个版本的编解码是不兼容的,需要同时升级提供者和消费者。 在编码方式上,差异主要体现在对于缺省值的处理,对于
<code>null</code> 的处理等问题上, 详细参考<a href="../upgrading/1_3_0T2_0_0.html">1.3.0 升级 2.0.0指导</a></p>
</div>
</div><footer>
<hr/>
<div role="contentinfo">
<!-- Copyright etc -->
</div>
Built with <a href="https://www.mkdocs.org/">MkDocs</a> using a <a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>.
</footer>
</div>
</div>
</section>
</div>
<div class="rst-versions" role="note" aria-label="Versions">
<span class="rst-current-version" data-toggle="rst-current-version">
</span>
</div>
<script>var base_url = '../..';</script>
<script src="../../js/theme_extra.js" defer></script>
<script src="../../js/theme.js" defer></script>
<script src="../../search/main.js" defer></script>
<script defer>
window.onload = function () {
SphinxRtdTheme.Navigation.enable(true);
};
</script>
</body>
</html>