户故事拆分是敏捷交付团队的日常做法。但是以我的经验,执行起来真的很困难。在本文中,我将所学到的有关故事拆分的所有内容汇总在一起。
我认为用户故事是范围的单位,是交付的单位。
重要的是,用户故事向他人传递了有用的(或有价值的)信息。在IT环境中,“他人”通常指的是使用系统的人(尽管有时是另一个利益相关者,想以某种方式限制用户,例如保护系统免受未经授权的访问)。
因此,通常从用户角度以“作为……我可以……以便于……”格式描述用户故事,从而迫使交付团队始终专注于用户正在试图达成的目标及其原因。
注意:术语“用户故事”通常用于两个有微妙差异的场景。
如上所述,我通常用它来表示要交付的范围单位,例如“我们已经在此Sprint中交付了故事X,Y和Z”。
但是,它也广泛地被用来指代这些范围单位的描述 -“ 尽可能……我可以……如此……”范式。
在本文中,我使用术语用户故事来指代范围本身,而术语用户故事描述则指的是这些范围单元的说明。当我谈论故事拆分时,我说的是将范围项拆分为较小的范围项,而不是将范围项的描述拆分为较小的说明!
根据INVEST 准则,用户故事应为:
以我的经验,这通常没有那么简单 - 拆分故事以使它们“足够小”通常会在故事之间引入依赖关系,而单个故事往往本身并没有价值,只有一定数量的相关故事一起,它们才有价值去交付。
因此,我倾向于将INVEST属性视为准则,而不是无可争议的法律。一些属性更适用于史诗(大故事),而另一些属性更适用于小故事。
对于所有故事而言,我的一条规则是:无论故事多大,它们都必须提供用户可见的内容。这等同于垂直故事切分,后面会详细介绍。
拆分故事最典型的原因是将它们分成几小块 - 足够小以在一次冲刺中交付其中的几个。你怎么吃大象?一次一小块。
我认为,还有另一个原因同等重要(如果不是更重要的话),并且可能不太为人理解,就是与帕累托原理(也称为80:20法则)有关。
80:20法则基本是说你可以在20%的时间内完成80%的工作。换句话说,完成最后20%的工作需要80%的时间。它反映了一个事实,即大多数工作都涉及许多“花哨的工作”,这些工作需要很长时间才能完成,因此尽管感觉您快要完成了,但实际上并不是。
在软件交付领域,工作的最后20%通常代表替代流程 - 异常,意外或错误情况。通常,这些高付出的“怪异碎片”中的相当部分都是价值很低的 - 它们处理的神秘场景通常会在蓝色月光下偶尔出现一次。
拆分故事使我们有机会将高价值的东西与低价值(高付出)的东西分开。一旦故事被拆分,并且子故事被放置在产品待办事项列表中,则在重新确定优先级的对话期间,低价值故事会自然地过滤到待办事项列表的底部。
这样做的好处是,我永远不需要与产品负责人就是否应该处理某个特殊情况争论不休。我可以将其放在待办事项列表上,并让他们相应地对其进行优先级排序。项目发起人迟早会花费时间在项目时间分配上,低价值的东西将永远无法交付(也称为“修剪尾巴”)。
拆分故事时,我会尽量牢记这两个目标:将它们缩小,然后剥离低价值的部分。
通常会将大型故事称为史诗。
我的经验是,可能有必要对史诗(大故事)进行多次拆分,然后才能找到适合开发团队的“大小适中”的故事。这取决于原始(史诗)故事的大小,开发团队希望这些故事的大小,以及我需要拆分多少次才能分离出所有低价值内容。
因此,我将故事拆分视为一种迭代活动 – 一个大故事拆分为两个或多个子故事,然后可以进一步将每个子故事拆分为多个子故事,依此类推,直到每个故事都变得足够小为止。这并不是说我需要事先拆分所有的故事。我只在适当的时候拆故事,以后在需要时再拆。关键是最终会有故事的层次结构,层次结构可以有很多层,并且并非所有分支都具有相同的深度。例如:
一些团队/方法/工具定义了故事的分类法,在层次结构中具有固定数量的级别。例如:
我不喜欢这种方式。我的经验是,故事层次结构并不总是整齐地落入固定的层级,因此团队需要花太多时间思考,“这个故事是史诗级的还是仅仅是功能性?”事实上是哪个层级并没有关系。
我更喜欢遵从迈克·科恩(Mike Cohn)的建议:一切都是故事。如果我分解一个故事,我会得到……一些故事。如果我有一个故事,感觉有点大,可以拆分,或者已经拆分了,可以将其称为史诗,但这仍然是一个故事。
一个史诗 是一个用户故事,感觉大到足以进行分割,或已经被分割。
重要的是,如上所述,无论故事大小如何,它仍然提供用户可见且对利益相关者有用的内容,因此我仍可以用“作为……我可以……这样……”的格式来编写其摘要。
您如何知道故事“足够小”?
我的经验是,这取决于你的交付团队以及冲刺周期。最近,我一直在两周一个冲刺的团队中工作,开发人员希望故事小到可以在每个冲刺中交付8-12个故事。他们希望能够最多用几天来发布一个故事。
较小的故事可以提高工作效率和动力 – 团队成员可以一次专注于一件小事,并很快完成它 – 每天回家时都可以完成某件事,或者是有希望结束。小的故事还有助于更好地进行资源计划 - 故事可以更轻松地在团队成员之间分配,并且更容易解决团队成员的缺席/离开。
将故事拆到如此程度通常意味着将其分解到可以被视为或者是独立的,或者是对最终用户有价值的程度 – 用户价值通常只有在交付了许多相关的、相互依赖的故事时才能得到。正如我上面提到的,很难让一个故事同时具备所有INVEST属性。
在我工作的每个团队中,我们都通过反复试验找到了理想的故事大小。方法如下:我会准备一些故事(已经有了BDD草案,并在适当的创建了一些线框或HTML原型)。我会安排一次三方会议,与团队一起逐一地过故事。对于每个故事,在我要求他们进行估算之前,我先询问他们是否认为故事足够小。如果没有,我们会去找一种拆分方法。经过几次这样的讨论后,我会感觉到什么是一个故事的“合适大小”,并且通常会以适当大小的故事进入三方会谈。具有讽刺意味的是,随着团队的成熟和工作效率的提高,他们有时会觉得现在的故事太小了,最终可能会合并以前拆开的故事!
对此的简单答案是:Just In Time。
我需要大小适中的故事来进入下一个冲刺/增量。或者,如果我正在用看板,则只需要大小适中的故事来让所有开发人员忙起来。
我按优先顺序分析故事,包括拆分。因此,如果故事的优先级较低,那么它可能仍然很大,因为我尚未对其进行任何分析。
因此,我希望产品积压的顶部附近的故事很小,而底端附近的故事会较大。我的待办事项应如下所示:
(顺便感谢Ken Rubin的图)
其实真实情况并非如此。请记住,我拆分故事的动机之一是拆分低价值的,并让它们过滤到待办事项的底部。每次我分解一个故事时,重要的是要将子故事与其余待办事项重新排序,请确保这种过滤效果的真实发生。因此,在我的待办列表底部可能还会有一些小故事 – 这些是我已经分解的那些低优先级故事。
在分解故事之前,我首先需要做一些分析工作来理解这个故事。否则,我对它的了解不足以支撑如何拆分它。
实际上,我有一个相当结构化的流程来进行分析工作。实际上,它是如此结构化,我给它起了一个名字:Business Analysis Designer Method(BADM)。下图总结了这一过程:
不要上当,BADM 不是瀑布方法。相反,每个阶段依次针对要传递的单个故事执行。
我并不是建议每个人都应该使用BADM,但是在拆分故事之前花一些时间来理解故事的范围绝对是一个好主意。
如上所述,我仅有一个分割故事的黄金法则。当我拆分故事,每个子故事都必须提供一些用户可见的内容。当我说“用户可见”时,我的意思是在系统的一个用户或系统界面上可以观察到(一个用户当然可以是另一个系统)。
这才是问题所在。当出现“太大”的用户故事时,开发团队的第一个直觉通常是按照体系结构划分,一名开发人员进行数据库架构更改,另一个编写中间层业务或控制器逻辑,第三个人变更用户界面。
这个故事被“水平地”切分到了架构的各个层次。这种方法存在一些问题:
因此,首选的方法是“垂直”分割故事,每个故事通过每个(相关)架构层传递一小段变更。这样,我们在每个故事交付后都有一些(较小的)用户收益,我们可以测试每个故事,并且可以更快地确定任何体系架构问题。
垂直切片故事时,请注意:
某些故事可能仅影响表示层(例如,更改字段或按钮标签),而不是对所有架构层进行切片。
有些故事会更改系统界面,而不是用户界面。它们仍然是“用户可见的”,但是在这种情况下,系统的用户是另一个系统,而不是人类用户。
一些故事是无功能的,例如性能或安全性增强。没关系,响应时间的改善是用户可见的。
有些故事是事件触发的(批处理)过程,它们会影响系统状态(即它们会更改数据库中的数据),但在发生时却无法通过任何用户或系统界面看到(尽管稍后会看到其效果,下次查询数据)。这是“用户可见”规则的一个特殊例外,允许用户佩戴X射线眼镜以观察系统状态的变化!同样,故事是可以测试的,但是测试人员需要在数据库中探测一下才能看到更改。
还要注意,一旦故事“足够小”,开发团队可能仍会决定将其分为“水平”部分:数据库变更,UI修改等。这完全没问题,将故事分解为任务 (特别是开发任务),有些团队会在冲刺计划时这样做,以帮助他们估算故事的工作量和/或在团队成员之间分配工作。不同之处在于,你需要理解并且有预期,除非所有开发任务都完成,故事才算完成(甚至才能进行测试)。范围或交付的单位是故事,而不是任务。
我只在这里提供了垂直故事切片的摘要。有很多非常好的文章更详细地进行了介绍,例如Ned Kremic撰写的这篇文章。
如上所述,垂直故事切片就是将故事分成越来越小的用户可见的更改。每个故事,无论多么小,都会为用户带来有价值的东西,这有助于他们实现某些目标。垂直故事切片的另一种方法是用户目标分解。通过拆分故事,我将用户目标拆分为子目标。
我们用一个例子来说明。假设我有一个用户故事,如下所示:
注册
作为不需要的东西的所有者,
我可以注册在线拍卖网站
,以便可以出售不需要的东西
该故事的用户目标是 注册在线拍卖网站。好处是 卖掉我的东西。但是这个故事太大了,我想把它分开。所以我这样分割它:
注册–输入详细信息
作为不需要的物品的所有者,
我可以输入我的注册详细信息(姓名,电子邮件地址等)
以便可以注册在线拍卖网站
注册–提交详细信息
作为不需要的物品的所有者,
我可以提交我的注册详细信息
,以便可以注册在线拍卖网站
我创建了两个子目标– 输入我的注册详细信息并提交我的注册详细信息。在这种情况下,后者取决于前者-我必须输入我的详细信息,然后才能提交它们。更重要的是,一旦我同时达到了两个子目标,我就实现了我的初衷– 注册在线拍卖网站。然后,我可以进一步细分子故事,直到我的故事“足够小”为止。我最终会得到故事的层次结构和目标的层次结构。
尤其要注意的是,子故事的“ so that”陈述(利益)就是原始故事的“ I can”陈述(目标)。换句话说,收益确实是更高层次的目标。一旦意识到这一点,编写子故事的“so that”部分就变得容易得多,它们只是父故事(即父目标)的“I Can”部分。
最好这样解释用户故事描述模板:
作为<用户>,
我可以<目标>
,以便<更高级别的目标>
也可以得出结论,我最初目标的利益– 销售我的东西 –实际上只是一个更高级别的目标。反过来,这又是更高级别目标的子目标,可以赚钱。反过来又是购买食物的一个子目标,然后是 供养我的家人,再然后 让我的家人活着。依此类推,直到您最终陷入上一级目标的无限循环,或者陷入诸如“我们存在的意义是什么”这样的哲学问题中……
由于用户目标的层次结构可能是无限的(向上和向下),因此确实存在迷失的风险。Alistair Cockburn 在其最出色的著作《写作有效用例》中很好地解释了目标分解,尤其是他通过识别和命名三个特定的目标等级来建立了隐喻锚。他在书中谈的是用例,但该理论同样适用于用户故事。
最重要的目标级别是用户目标级别,也称为海平面或蓝色。在此级别上,一个用户可以在单个活动中实现(或未能实现)单一目标,例如“列出要出售的商品”。您可以通过询问目标是否通过“咖啡时间测试”来检查这个目标是否为用户目标:实现目标后,您是否有足够的理由来休息一下喝杯咖啡?
高于用户目标级别的是摘要级别,也称为云/风筝级别或白色。摘要级别的用户目标是相关用户级别目标的集合,例如“管理窗口小部件”细分为“创建窗口小部件”,“视图窗口小部件”,“编辑窗口小部件”和“删除窗口小部件”。
低于用户目标级别的是子功能级别,也称为水下级别或靛蓝。子功能级别的目标本身对用户无直接的价值,它们是实现用户级别目标的步骤,例如,“输入商品名称”是“列出待售商品”的子目标。“登录”是常见的子功能级别目标,登录本身并不能实现任何目的,它通常是实现其他目标的前提。
Cockburn非常有意地选择了海平面/云层/水下隐喻。天空很高,海洋很深,相应地有许多嵌套级别的云层目标和水下目标(并且有许多白色阴影和许多靛蓝阴影)。但是只有一个海平面——用户目标是用户目标,应该非常清晰地定义。需要澄清的是,我并不是在提倡三层固定的用户故事层次结构,我早些时候已经对此表示了反对。我只是说,识别海平面在用户故事层次结构中的位置对于跟踪交付用户价值很有用。
有许多久经考验的方式来垂直剖析故事。多年来,我注意到我倾向于按特定顺序应用各种技术。有些技术适用于较大的故事,而其他技术一旦当故事变小就变得更加趁手。因此,我将按通常应用它们的顺序来介绍各种技术。请注意,这不是一成不变的规则。对于给定的故事,并非所有技术都适用,并且不一定以相同的顺序使用。我可能还会多次使用某一种技术。
开始了…
对于任何给定的故事,我想做的第一件事就是将其分为两部分:一部分交付功能本身,另一部分交付该功能的NFR(非功能需求)。
拆分的主要目的是使团队专注于交付功能,而不会被NFR分散注意力。在项目早期尤其重要,此时有很多尚未解决的架构问题,事先让他们都一一解答会让事情变慢。
以下是您可以考虑推迟来关注的NFR列表:
所有这些事情都具有分散团队注意力的可能,使他们无法快速交付简单的东西(刚好可以正常工作),并且可以随后基于(重构)这些东西以在适当的时候交付各种NFR。拆分NFR并对团队说:“我们将研究NFR,但现在不必担心它们”。也许我们甚至可以在不(正式地)考虑所有NFR的情况下提供MVP。这取决于我的MVP是公开发行还是有限定向用户组的私人Beta版。
我通常一开始将NFR分解为一个单一的故事,称为“ Project X NFR”或“ Feature X NFR”。在适当的时候,当我们确实 需要更详细地研究NFR时,我才可能将这个NFR故事进一步细分为各个NFR类别(性能,可用性,安全性等),并一一列举,当然是按严格的优先级顺序。
最终,对于每个后续故事,我们可能会将相关的NFR纳入“完成的定义”中。但这是另一篇文章要说明的事情。
恰好在“适当时候”发生,是一个很好的平衡。我们不想太早地在体系架构上受挫,但是我们也不想太晚去考虑它,否则我们可能会承担过多的技术债务,而且还要承担体系架构风险。同样的,判断是伴随经验而来的,从来不会有一个唯一简单的答案,多年来,我见到的过度设计的系统和设计不完善的系统一样多。
这是拆分NFR的特殊情况。我上面提到的NFR之一是跨浏览器/平台的支持,如果我的系统具有用户界面,并且打算支持多个渠道(例如,台式机,平板电脑,移动设备)或多个平台(例如,Windows,Mac,iOS,Android,各种智能电视),那么首先集中精力在这些渠道/平台其中的一个是更有意义的,显而易见应该选择我们认为大多数用户拥有的渠道/平台。
但是,与其他NFR一样,这又是一个平衡:在项目中的什么时候开始考虑其他平台?当然,没有正确的答案,这取决于具体情况。
我从事的一些项目具有公司(或政府)标准的UI框架,该框架已经被设计为可响应的(即在多个平台/设备上工作)。显然,从一开始就使用标准框架是有意义的,前提是它相对稳定并且不会增加过多的开销或学习曲线。即使我们不选择这样做,我们仍然可以选择推迟正式开始跨平台的合规性。这里有一个细微的差异:推迟正式合规,是说我们还不会进行跨平台系统测试。跨平台测试可能会非常耗费人力,通常最好在发布前不久进行一次,而不是在每个故事中都做一遍。我们的测试人员现在可以专注于功能测试,并且我们可以更快地进展。
一些故事服务于多样化的用户社群。我在这里没有太多谈论国际化,而是在考虑用户类别。
例如,在最近的项目中,我们的用户分为以下几类:
不要让我开始聊英国脱欧,这是另一个故事(呵呵)。
要点是,这三个类别的功能都不相同。英国用户必须采用一种方法进行注册,欧盟用户采用第二种方法进行注册,第三国用户则采用另一种方法进行注册。
因此,我们决定首先关注英国用户。然后我们发现了另一个分类:
同样,每个类别的功能都不同。因此,我们决定首先关注英国组织。然后我们发现了另一个分类:
信不信由你,这些子类别的注册规则又再次不同。我们首先关注英国注册公司,并为他们提供了完整的注册过程(尽管我们用稍后将介绍的其他技术进一步简化了注册过程)。然后,我们开始按照业务优先级顺序添加其他用户类型。再次回到英国脱欧——如果英国很快退出欧盟,我们将不会区分欧盟还是第三国用户,因此我们将欧盟用户放在优先级较低的位置。
最后,我们将用户群细分为大约15个用户类型,并针对大约8个独特的用户旅程进行注册。为第一种用户类型交付功能需要大量工作,随后添加的每种额外用户类型变得越来越容易。但是,如果我们尝试一次全部完成这些任务,我认为任务将是无法达成的。
到目前为止,我们已经拆分了各种NFR,因此我们可以先关注功能,然后再按用户类型进行拆分,以便专注于为单个用户组提供服务。
在接下来的拆分中,我们回到前面讨论的“三个命名的目标级别”的概念。具体来说,我们希望将故事分解成可以通过“咖啡时间测试”的单个用户目标(“海平面”目标),这些目标可以由一个用户一次活动就可以实现,而完成后则可以享受咖啡时间。
一个非常常见的例子是将数据维护故事分为其CRUD组件,例如:
变成
另一个常见的例子是一个涉及多个角色的故事。例如:
可能成为:
摘要级别目标还有无数其他类型,可以细分为用户目标。
子故事通常遵循逻辑顺序,你必须能够创建一个小控件才能查看它,并且您可能想先查看一个小控件,然后再对其进行更新或删除。您必须先请求发表文章才能获得批准。但这并不一定意味着故事必须按顺序交付,完全有可能通过后门来创建窗口小控件(通过直接数据库加载),因此,如果查看窗口小控件是价值最高的子故事,则可以这样做。
到目前为止,我们的故事处于用户目标级别:可以由一个用户一次活动就可以实现。作为美好的一天,对我们的用户来说一切都会很美好。他们将执行操作、输入数据并实现他们的目标。这就是我们所说的Happy Path方案。会有许多方法来实现目标,因此可能会有多条快乐的路径。
但是事情可能不会那么顺利,他们可能输入了无效数据,或者他们执行了不适当的操作,或者他们找不到正在搜索的数据,并且他们可能未达到目标。在任何情况下,事情都可能会出错,因此我们将其称为替代流程。
例如,
可能成为
另一个例子:
可能成为
这里的关键点是,某些替代流程的优先级可能较低。例如,处理无效的用户ID或密码是高优先级,但是在三次失败尝试后锁定帐户可能不是很高。
对于给定的用户级别故事,并非总是很清楚所有替代流程是什么。为了找到它们,为主要的欢乐路径写出相应的步骤是一个非常好的主意。例如:
步骤4提出了一个问题:如果登录详细信息无效,该怎么办?我们找到了替代流程。
步骤5提出了一个问题:如果账户被锁定怎么办?我们找到了另一种替代流程。
如果您在想拆分替代流程时束手无策,绝对应该阅读 Alistair Cockburn的书。
在技巧5中,我将故事分为个人流程 - 欢乐路径和替代流程。我们很可能会先专注于交付主要的欢乐路径。但是在我们这样做之前,我将先看一下它,看看是否有我可以先做的更简单的版本。
例如,假设我们正在构建一个注册功能,而我们想要捕获的一个信息就是用户的生日。我们可以将其实现为日期选择器,并弹出一个漂亮的日历。但是我们也可以将其作为一个简单的文本输入字段来完成,这可能会更快,更容易构建。从而:
变成
我有时称其为“铜镀层”,而不是“金镀层”,对于MVP来说已经足够了。当然我们可以做得更好,如同前文阐述的,在待办事项优先级的驱动下,是否以及何时使它变得更好,取决于与其他的待办事项相比,我们对日期选择器的重视程度。
这是解决团队内部争执于如何准确交付特定功能的一个好技巧。您将流程分解为“最低限度的最低要求”(即MVP),然后为每个层次的要求分别建立一个故事。(产品负责人)可能会认为某些功能要求是必不可少的 - 没问题,我们在MVP故事之后不久就做。其他人会将其放进待办列表,以待日后完成,也许永远不做。有趣的是,我被多次告知某项功能“必不可少”,仅仅是因为发现该功能在待办列表的底部停留了六个月之久。显然,它上面的所有内容都更重要。
这是您可以使用的其他一些镀铜技巧:
因此,到目前为止,我已经为单个功能确定了一条快乐路径,并且将其缩减为对MVP有意义的最低限度……
…但是我的开发团队仍然希望将其分解成较小的功能进行交付,以便他们可以快速完成故事并查看进度。
这里的一种选择是告诉开发人员,在继续具有商业价值的同时,无法合理地分解故事。另一个选择是将其进一步分解,以使有价值的故事一次累积一点。
重要的是,我们仍然希望垂直分割故事,以便每个子故事都提供用户可见的内容。我们不想水平地分割故事,因为这只会给我们开发任务,而不是故事。
显而易见的事情是将功能分解为各个步骤。在第一种情况下,如果该功能涉及用户遍历多个屏幕,则可以在每个屏幕上拆分一个故事。然后,您可以将其拆分,以便逐步交付每一个屏幕。您的工作量取决于开发团队的偏好。我经常发现当团队还年轻时希望故事很小,而随着他们的成熟,可以应对更大的故事。
逐一步骤的一种变体是为该功能构建一个“骨架”。你从具有起点和终点但中间没有任何东西的功能开始。例如,一个“注册”按钮可通过“提交”按钮将用户带到空白页。当用户单击“提交”时,他们会收到一条消息,告知他们已成功注册。但是实际上什么也没发生。然后,逐个故事地填补空白,收集各种数据并实际创建注册。关键是您可以采用多种不同的方式对其进行分解,而不必按顺序进行。
骨架方法的另一个变体是先构建前端用户流,但不建立后端 - 所有后端调用都被插桩。这样的好处是,它可以让您尽早看到完整的用户流,并在不起作用时对其进行调整(尽管另一种方法是构建简单的HTML原型)。
顺便说一句,此技巧的另一种用途是将故事从用户目标级别(海平面)拆分为子功能级别(水下平面)。
Spike探针是一个旨在传递知识而不是交付生产的故事。当遇到大小和/或架构不确定的故事时,团队通常会“尝试Spike”并花一些时间进行基本的研究,或者更可能是进行实际编码,以便更好地了解大小或方法。
从表面上看,这似乎是个好主意,但是以下是我遇到过的探针问题:
范围不确定和时间有限的结合,对于开发人员来说,这是一个随机探索并看看会发生什么的机会。即使是纪律严明的开发人员,也有被带走的严重风险。
例如,假设我们的团队正在构建API,并且我们决定使用RAML来说明那些API(RAML是目前非常流行的API标记语言)。我们希望在我们的网站上发布API文档,并且我们决定一种较好的方法是构建一个RAML到HTML的转换工具。
因此,我们进行探针,构建了一个RAML到HTML的转换工具。一个开发人员被分配到这个探针,然后开始。他们每天在站会上报告进展,应该很快能够完成。几周后,进展顺利,而且是个好消息 – 它完全符合RAML 1.0,并且可以从任何有效的RAML文件生成HTML!
但是,当我们查看实际想要构建的API时,我们发现我们只需要使用RAML 1.0的子集,他构造的一半是浪费的精力。
这个例子基于一个真实的故事,但实际上并没有那么糟糕。在构建的过程中,我们更改了方法。我们没有继续“ RAML到HTML转换器工具”的介绍,而是开始定义面向业务的故事,为真实的API提供实际文档,而我们专注于一次建立一点点RAML到HTML的转换,仅构建我们实际需要的。通过这种方法,我们避免了解决方案的过度设计,并且还更快地交付了一些实际的业务价值。
我的母亲总是告诉我不要用尖锐的铅笔四处走动,原因是如果我绊着而摔倒,可能会导致严重的事故并最终住院。换一种说法:
通常,我会尽量避免探针。相反,我花时间与开发人员一起了解架构不确定性所在的位置,并尝试将一个故事分解为一些子故事,这些故事使他们可以一次了解一点儿架构,同时仍能始终提供业务价值。
将这项技巧编号为999,有两个原因。首先,这是不得已的方法,必须在所有其他技术之后使用;其次,(在英国)这是您拨打的呼叫救护车的电话号码,这似乎很适合使用具有潜在危险性的技术!
当我着手撰写本文时,我并没有意识到会需要多长时间。具有讽刺意味的是,我写了一篇有关将史诗分解成故事的本身就是史诗的文章。
这篇文章的长度可以说明问题 - 故事拆分很复杂,以我的经验来说,做起来很难。我已经做了很多年了,但我仍在学习新的技巧。这是通过实践和经验而能变得更好的技能之一,因此,如果您在挣扎中,请不要放弃!
自从您开始阅读本文以来,可能已经过去了几十年,值得快速回顾一下:
锋网按:本文为AI研习社编译的技术博客,原标题 Analyzing Utah’s Air Quality – Connecting to the EPA’s AQS Data API,作者为 Randy Zwitch 。
翻译 | 京鹏 校对 | 余杭 整理 | 余杭
住在山谷里有点像生活在汤碗里,所有重物似乎都集中在碗底。 我想说犹他州的许多山谷被称为地垒和地堑,虽然我确信一些地质学家可能纠正我的错误。无论如何,四面环山意味着空气污染往往会收集并集中在山谷底。
从伍德兰丘陵看犹他州
作为一个终身的犹他人,我开始怀疑污染有多严重? 新闻记者似乎认为很糟糕。 政界人士却说这种情况从未如此好过。 有多糟糕呢? 它对房地产价值等因素有何影响? 有多少人受到影响?
为了帮助回答其中一些问题,我们与 MapD的高级开发人员倡导者 Randy Zwitch合作。 通过这种合作,我们希望可以更好地了解犹他州的空气质量及其影响,并且随着我们的学习,我们将与您分享我们的流程。
这种合作关系将产生一系列博客文章,记录我们的流程和学习所得。
分析犹他州空气质量
第一部分:连接到 EPA 的 AQS 数据 API
第二部分:AQS 数据清理和转化
第三部分:使用 Shapefile 并在 MapD 中分配AQI站点
第四部分:在 MapD 中构建犹他州 AQI 仪表板
第五部分:最终分析:空气质量调查结果
让我们开始吧...
在 EPA.gov 上注册一个账号
我们需要从环保局获取我们的空气质量数据。数据免费提供,唯一的要求是创建一个账户,用于访问空气质量数据API。
要创建新帐户,请访问 https://aqs.epa.gov/signup。您需要提供的唯一信息是电子邮件地址。 在使用您的电子邮件地址提交表单后,您将收到一个密码。
熟悉 API 参数和数据
收到 API 密码后,你就可以通过一个基于网页的查询表查询空气质量数据。
EPA 上基于网页的空气质量查询工具
使用这个基于网页的查询工具可以快速熟悉可用的数据类型,用于选择所需数据的参数以及整体数据输出格式。
确定分析所需的数据
通过API可以获得大量的空气质量数据,当您尝试使用基于网页的查询工具时,您可以开始了解哪种数据集最符合您的兴趣。 对于我们的分析,我们使用以下参数:
AQI污染物:该数据集包含用于测量空气质量指数的所有污染物,您可能更熟悉将其作为污染物指标,例如今天我们的空气污染是红色,请拼车出行。
参数代码:我们没有提供参数代码,因为我们想要评估所有与AQI相关的污染物。 但是,如果您只对臭氧感兴趣,可以通过传入“臭氧”参数代码(44201 - 臭氧)来限制查询。
州代码:在这个分析中,我们对犹他州(49 - 犹他州)感兴趣。
郡代码:我们想要检索犹他州所有郡的空气质量数据,但是将此参数留空会导致 API 调用失败,因此我们需要单独请求每个郡的数据集。 下一步有更多这方面的内容。
从网页表格迁移到编程 API 调用
一旦您理解了数据并了解了如何构建查询,就可以从基于网页的表单转换为您选择的编程语言,以便对数据进行检索,挖掘,清理,传输等。 对于此示例,我们将使用Python。
有关如何与API交互的详细文档,请参阅:https://aqs.epa.gov/aqsweb/documents/ramltohtml.html
Python 脚本示例
项目路径结构
让我们分解这个例子中的操作:
第1步: 导入 Python 库
pandas:由于数据来自API,我们将使用 Pandas 将数据存储在 DataFrame 中。 稍后,我们将在操作数据时使用Pandas 的其他功能。
io:我们将使用 io 库来解码从API返回的数据。
requests:Requests 库将用于向 EPA.gov 服务器发出API请求。
第2步:创建 Pandas Dataframe
我们将创建一个空的 DataFrame 来存储 API 的响应。
第3步: 导入配置数据
如前所述,我们无法请求整个州的数据,因此我们需要一种有效的方法来按县逐个请求数据。 为了使代码更具可伸缩性,我们将使用 county.py 来检索要处理的郡列表。 虽然我们在这里只看犹他州,但代码可以很容易地扩展到处理任何州.
将用于构造 API 调用的基本配置信息包含在名为 config.py 的文件中,此文件作为基本配置文件运行,您要从主项目代码中抽象出来的任何细节都可以放到里面。
第4步: 遍历州的每个郡
现在我们需要遍历有兴趣分析的州的每个郡。
这就是我们定义循环的方式。 使用 county.py 中包含的县列表,我们将遍历州的县列表中的每个县名(如 config.py 中所定义)。 对我们来说,我们的 config.stateName = utah。
第5步: 构建API调用
......
想要继续阅读,请移步至我们的AI研习社社区:https://club.leiphone.com/page/TextTranslation/771
更多精彩内容尽在 AI 研习社。
不同领域包括计算机视觉,语音语义,区块链,自动驾驶,数据挖掘,智能控制,编程语言等每日更新。
雷锋网雷锋网
者:人月神话,新浪博客同名
简介:多年SOA规划建设,私有云PaaS平台架构设计经验,长期从事一线项目实践
在前面关于微服务方面的文章里面提到,对于多个微服务模块间往往都是以轻量的Http Rest API接口方式进行集成和协同。那么对于API接口的设计,接口服务的注册接入,后续的治理管控就是整个微服务架构里面不可或缺的内容。因此今天将对这部分内容重新做下总结。
对于HTTP Rest接口的设计,网上已经有很多文章都有详细的阐述,今天再重新整理下这里面的一些重点,大家都清楚Rest接口是面向资源的接口设计方法,而且基于原生的Http协议,因此里面就有两个最关键的点,一个就是对资源的理解,一个就是对操作的理解。
图片来源网络
什么是RESTful架构,重要的几点如下:
对资源的理解
就是我们平常上网访问的一张图片、一个文档、一个视频等。这些资源我们通过URI来定位,也就是一个URI表示一个资源。资源是做一个具体的实体信息,它可以有多种的展现方式。而把实体展现出来就是表现层,例如一个txt文本信息,它可以输出成html、json、xml等格式,一个图片他可以jpg、png等方式展现,这个就是表现层的意思。
URI确定一个资源,但是如何确定它的具体表现形式呢?应该在HTTP请求的头信息中用Accept和Content-Type字段指定,这两个字段才是对”表现层”的描述。
对操作的理解
客户端能通知服务器端的手段,只能是HTTP协议。
具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源。
对于资源的任何操作,都应该映射到HTTP的几个有限的方法(常用的有GET/POST/PUT/DELETE 四个方法,还有不常用的PATCH/HEAD/OPTIONS方法)上面。所以RESTful API建模的过程,可以看作是具有统一接口约束的面向对象建模过程。
按照HTTP协议的规定,GET方法是安全且幂等的,POST方法是既不安全也不幂等的(可以用来作为所有写操作的通配方法),PUT、 DELETE方法都是不安全但幂等的。将对资源的操作合理映射到这四个方法上面,既不过度使用某个方法(例如过度使用GET方法或POST方法),也不添 加过多的操作以至于HTTP的四个方法不够用。
面向资源的设计
图片来源网络
注意传统的接口设计一般是面向方法和操作的,即一般都是动宾结构为主的方法操作。比如查询用户信息接口,更新供应商状态接口,删除人员信息接口等等。而对于面向资源的设计,一个大的转变就是首先要识别和定义资源,再来对资源的Http各种操作方法进行定义。
资源可以理解为对象,更多的是数据模型,这种数据模型本身就是有层次结构,本身是可以嵌套或递进的。比如你可以将订单理解为一个资源,那么这个订单资源本身是一个层次化的结构对象,包括了订单头信息,也包括了订单明细信息。我们可以对这个层次化的资源结构进行详细定义。
在数据库设计的时候,经常会出现视图的设计,或者说表之间的关联,而这种视图本身就不是一种资源实体,但是在进行资源定义的时候,我们可以定义这种层次化的对象结构。
举个例子来说,在对订单这种资源对象进行定义的时候,在订单明细中传统的视图中会关联订单明细表和物料表形成订单行ID,物料编码ID,物料名称,类型,订购数量的关联视图信息。但是在资源定义的时候,我们仍然只需要定义订单行信息,但是订单行信息中出现了物料,我们形成一个对物料资源信息的Ref引用,而获取到完整的物料属性信息。
当然资源本身也有关联关系,我们可以定义这种关联关系来获取到关联资源的信息,比如某个用户发布的评论信息,我们可以先定位到该用户资源,然后再定位到该用户资源对应的评论资源。
具体可以参考:
http://www.ruanyifeng.com/blog/2014/05/restful_api.html
http://blog.51cto.com/xxdeelon/2083707
为了使 API 看上去简单明了,可读性强,我们一般使用名词,而不是动词来命名这些资源。比如下面这些都是糟糕的设计,比如下面的动宾结构的方法名作为资源:
之所以糟糕,不仅仅是它们显得拖沓冗长,最重要的是,使用这样的风格和名字没有固定的形式,不同的开发者往往需要阅读你的文档才能开始使用,也没有充分利用HTTP Method,何况使用自己的动词可能会产生和HTTP Method冲突的情况。使用 REST 风格的优秀设计应该像下面这些:
这些API所有的操作只有一个节点 /users,显得简洁明了,如果熟悉 HTTP Method 的开发者,一眼看上去就能猜到应该如何使用。
注:在现在进行Http Rest API接口设计的时候,实际上往往并没有遵循面向资源设计的思路,仍然是参考上面的操作+数据的设计方法,比如有些公司的设计规范往往还是全部约定为使用POST接口设计,这个并不能说就一定不合理。这种情况更多就是使用Http API而已。
您可能已经注意到了,以上 API 中资源命名都使用了复数的形式。这是一个约定,它可以省去设计时考虑数据具体细节的麻烦(数据是复数还是单数?)。现在大很多常见的系统都使用了复数形式。比如Twitter 的 REST APIs 和 Facebook 的 Graph API 基本都是复数形式。
然而实际系统一般都不可能只有单一资源,资源和资源之间有各种关系是很正常的情况,那么如何设计存在关联资源(数据)的API呢?如果要设计一个资源拥有另外一个资源的情况的API,例如,设计一个包含用户(users)和用户的评论(comments)的 API 可以采用这样的形式:
当然,如果一个资源并不依附其它资源而可以独立存在,是没有必要这样设计的,完全可以使用和 users 一样的形式提供,如果要查询其中的关系,可以使用其它资源作为 ID 的形式来过滤。
例如 /comments?user_id=1234。关于这点详细内容可以参见下面的第三条“优雅的设计条件过滤,排序,搜索和限制返回数据的参数形式”。
对于Http Rest接口设计规范,可参考
https://blog.csdn.net/zghwaicsdn/article/details/53788535
https://blog.csdn.net/zl1zl2zl3/article/details/73867113
https://blog.csdn.net/yyjava/article/details/81626991
https://www.jianshu.com/p/00d1ab8cc073
http://wangwei.info/about-rest-api/
资源定义格式:
URI = scheme "://" authority "/" path [ "?" query ] [ "#" fragment ]
URL是URI的一个子集(一种具体实现),对于REST API来说一个资源一般对应一个唯一的URI(URL)。在URI的设计中,我们会遵循一些规则,使接口看起透明易读,方便使用者调用。
"/"分隔符一般用来对资源层级的划分
例如 http://api.canvas.restapi.org/shapes/polygons/quadrilaterals/squares
对于REST API来说,"/"只是一个分隔符,并无其他含义。为了避免混淆,"/"不应该出现在URL的末尾。
在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的"集合"(collection),所以API中的名词也应该使用复数。
在我们在实施过程中如何通过一些工具支撑来进行接口设计的规范化和标准化问题。大家都知道,在采用传统的SOAP WebService服务接口的时候,由于SOAP WS服务本身有WSDL和XSD文件的强接口契约规范格式要求,因此很容易基于从上向下,规范先行的思路来进行服务实施流程管控。即:
可以看到传统的WS服务实施下,我们可以通过规范和契约先行的方式很好的控制接口的标准化和一致性,同时大家遵循完全相同的一套WSDL文件进行接口的提供或开发,基本不存在接口实现中数据结构不一致的问题。
在采用RestAPI接口进行设计的时候,实际上仍然有一个关键点,就是设计要先行,先设计,然后再进行开发和测试,同时设计文件能给标准化和结构化,通过设计文件来生成相应的客户端和服务端代码框架,通过设计文件来生成相应的测试用例,通过设计文件来生成对应的API接口设计文档。
设计先行,是Http Rest接口服务实施中的一个关键点。
1. 设计工具的选择问题
当前两者主流的设计规范和建模语言,一个是RAML,一个是WADL。
RAML(RESTful API Modeling Language) 即 RESTful API 建模语言)是对 RESTful API的一种简单和直接的描述。它是一种让人们易于阅读并且能让机器对特定的文档能解析的语言。
RAML 是基于 YAML,能帮助设计 RESTful API 和鼓励对API的发掘和重用,依靠标准和最佳实践从而编写更高质量的API。通过RAML定义,因为机器能够看得懂,所以可以衍生出一些附加的功能服务,像是解析并自动生成对应的客户端调用代码、服务端代码 结构, API说明文档。
https://www.w3cschool.cn/web_api_next/qw2zpozt.html
https://raml.org/projects
https://www.cnblogs.com/softidea/p/5728952.html
WADL(Web Application Description Language)是一种用于描述web应用对外提供接口语言,它使用XML来表示。其中主要包括每个资源的定位(URI)、请求类型(GET、POST等)、请求参数类型(路径参数、query参数等)、响应值和响应类型等一系列内容。
根据这个XML,可以直接读取到系统提供的所有REST API,并能够进行调用。由于WADL使用XML描述,对于开发者来说,可以通过标准的xslt进行转换,将机器读取的XML转换成便于人读取的HTML,进而行成API文档。
https://coolex.info/blog/547.html
http://blog.chinaunix.net/uid-9789791-id-1997442.html
http://www.doc88.com/p-6661149184381.html
注:对于两种接口规范如何选择的问题?个人认为如果我们需要一套接口规范同时兼容传统的SAOP WebService和Http Rest API,那么采用WADL更好。如果已经是全新Http Rest接口设计,往往当前RAML更加主流。
主流的Swagger设计工具
对于设计工具,这里只谈下Swagger设计工具,这个工具最大的好处就是只需要通过编辑器进行Rest接口设计文件的定义,然后可以自动生成测试框架,自动生成客户端和服务端多种语言下的开发框架,生成API接口设计文档,这个工具本身设计完成内容还可以导出为YAML文件或Json文件,也支撑文件导入。
https://swagger.io/tools/
Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。Swagger的目标是对REST API定义一个标准的和语言无关的接口,可让人和计算机无需访问源码、文档或网络流量监测就可以发现和理解服务的能力。
当通过Swagger进行正确定义,用户可以理解远程服务并使用最少实现逻辑与远程服务进行交互。与为底层编程所实现的接口类似,Swagger消除了调用服务时可能会有的猜测。Swagger是一组开源项目,其中主要要项目如下:
Swagger API Spec包含以下部分内容,详细参考:
https://www.cnblogs.com/jpfss/p/8072443.html
注意通过Swagger-Editor编辑器可以完成整个接口的定义和接口设计工作,同时一方面用于客户端和服务端代码框架的生成,一方面用于帮助文档的生成。但是Swagger对于帮助文档的生成需要在代码框架里面增加注解,相对来说注解编写的工作量较大。这也是网上很多提到的文档生成方面Swagger往往并不是最佳选择。
原有的接口服务规范编写思路改进
对于原来的接口服务规范编写思路,可以看到几个改进或优化思路。
1. 还是保留先编写Word格式的接口服务规范,然后再将Word接口服务规范转为标准的YAML或WADL接口规范定义文件,同时再生产客户端或服务端的代码框架文件。
2. 抛弃Word编写规范模式,直接在类似Swagger编辑器中对接口文件进行设计,设计完成后发出文件进行评审和检查,没问题后由集成平台归档并导入管控生产服务规范,同时生成客户端和服务端代码框架一同下发。
现在发现的一个问题不好解决点在于,在进行接口文件设计的时候能否直接增加字段和数据项的中文描述和业务备注说明,该信息最终体现到生成的文档上。或者说我们需要通过YAML或Json文件,来逆向生成Word文档,然后Word文档再下发给业务系统补充中文描述信息。
API文档的生成
如果采用Swagger工具来进行API接口服务的设计,可以看到在编辑器里面,设计过程和文档生成过程基本是同步的,即一个API接口设计好后,文档基本就已经生成完成并可用。当前唯一发现的问题就是没有想过的中文描述,如果需要中文信息,还需要在代码框架里面增加注解,这个过程来说就想到繁琐了。
通过Swagger注解生成文档:
https://www.jianshu.com/p/0438034ee55f
SpringBoot项目生成RESTfull API的文档,第二篇给出SpringBoot和Swagger整合思路
https://www.jianshu.com/p/af7a6f29bf4f
http://blog.didispace.com/springbootswagger2/
https://gitee.com/didispace/SpringBoot-Learning
Wisdom REST Client
Wisdom RESTClient supports automated testing and automatically generating RESTful API document based on history cases. Wisdom RESTClient可以自动化测试RESTful API 接口,同时,基于测试过的历史数据,可以自动生成API文档。
工具地址:https://github.com/Wisdom-Projects/rest-client
这个工具是偏测试后的文档生成,最终生成的文档展现和Swagger展现效果类似。
Api2Doc文档生成工具
Api2Doc 专注于 Restful API 文档的自动生成,它的原理与 Swagger2 是类似的,都是通过反射,分析 Controller 中的信息生成文档,但它要比 Swagger2 好很多。最大的不同是: Api2Doc 比 Swagger2 要少写很多代码。
第一是对应注解过程本身更加简单,其次就是可以自动分析类引用,将类定义中关于数据项的注释信息引用到最终生成的文档中,这个功能就相当好用了,很好的解决了我们前面提到的数据项中文描述的问题。
参考:
http://blog.51cto.com/13613194/2090764
https://www.cnblogs.com/yoyotl/p/6376624.html
使用Asciidoc代替Markdown和Word撰写开发文档
Asciidoc是另外一个可以用于HttpRest接口文档生成的工具。该工具简洁而不简陋的语法,它专门为编写书籍而生,在语法的支持上很到位,但不像Word那样可以随性,可以让你的文档更统一美观。AsciidocFX工具开源跨平台,使用体验很不错,更可以导出HTML、PDF、EBook等格式
具体参考:
https://my.oschina.net/gudaoxuri/blog/524132
http://houqp.github.io/wbwa/wbwa.html
感觉该工具完全可以用于帮助手册的制作。常用的列表,段落,超链接,图片,表格,代码等能力都支持。
对于API管理平台参考
https://www.eolinker.com/#/
对于Eolinker基本提供了一套完整的API全生命周期管理方案。无论您使用什么语言开发,无论是 HTTPS、Websocket、TCP、UDP 等协议,还是 Restful、SOAP、WebService 等规范,Eolinker 都可以帮您统一规范地管理起来,并提供强大的文档管理、协作、测试、分享功能。
由于我们原来重点是实施传统ESB服务总线项目,以SOAP WS服务接入为主,并形成了完整的SOA治理管控平台,实现服务全生命周期管理和运维监控。而对于Http Rest接口服务的注册接入和后续管控,要完全重新实现一套面向Http Rest接口的平台并不太现实。
因此需要基于当前的SOA管控平台来思考如何平滑扩展对Http Rest接口服务的支撑能力。
首先我们要意识到,ESB总线和微服务网关是可以并行存在的。在一个集团企业内部,有些业务域已经完全实施微服务架构转型和改造,而有些业务系统仍然采用了传统架构,企业在朝微服务架构转型过程中实际上是一个逐渐过渡的过程。在传统架构支撑中我们搭建了ESB企业服务总线实现接口服务集成和统一管控,而在微服务架构支撑中,我们如何支持?
这里的一个关键思路和边界就是,对于某个业务域完全实施了微服务架构转型,比如一个大供应链系统的建设,里面涉及到招投标,采购,物流,库存等多个微服务模块,都按照微服务架构进行设计,开发和集成。那么在这个业务域内部就需要有微服务注册中心,同时又一个微服务网关统一提供对外能力。
那么企业原来已有的全局ESB服务总线只是和该业务域提供的微服务网关进行对接。这个有点类似于两级ESB集成模式,即供应链内部微服务模块间的集成不走ESB总线,在内部通过注册中心或网关完成即可。
其次ESB总线虽然比较重但是不代表性能弱,我们经常会遇到业务系统提出的问题,就是走ESB服务总线集成是否太重,而应该改为轻量的API网关类产品。在这里我们要提出一个关键点,就是ESB总线虽然说是一个重型的产品,但是这种重更多是指的其对底层资源和中间件的要求比较高,并不是说重量级的产品性能就差。
对于ESB这种重型产品,底层引擎更加强大,当你只启用简单的Http服务代理,安全等功能的时候,其性能,可靠性和稳定性反而是远远高于我们经常说的各种开源的API网关类产品的。
举个例子来说,就拿各种重量级的V6大型越野车来说,其重点是油耗高,刚开始的加速弱点,当时真正加速起来后的性能,通过性和稳定性远超各种一般的轿车。而且越野车不是说只跑山路烂路有优势,就是其跑高速路带来的性能你也不上。
微服务网关或API网关,我们看到,只要是这种网关类产品,就一定需要提供服务代理能力来保证服务位置透明,但是一提供这个能力,那么所有的消息流就一定会通过网关来承载,因此API网关本身受到的性能压力,高可用性压力是很大的。也对产品本身也有更高的要求,而对于大型ESB总线在这块往往已经得到充分验证。
也就是说,要追求性能,我们不要用ESB总线里面类似协议转换,数据映射,适配等复杂消耗性能的功能,而将ESB总线当作网关类产品来用就可以了,这样ESB总线完全可以提供类似API网关应该有的性能。
最后,实现对Http Rest接口服务的注册和接入并不一定说要完全遵循面向资源编程的思路。谈Http Rest接口设计的时候,一定会提到面向资源的编程思路,包括资源的识别和定义,基于资源的各种标准Http操作。但是要注意到,在一个微服务架构内部,我们完全可以按这个思路来执行和推广。但是在涉及到跨域的基础,微服务网关和传统业务系统,ESB集成的时候。
这个接口设计是否一定遵循Http Rest面向资源的设计规范并不那么重要。
也就是说,传统ESB在接入Http Rest接口的时候,我们完全可以按传统的接口设计方法进行设计,比如全部走POST接口设计。只是传统协议走了标准的Http接口协议,传输内容走了更加轻量的Json格式而已。只有把这点想明白,才能给继续思考我们当前的SOA管控平台,如何能给平滑的实现对Http Rest接口服务接入的支撑。
基于前面的思路,对于当前的SOA管控平台,需要考虑如何平滑的支撑Http Rest接口服务。
1. 对于新的Http Rest接口的注册和接入
对于当前已有的基于SOAP WS的服务规范定义文档,最好的方式就是做到SOAP和REST接口都能给通用,虽然这样不符合Rest面向资源的要求,也丧失了Rest接口本身的一些灵活性,但是可提升对服务规范的标准化管理。在这种模式下,对于服务规范涉及到的变化估计在于:
也就是说,我们完全可以做到一套服务规范来支撑SOAP和Rest接口,毕竟在做跨系统间接口服务集成的时候,这里的接口更多都是粗粒度的服务定义,并不会涉及到细粒度的数据表或对象的所有CRUD操作。跨系统间的接口更多的是满足跨业务协同和数据集成需要即可,是横向系统和系统之间的接口,而不是类似单个业务系统展现层和逻辑层到DB层的接口。
对于Http Rest接口,传递的数据要支持XML和Json两种格式,这个也需要提前进行约定和配置。而为了兼容当前的服务规范格式,可以看到这样转过了的Rest接口方法全部为Post接口,同时接口服务名即为资源对象名,同时在接口调用的时候传递的是一个我完整的数据集合对象。这和我们前面讲Rest接口的设计和调用是有区别的。
规范定义讲清楚后,整个过程就简单了,原来已有的直接在界面上定义服务规范或者通过Word文档导入服务规范功能基本都可以保留,做少量变更修改即可。导入的规范本身也可以进一步导出或直接在线查看。
对于Rest接口服务,我们仍然可以遵循一样的契约先行和自顶向下设计的思路进行。
规范清楚后,接着还是需要导出标准的契约格式文档,而这里建议还是采用WADL,基于Rest接口的服务定义语言,有强契约格式要求。而对于RAML,更多是Rest接口设计建模的语言,类似于通过RAML来写文档。如果我们是以Word服务规范文档格式为主,实际上是没必要再用RAML。但是可以考虑服务规范导出RAML语言定义。
RAML(RESTful API Modeling Language 即 RESTful API 建模语言)是对 RESTful API的一种简单和直接的描述。它是一种让人们易于阅读并且能让机器对特定的文档能解析的语言。这样也方便我们后面基于RAML来生成API定义文档。
在规范导出为WADL设计文件后,那么就有严格契约格式要求,业务系统可以基于该WADL定义文件,通过工具自动生成客户端和服务端的代码开发框架,然后再填充业务规则和逻辑,这个过程和我们标准的SOAP WS接口服务的设计和开发是一致的,并没有太多的区别。
在服务提供端按WADL要求开发后Rest接口服务后,由ESB进行服务代理封装接入,这个需要单独设计功能支持,即需要采用单独定制的OSB服务封装模板。在服务代理接入后,我们原来对于SOAP接口的安全,日志等控制能力全部都需要保留和平滑支持。
服务代理接入后,仍然自动化部署,部署完成后给出代理封装后的访问地址。
对于服务调用访问,本身也会产生日志记录,对于已有的服务运行日志监控,服务异常日志监控等功能,都需要完全平滑的支撑Http Rest接口服务。对于原有的日志元数据配置表,服务日志运行实例表等也保留一套。
2. 对于SOAP和Rest接口本身的转换映射问题
当我们采用同一套规范体系的时候,我们就很容易去实现两种类型接口的转换和映射。
业务系统如果提供的SOAP WS接口服务,我们需要单独提供一个功能,将该SOAP WS服务发布为Http Rest接口服务,同时在发布的时候可以选择是采用XML格式,还是Json格式进行发布。
这样好处就是原来业务系统以及提供了SOAP接口,但是当我们在对接一个微服务架构体系的时候,消费端为了自身的标准化需要消费Rest接口服务,那么由ESB来完成这种转换映射即可,原有的业务系统服务提供方不再需要做任何服务设计变更和代码修改。
当然,如果业务系统本身提供的Http Rest接口服务,为了对已有的历史系统进行支撑,我们也可以将Http Rest接口服务转化为SOAP WS服务再进行暴露。在转换的时候新数据格式只能够是XML格式。
3. 对于消息发布订阅,消息中间件的支持
对于消息发布订阅,我们采用Weblogic JMS消息中间件,但是将JMS能力适配后发布为SOAP WS接口服务。在Http Rest接口服务实施过程中,我们需要支撑将JMS适配后能力直接发布为Rest接口服务。
当然为了简化,该功能非必须,即我们可以考虑在ESB接口上做两次代理,即将ESB已经暴露的SOAP WS消息分发服务接口,通过Http Rest接口服务转换功能直接进行转换后二次发布。
对于JMS消息订阅端,本身走Java API接口进行消息消费,本身和服务采用SOAP还是Rest没有太大关系。
4. 对于Http Rest接口的纯代理接入
对于业务系统已有的HttpRest接口,如果不希望按严格的服务规范设计和契约先行的思路进行变更,我们也可以直接对已有的Rest接口进行纯代理接入,即ESB做完全的代理转发,不做任何的安全控制,输入内容消息头的解析,数据映射转换等。对于这类Rest接口服务,ESB总线只起代理转发作用。
对于ESB服务总线和API网关产品来说,在前面很早的博客文章自己就谈到过,整体思路是底层引擎是两套,即一个是偏重的ESB总线引擎,一个是API网关引擎,但是对于SOA治理管控和运营开放则是整合为一套。一个是SOA运维监控平台是统一的一套,一个是能力开放平台也统一为一套。
但是我们看到虽然ESB总线是一个偏重的引擎,但是我们不启用其复杂的协议转换,数据映射,服务编排等功能的时候仍然可以做为要给轻量的SOA总线来使用。而且我们看到另外一个场景,即企业很多时候不会很快就完成一个微服务架构化的转型,始终是存在传统的遗留系统。
因此集成问题和场景本身是很复杂的,即使整个集成趋势是Http Rest接口集成和API网关集成为主,但是你还是得兼容传统观的WS服务集成和简单的协议转换能力。
实际上对于ESB总线来说本身就是支持Http Rest接口服务得注册和接入的。因此实际上对ESB服务总线和API网关引擎存在两种思路可以选择。
对于第二种方式相对来说并不会很复杂,也容易实施,即通过对ESB服务总线的升级来完成对ESB总线+API网关两方面能力的完全支撑。你可以说卖的是ESB服务总线,但是完全兼容适配API网关所有能力。
基于上面这个思路,我们需要做的主要包括
基于以上关键点进行进一步的优化和完善后,即能够为企业提供一套完整的SOA服务总线产品,同时支撑传统的ESB服务总线能力,又对Http Rest API接口的接入,注册和管控方面能力得到全面增强。
欢迎关注@人月聊IT 分享SOA,微服务,DevOps平台规划和建设。
*请认真填写需求信息,我们会在24小时内与您取得联系。