REST API的演变

每个开发人员都以某种方式接触到API 。 要么为一家大公司集成一个主要系统,或者使用最新的图形库生成一些精美的图表,要么直接与他喜欢的编程语言进行交互。 事实是,API无处不在! 它们实际上代表了当今Internet的基本构建块,在不同系统和设备之间发生的数据交换过程中扮演着重要角色。 从您手机上的简单天气小部件到您在网上商店中执行的信用卡付款,如果这些系统无法通过调用彼此的API相互通信,那么所有这些都是不可能的。

因此,随着连接到互联网的异构设备生态系统的不断发展,API提出了一系列严峻的挑战。 尽管它们必须继续以可靠和安全的方式运行,但它们还必须与所有这些设备兼容,从手表到数据中心内最先进的服务器。

REST来解救

用于构建此类API的最广泛使用的技术之一就是所谓的REST API。 这些API旨在提供异构系统之间通用且标准化的通信方式。 由于它们严重依赖于标准的通信协议和数据表示(例如HTTP,XML或JSON),因此很容易在大多数编程语言上提供客户端实现,从而使其与绝大多数系统和设备兼容。

因此,尽管这些REST API可以与大多数设备和技术兼容,但它们也必须不断发展。 演化的问题在于,有时您必须保持与旧客户端版本的回溯兼容性。

让我们建立一个例子。

让我们想象一个约会系统,在该系统中您具有一个用于创建和检索约会的API。 为简化起见,让我们想象一下约会对象和日期和来宾姓名。 像这样:

public class AppointmentDTO {public Long id;public Date date;public String guestName;
}

一个非常简单的REST API如下所示:

@Path("/api/appointments")
public class AppointmentsAPI {@GET@Path("/{id}")public AppointmentDTO getAppointment(@PathParam("id") String id) { ... }@POSTpublic void createAppointment(AppointmentDTO appointment) { ... }}

让我们假设这个简单的简单API可以正常工作,并且可以在允许预订和显示约会的手机,平板电脑和各种网站上使用。 到目前为止,一切都很好。

在某个时候,您认为开始收集有关您的约会系统的一些统计信息将非常有趣。 为了简单起见,您只想知道谁是预订次数最多的人。 为此,您需要将访客之间关联起来,并决定需要为每个访客添加唯一的标识符。 让我们使用电子邮件。 因此,现在您的对象模型将如下所示:

public class AppointmentDTO {public Long id;public Date date;public GuestDTO guest;
}public class GuestDTO {public String email;public String name;
}

因此,我们的对象模型稍有变化,这意味着我们将不得不在api上调整业务逻辑。

问题

evolution2-300x118

尽管使API适应存储和检索新对象类型应该是一件容易的事,但问题是您当前的所有客户端都在使用旧模型,并且将继续这样做直到更新。 可以说您不必为此担心,客户应该更新到较新的版本,但事实是您不能真正地从头到尾进行更新。 始终会有一个时间窗口,您必须保持两个模型都运行,这意味着您的api必须具有复古兼容性。

这是您的问题开始的地方。

回到我们的示例,在这种情况下,这意味着我们的API将必须处理两个对象模型,并能够根据客户端存储和检索那些模型。 因此,让我们将guestName添加回我们的对象中,以保持与旧客户端的兼容性:

public class AppointmentDTO {public Long id;public Date date;@Deprecated //For retro compatibility purposespublic String guestName;public GuestDTO guest;
}

请记住,关于API对象的一条很好的经验法则是,永远不要删除字段。 添加新字段通常不会破坏任何客户端实现(假设它们遵循忽略新字段的良好经验法则),但是删除字段通常是噩梦之路。

现在,为了维护API兼容,有几种不同的选择。 让我们看一些替代方案:

  • 复制 :单纯。 为新客户端创建一种新方法,并使旧客户端使用相同的方法。
  • 查询参数 :引入一个标志来控制行为。 诸如useGuests = true之类的东西。
  • API版本控制 :在您的URL路径中引入一个版本,以控制要调用的方法版本。

因此,所有这些替代方案都有其优缺点。 尽管复制很简单,但它可以轻松地将您的API类变成一碗重复的代码。

可以(并且应该)将查询参数用于行为控制(例如,将分页添加到列表中),但是我们应避免将它们用于实际的API演变,因为这些参数通常是永久性的,因此您不希望使用对于消费者来说是可选的。

版本控制似乎是个好主意。 它提供了一种发展API的干净方法,它使旧客户端与新客户端分离,并为您在API寿命期间发生的各种更改提供了通用基础。 另一方面,它也引入了一些复杂性,特别是如果您在不同版本上有不同的调用时。 您的客户最终将不得不通过升级调用而不是API来自己管理API的演变。 就像您没有升级库到下一个版本,而是只升级了该库的某个类。 这很容易变成版本梦night……

为了克服这个问题,我们必须确保我们的版本涵盖整个API。 这意味着我应该能够使用/ v2来调用/ v1上的每个可用方法。 当然,如果v2上存在给定方法的较新版本,则应在/ v2调用上运行它。 但是,如果给定的方法在v2中没有更改,我希望可以无缝调用v1版本。

基于继承的API版本控制

为了实现这一点,我们可以利用Java对象的多态功能。 我们可以以分层的方式构建API版本,以便较新版本可以覆盖较旧版本的方法,而对未更改方法的较新版本的调用可以无缝地回退到其较早版本。

因此,回到我们的示例,我们可以构建一个新版本的create方法,以便API如下所示:

@Path("/api/v1/appointments")    //We add a version to our base path
public class AppointmentsAPIv1 { //We add the version to our API classes@GET@Path("/{id}")public AppointmentDTO getAppointment(@PathParam("id") String id) { ... }@POSTpublic void createAppointment(AppointmentDTO appointment) { //Your old way of creating Appointments only with names}
}//New API class that extends the previous version
@Path("/api/v2/appointments")                      
public class AppointmentsAPIv2 extends AppointmentsAPIv1 {@POST@Overridepublic void createAppointment(AppointmentDTO appointment) { //Your new way of creating appointments with guests}
}

因此,现在我们有2个有效的API版本。 尽管所有尚未升级到新版本的旧客户端将继续使用v1,并且不会看到任何更改,但您的所有新客户现在都可以使用最新的v2。 请注意,所有这些调用均有效:

呼叫 结果
GET /api/v1/appointments/123 将在v1类上运行getAppointment
GET /api/v2/appointments/123 将在v1类上运行getAppointment
POST /api/v1/appointments 将在v1类上运行createAppointment
POST /api/v2/appointments 将在v2类上运行createAppointment

这样,任何想要开始使用最新版本的使用者都只需将其基本URL更新为相应的版本,并且所有API将无缝转换为最新的实现,同时保持旧的不变。

警告

出于敏锐的眼光,这种方法立即引起了警告。 如果您的API由十分之几的不同类组成,那么即使您实际上没有任何更改,较新的版本也意味着将它们全部复制为较高版本。 这是一些可以自动生成的样板代码。 仍然很烦。

尽管没有快速的方法可以解决此问题,但是使用接口可能会有所帮助。 除了创建新的实现类之外,您还可以创建一个新的带路径注释的接口,并在当前的实现类中对其进行实现。 尽管您将必须为每个API类创建一个接口,但它有点干净。 它有一点帮助,但仍然是一个警告。

最后的想法

API版本控制似乎是当前的热门话题。 存在许多不同的观点和意见,但似乎缺乏标准的最佳实践。 尽管本文并非旨在提供这样的内容,但我希望它有助于实现更好的API结构并有助于其可维护性。

最后要说的是罗伯托·科尔特斯 ( Roberto Cortez)鼓励并允许将此帖子发布在他的博客上。 这实际上是我的第一篇博文,因此请加载大炮并随意开火。 :)

翻译自: https://www.javacodegeeks.com/2015/03/rest-api-evolution.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/360688.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Oracle MAF中的LOV

我们都喜欢最强大的ADF功能值列表之一。 使用它们,我们可以在ADF应用程序中声明性地轻松构建非常复杂的功能。 一件好事是,我们在Oracle MAF中也有类似的方法。 在ADF BC中,我们在业务服务级别(基本上在实体或VO级别)定…

怎么移动矩形选框工具选中的东西_ps矩形选框工具怎么用,你值得一看的技巧...

PS是一款非常好用的图片制作软件,我们可以使用矩形选框工具,选择自己需要的区域进行操作,下面小编就教大家ps矩形选框工具怎么用,希望可以帮助到大家。操作方法01首先我们打开PS进入到主界面,如图所示。02之后我们需要…

stream 过滤俩个字段_Java8 Stream:2万字20个实例,玩转集合的筛选、归约、分组、聚合...

点波关注不迷路,一键三连好运连连!先贴上几个案例,水平高超的同学可以挑战一下:从员工集合中筛选出salary大于8000的员工,并放置到新的集合里。统计员工的最高薪资、平均薪资、薪资之和。将员工按薪资从高到低排序&…

一个JSF清单示例

这是使用JSF 2.0(JavaServer Faces)构建的示例列表应用程序。 该应用程序是待办事项列表。 该应用程序具有添加,编辑或删除列表中项目的功能。 待办事项具有名称和描述属性。 完成的应用程序的JSF页面具有: 使用h:selectOneList…

hdu 2444(二分图的判断以及求最大匹配)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid2444思路:首先要判断能否构成二分图,用bfs对当前点u染色,对u的邻接点v的颜色进行判断,如果为染色,则染色后入队列,否则,判断co…

Delta架构:统一Lambda架构并利用Hadoop / REST中的Storm

最近,一群人要求我详细介绍我为我们的书《分布式实时计算的风暴蓝图》撰写的Druid / Storm集成。 德鲁伊很棒。 风暴很大。 两者一起解决了实时维查询/聚合问题。 实际上,人们正在将其视为主流,将其称为RAD Stack ,并添加了“ La…

activiti mysql 版本_Mysql8.0.17版本不能自动创建activiti表的坑

maven项目如下:配置好数据库,和activiti的配置之后,开始执行流程部署package com.yuanqiao.first_activiti.deployment;import java.util.List;import org.activiti.engine.ProcessEngine;import org.activiti.engine.ProcessEngines;import …

Linux Kernel 多个本地信息泄露漏洞

漏洞名称:Linux Kernel 多个本地信息泄露漏洞CNNVD编号:CNNVD-201306-028发布时间:2013-06-04更新时间:2013-06-04危害等级: 漏洞类型: 威胁类型:本地CVE编号: 漏洞来源&#xff1a…

Spinner 学习笔记

Adapter 适配器//方法一&#xff1a;直接使用字符串数组//String[] sSexList new String[]{"男", "女"};//方法二&#xff1a;使用资源文件String[] sSexList getResources().getStringArray(R.array.Sexarray);//实例化一个集合适配器ArrayAdapter<S…

Java 8发布一年后,IDE和编译器尚未完全就绪

一年前&#xff0c;即2014年3月18日 &#xff0c;发布了Java SE 8&#xff0c;并伴随着通过lambda表达式和streams API实现了功能性编程的幸福。 这对于我们所有的Java生态系统都是个好消息&#xff0c;许多人已经升级到Java8。Stack Overflow已经提出了大约2500个有关Java 8的…

mysql注入提取邮件_【sql注入教程】mysql注入直接getshell

Mysql注入直接getshell的条件相对来说比较苛刻点把1:要知道网站绝对路径&#xff0c;可以通过报错&#xff0c;phpinfo界面&#xff0c;404界面等一些方式知道2:gpc没有开启&#xff0c;开启了单引号被转义了&#xff0c;语句就不能正常执行了3:要有file权限&#xff0c;默认情…

使用WildFly 8.2.0.Final,Primefaces 5.1和MySQL 5的JDBC领域和基于表单的身份验证

我会不时查看我博客上最受欢迎的内容&#xff0c;并尽力满足您的最佳需求。 因此&#xff0c;阅读我的博客是其他读者推动内容的一种方式。 另一种方法是通过评论或电子邮件与我联系。 今天&#xff0c;我将使用Primefaces修改我的JDBC Realm示例并将其更新到最新的WildFly服务…

【转】代码里的命名规则:错误的和正确的对比

原文出处&#xff1a; goyello 译文出处&#xff1a; 外刊IT评论 编程初学者总是把大量的时间用在学习编程语言&#xff0c;语法&#xff0c;技巧和编程工具的使用上。他们认为&#xff0c;如果掌握了这些技术技巧&#xff0c;他们就能成为不错的程序员。然而&#xff0c;计算…

您的Apache Camel应用程序现在包括现成的文档

几个月前&#xff0c;我在博客中发布了有关即将发布的2.15版本的信息&#xff0c;该功能包括获取有关在端点上配置的每个属性的详细信息的功能-Apache Camel&#xff0c;请向我解释这些端点选项的含义 。 我们沿着这条道路继续前进&#xff0c;今天&#xff0c;我们将其从端点…

具有Spring Boot和数据功能的Java头优先弹性搜索

在本文中&#xff0c;我将为您提供有关如何在Java项目中使用Elastic Search的简单介绍。 由于Spring Boot是开始我们项目的最简单&#xff0c;最快的方法&#xff0c;因此我选择使用它。 此外&#xff0c;我们将大量使用心爱的Spring Data的Repository Goods。 首先&#xff0…

python 表单中值为空的还需要传入么_牛掰!100行Python,自动动手打造一款多国语言翻译软件...

大家在平时的学习或者工作中&#xff0c;往往少不了要阅读外文的文献&#xff0c;或者将外文的文献翻译成中文。这时候就需要打开网页&#xff0c;然后进行搜索&#xff0c;非常的麻烦。既然是玩Python&#xff0c;小编就带领大家来打造一款多种语言翻译的软件&#xff0c;无需…

使用Visual Studio 2010 一步一步创建Powershell Module 和 Cmdlet

之前写了一个C# 调用PowerShell方法&#xff0c; 那么怎么反过来操作呢&#xff0c;也就是怎么样用C#写一个powershell命令呢&#xff1f; 现在就用C#写一个超级简单的Module和Cmdlet 1. 在VS中创建一个Library的项目 文件->新建->项目->C#->Class Library 在这里给…

休眠锁定模式– PESSIMISTIC_FORCE_INCREMENT锁定模式如何工作

介绍 在我以前的文章中 &#xff0c;我介绍了OPTIMISTIC_FORCE_INCREMENT锁定模式&#xff0c;并将其应用于将子实体版本更改传播到锁定的父实体。 在本文中&#xff0c;我将介绍PESSIMISTIC_FORCE_INCREMENT锁定模式&#xff0c;并将其与乐观的锁定模式进行比较。 相像多于不…

实用程序类与函数式编程无关

最近&#xff0c;我被指控反对函数式编程&#xff0c;因为我将实用程序类称为反模式 。 绝对是错的&#xff01; 好吧&#xff0c;我确实认为它们是一种糟糕的反模式&#xff0c;但是它们与函数式编程无关。 我相信有两个基本原因。 首先&#xff0c;函数式编程是声明性的&…

freeredius3.0 mysql_EDIUS视频采集卡 STROM 3G HD/HD SDI

EDIUS STROM 3G HD/HD SDI高清非编系统视音频采集卡STORM 3G? 适用于视频专业人士&#xff0c;满足基于SDI编辑和无带化工作流程&#xff0c;同时可以在低成本的HDMI监&#xff0f;视器上预监。基于PCIe插口类型的STORM 3G解决方案包括EDIUS?非线性编辑软件&#xff0c;3G HD…