本文概述
相关:深入了解JSON与XML, 第1部分:每个标准的历史
在本文的第1部分中, 我们仔细研究了Web 1.0到2.0的演变, 以及它不断增长的痛苦如何导致XML和JSON。由于JavaScript在过去十年中对软件趋势的影响, JSON继续比其他任何数据交换格式都受到越来越多的关注。 Blogosphere宣称有无数文章将这两种标准进行了比较, 并构成了一种逐渐扩大的偏见, 称赞JSON的简单性并批评XML的冗长性。 JSON是否比XML更好?更深入地了解数据交换将揭示JSON和XML的优缺点, 以及每种标准对于普通应用程序与企业的适用性。
在本文的第2部分中, 我们将:
- 评估JSON和XML之间的根本区别, 并联系每种标准对简单应用程序和复杂应用程序的适用性。
- 探索JSON和XML如何影响与企业相比普通应用程序的软件风险, 并探索每种标准减轻其影响的可能方法。
软件前景广阔, 并因各种平台, 语言, 业务环境和规模而有所不同。术语”规模”通常与用户交互相关联, 但也用于表示将复杂程度与简单性区分开的软件质量:”开发规模”, “投资规模”和”复杂性规模”。尽管所有开发人员(包括个人和团队)都希望减轻其应用程序的软件风险, 但严格遵守要求和规范的要求将普通应用程序与企业区分开来。关于JSON和XML, 对这种严格性的考虑揭示了两种标准的优缺点和根本区别。
通用应用程序与企业版的JSON
为了评估JSON和XML对常见应用程序和企业的适用性, 让我们定义数据交换, 并提出一个简单的用例, 以帮助阐明这两种标准之间的差异。
数据交换涉及两个端点, 区分为两个参与者:生产者和消费者。数据交换的最简单形式是单向的, 它涉及3个一般阶段的序列:生产者(1)产生数据, 交换(2)数据, 而消费者(3)消耗数据。
仅在生产者和使用者是同一逻辑参与者的情况下, 才能进一步简化单向数据交换用例, 例如, 当数据交换由应用程序在时间$ \ xi_1 $产生数据并消费时用于磁盘存储时时间$ \ xi_2 $。
与这种简化无关, 用例的重点是确定数据交换正在解决的问题。位于数据生产(1)和数据消耗(3)之间的数据交换(2)提供了一种在生产者和消费者之间交换数据的格式。没有比这更简单的了!
JSON作为数据交换
Crockford本人声称JSON的巨大优势在于JSON被设计为一种数据交换格式。1旨在从一开始就以简洁的方式在程序之间传递结构化信息。 Crockford在简洁的9页RFC文档中定义了JSON, 描述了一种具有简洁语义的格式, 专门针对其设计目的:数据交换。2
JSON满足了在两个参与者之间高效有效地交换数据的即时需求, 但是在软件开发的现实世界中, 即时需求通常只是其他需求中的第一层。对于为满足简单规范而开发的简单应用程序, 只需进行简单的数据交换即可。对于大多数常见应用程序, 围绕数据交换的要求的简单性建议将JSON作为最佳解决方案。但是, 随着围绕数据交换的需求变得越来越复杂, JSON的不足被揭示出来。
围绕数据交换的复杂需求涉及定义生产者和消费者之间的业务关系的更高考虑因素。例如, 在软件系统的体系结构和参考实现时可能不知道其演化路径。将来可能必须添加或删除消息格式的对象或属性。如果生产者或消费者涉及大量的参与者, 则系统可能还需要支持消息格式的当前版本和先前版本。被称为消费者驱动的合同(CDC), 服务提供商面临的一个普遍问题是在生产者和消费者之间发展合同。3
消费者驱动的合同
让我们扩展用户案例, 以包括欧洲银行与零售商之间由消费者驱动的合同, 该合同传达客户购买的银行信息。
SWIFT是银行交易电子通讯的最早标准之一。4除购买信息外, 裸露的SWIFT消息还包含识别帐户的SWIFT代码。
{
"code": "CTBAAU2S"
}
具有属性代码的此消息具有代表其状态空间的特定值。消息的状态空间定义为可以表示的所有状态的完整空间。通过区分有效状态和无效状态, 我们可以隔离状态空间的错误部分, 并将其称为错误空间。令$ \ xi $表示消息的错误空间, 其中$ \ xi = 1 $表示所有有效和无效状态(即状态空间的所有状态)的空间, 而$ \ xi = 0 $表示消息的错误空间。仅有效状态(即状态空间的有效状态)。
数据交换层是使用者的入口点, 其错误空间$ \ xi_0 $(下标0用于表示入口点层)由所有可能的输入(有效和无效)的比例确定, 有效。输入消息的有效性分为两层:技术层和逻辑层。对于JSON, 技术上的正确性取决于消息是否符合Crockford RFC文档中指定的JSON语义的语法一致性。2但是, 逻辑上的正确性涉及对帐户标识符有效性的确定, 这由SWIFT标准定义: 8或11个字母数字字符和符合ISO 9362的结构。” 5
经过多年的成功运营, 欧洲银行和零售商必须修改其以消费者为导向的合同, 以包括对新帐户识别标准IBAN的支持。修改了JSON消息以支持新的属性表示类型, 以将标识符区分为SWIFT或IBAN。
{
"type": "IBAN", "code": "DE91 1000 0000 0123 4567 89"
}
此后, 欧洲银行决定进入美国市场, 并与美国零售商签订以消费者为导向的合同。银行现在必须支持ACH帐户识别系统, 将消息扩展为支持”类型:” ACH”。
{
"type": "ACH", "code": "379272957729384", "routing": "021000021"
}
JSON消息格式的每个扩展都会导致应用程序的系统风险增加。在银行整合SWIFT时, 尚不知道会随后出现对IBAN和ACH的支持。随着大量零售商与银行交换数据, 错误风险加大了, 促使银行设计解决方案以减轻这种风险。
这家欧洲银行是一种企业解决方案, 并且坚决要求其严格确保与零售商的无差错运作。为了减轻软件的错误风险, 银行努力将错误空间值$ \ xi_0 $从1减少到0。为帮助银行系统降低软件风险, 管理SWIFT, IBAN和ACH代码标准的组织将简单的测试功能定义为确定标识符的逻辑正确性。每个标准的测试功能可以以正则表达式形式表示:
????SWIFT(id) = regexid( "[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?" )
????IBAN (id) = regexid( "[A-Z]{2}\d{2} ?\d{4} ?\d{4} ?\d{4} ?\d{4} ?\d{0, 2}" )
????ACH(id, rt) = regexid( "\w{1, 17}" ) × regexrt( "\d{9}" )
为了使$ \ xi_0 $接近于0, 银行的系统必须仔细检查每条输入消息, 以确保检测到可能遇到的尽可能多的错误。由于JSON仅能进行数据交换, 因此验证逻辑必须在系统本身的一层中实现。这种逻辑的一个例子是:
isValid(message) {
if (!message || !message.type) {
return false;
}
if (message.type == "SWIFT") {
return message.code &&
message.code.matches("[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?"));
}
if (message.type == "IBAN") {
return message.code &&
message.code.matches("[A-Z]{2}\d{2} ?\d{4} ?\d{4} ?\d{4} ?\d{4} ?\d{0, 2}"));
}
if (message.type == "ACH") {
return message.code &&
message.code.matches("\w{1, 17}") &&
message.routing &&
message.routing.matches("\d{9}"));
}
return false;
}
验证逻辑的规模与要验证的消息变体的复杂度成正比。欧洲银行的精简消息格式很简单, 因此其验证逻辑的规模也很简单。对于具有更多属性的消息, 尤其是具有嵌套对象的消息, 验证足迹的行数和逻辑复杂性都将大大增加。使用JSON作为数据交换, 验证层被实现为系统的耦合组件。由于验证层使用输入消息, 因此会导致数据交换层的$ \ xi_0 $。
软件系统中的每个层的值均为$ \ xi $。数据交换正上方的验证层具有其自身的潜在错误状态空间, 该错误状态是由于其自身的复杂性而导致的。对于欧洲银行来说, 这种复杂性是isValid(message)函数。由于验证层使用了来自数据交换层的输入, 因此其$ \ xi_1 $成为$ \ xi_0 $的函数。这种关系的抽象外推表明, 一层的$ \ xi $吸收了上一层的$ \ xi $, 这吸收了上一层的$ \ xi $, 依此类推。此关系可以用以下表达式表示:
$ \ xi_n = \ alpha_n \ times \ xi_ {n-1} $
在这里, $ \ xi_n $表示层$ n $的错误空间作为$ \ xi_ {n-1} $的函数(即前一层的错误空间)乘以$ \ alpha_n $, 表示错误由$ n $层本身的软件复杂性引起的组件(即, 代替第n层$ n中的代码复杂性, 通过复合错误)。
弹性系统是通过减少输入, 内部处理逻辑和输出的复杂性空间来减轻其软件风险的系统。
复杂需求的软件风险
我们所说的”系统的软件风险”等效于$ \ xi_N $, 其中$ N $代表系统中的最高层。在设计新的应用程序时, 由于实现的可见性有限, 因此评估$ \ xi_N $可能具有挑战性。但是, 可以通过分析需求的复杂性来估计整个系统的复杂性。
软件风险与需求的复杂度成正比。随着需求的复杂性增加, 错误的空间也随之增加。消息格式越复杂, 系统必须考虑的错误情况就越多。特别是, 对于涉及不同系统的服务提供商, 服务提供商(消费者)必须针对其客户(生产者)的消息采取防御性策略。服务提供商不能假定从其客户端交换的数据没有错误。为了保护自己免受错误输入数据的侵害, 使用者验证每个消息的内容。
对于具有复杂要求的应用程序, 尤其是企业系统, 降低软件风险是首要考虑因素。
为了减轻逻辑复杂性的风险, “封装”的编程原理可帮助开发人员将代码组织到具有逻辑边界的层中。在每个封装层, 对输入进行验证, 处理并传递到下一层。该原理为逐步减少每个层$ n $上的”错误空间” $ \ xi $提供了一种有条理的方法, 从而进一步隔离了由于错误输入而导致的高级业务逻辑与错误的隔离。正确的封装会导致更高的内聚性, 从而可以明确定义特定封装模块的职责, 并且不会与辅助职责相关联。通过思想的清晰区分, 减少了消息变异性的”特定风险” $ \ xi_0 $, 以及数据交换上方每一层的”一般风险” $ \ xi_n $, 以及”总风险” $整个应用程序的\ xi_N $。
JSON作为满足复杂需求的数据交换
使用JSON作为数据交换格式时, 必须在JSON之外的层中处理通信协议的复杂要求。尽管JSON因其简单的语义而很棒, 但围绕数据交换的复杂性不可避免地会蔓延到应用程序的其他层。为了对付由于输入复杂性而导致的错误风险, 系统依靠封装将消息验证与业务逻辑分开。 JSON与JavaScript的紧密集成使开发人员可以将此逻辑集成到应用程序堆栈中更高的层中, 这些层通常与业务逻辑本身交织在一起。不幸的是, JSON的过分简单的特性鼓励了这种方法-封装了用于翻译消息格式的不同变体的逻辑的中间层是降低风险的解决方案, 但是这种模式通常被认为过分, 不流行并且因此很少使用。检查帐户标识符是否有效的逻辑可以通过以下方式实现:
- 数据交换上方的验证层, 导致责任和逻辑凝聚力的封装。
- 在业务逻辑中, 导致责任和逻辑耦合的合并。
JSON作为数据交换需要将验证逻辑吸收到应用程序代码中。在应用程序中实现isValid(message)函数会导致消息格式的逻辑含义(由消费者驱动的合同协议的生产方和消费者方之间进行协商而决定的一种格式)与以下功能的耦合:消息处理。通过消费者驱动的合同, 数据交换的范围扩展到包括验证和支持消息变体的处理代码。从这个角度来看, JSON是洋葱的小中心, 洋葱层代表验证, 处理, 业务逻辑等所需的附加逻辑。
一个。 JSON作为数据交换
b。封装的消息验证。此代码耦合到应用程序。
C。未封装的消息验证。此代码与业务层混合在一起。
d。应用程序。
在为应用程序选择最佳数据交换格式时, 是仅仅是我们关注的洋葱的中心, 还是整个洋葱?
生产者无法使用在使用者中实现的自定义验证逻辑, 反之亦然。尤其是如果系统是在不同的平台上实现的, 则期望验证功能可以在通信协议的各方之间共享是不合理的。只有两个系统共享同一平台, 并且验证功能已正确封装并与应用程序分离, 才能共享代码。但是, 这种完美条件的独特用例是不现实的。随着验证逻辑变得越来越复杂, 其在应用程序中的直接集成可能导致意外(或预期)的耦合, 从而使功能不可共享。例如, 欧洲银行希望通过要求零售商也验证其两端的所有消息来进一步降低其软件风险。为此, 将要求银行和零售商实施自己的isValid(message)版本。由于每一方都应对自己的实施负责, 因此:
- 留出各方在执行过程中出现差异的空间。
- 对于每一方的系统, 请将消息格式的逻辑含义与技术实现相结合进行处理。
虚线表示缺乏规范性的标准来表达由消费者驱动的合同, 以在功能上约束生产者和消费者。为了执行由消费者驱动的合同, 每一方都要负责自己的实施。
开源图书馆弥合鸿沟
JSON是简单高效的数据交换标准, 但它没有提供标准模式来帮助开发人员使用洋葱的外层。为了解决数据交换的复杂性, 开发人员在两种通用的体系结构方法之间进行选择:
- 实现上述自定义代码, 例如isValid(message), 以功能和逻辑耦合为代价。
- 集成实施与问题相匹配的通用解决方案的第三方库, 但要承担第三方库的软件风险。
已经开发了许多开源解决方案来解决围绕数据交换的常见复杂性。通常, 这些解决方案是特定的, 旨在解决问题的一部分, 而不是另一部分。例如, OpenAPI是一个开放源代码规范, 它定义了REST API的接口描述格式。6OpenAPI解决了REST服务的发现和规范, 但是消费者驱动合同的规范实施不在其范围之内。 GraphQL是Facebook开发的另一个流行的库, 用于对现有数据进行查询。7GraphQL通过将数据层中的API直接暴露给生产者的数据交换, 为消费者提供了一种从生产者访问数据的标准化方法。对于具有简单数据的简单应用程序, 将数据层直接互连到传输层可以为开发人员节省很多样板代码。但是, 对于坚决降低应用程序软件风险的企业系统而言, GraphQL方法可减少封装, 减少内聚并增加耦合。
使用JSON作为数据交换格式, 公司和个人开发人员可以独自拼凑满足复杂要求的库和框架的集合。由于每种解决方案都旨在解决问题的一部分, 而不是另一部分, 因此开发人员很难找到以下解决方案:
- 解决围绕数据交换的复杂性, 以满足系统的要求和规范。
- 保持较高的封装和内聚性, 并保持较低的耦合度, 以降低总软件风险$ \ xi_N $。
由于这些解决方案的非标准性质, 项目承担了难题中每个组成部分的软件风险。
依赖更多外部库的应用程序比依赖更少外部库的应用程序承担更高的软件风险。为了减轻复杂应用程序的软件风险, 开发人员和软件架构师通常选择依赖标准而不是定制解决方案。应用程序不必担心标准会更改, 破坏甚至不再存在。但是, 对于定制解决方案, 情况并非如此。例如, 集成了一系列开源项目的定制解决方案承担了每个构成依赖的风险。
JSON的优点和缺点
与其他数据交换标准相比, JSON是最简单, 最紧凑的人类可读格式。对于为满足简单规范而开发的简单应用程序, 只需进行简单的数据交换即可。但是, 随着围绕数据交换的需求变得越来越复杂, JSON的缺陷也被暴露出来。
就像导致XML发明的HTML的”发散灾难”一样, 在依赖JSON进行数据交换的复杂代码库中也实现了类似的效果。 JSON规范并未封装围绕数据交换的直接功能, 这可能导致应用程序高层中的逻辑碎片。使用JSON作为数据交换时, 应用程序的高层由自己来实现JSON本身缺少的功能。通过在应用程序的表层上包含围绕数据交换的复杂性, 捕获数据相关错误的能力也被推到了应用程序的表面。这种体系结构上的副作用可能导致直接向应用程序用户表达与数据相关的错误, 这对于企业来说是不可接受的。
JSON的根本缺陷是缺乏为文档的逻辑成分提供规范描述的通用标准。
对于努力主张无错误操作的企业应用程序, 将JSON用作数据交换会导致导致更高软件风险的模式, 并导致意想不到的障碍, 从而阻碍了实现高代码质量, 稳定性以及对未来未知数的复原力的目标。
JSON为数据交换提供了简洁的语义和基本的简便性, 但它有可能导致整个系统的封装减少。消费者驱动合同的范围或围绕数据交换的其他复杂要求(例如协议版本控制和内容验证)必须在JSON之外解决。通过适当的封装, 可以以内聚和非耦合的方式实现复杂的需求, 但是这将在一开始就专注于体系结构。为了避免进行正确封装所需的过多工作, 开发人员经常实施使层之间的线模糊的解决方案, 从而使数据交换周围的复杂性扩散到应用程序的其他层中, 并导致潜在错误的逐步增加。
开发人员还可以通过一系列库和框架来弥补缺失的缺口, 从而满足更高的要求。但是, 通过依赖外部库的混合包, 应用程序可以吸收每种构成依赖的风险。由于这些解决方案是特定的, 旨在解决问题的一部分, 而不是解决另一部分, 因此这进一步导致封装减少, 内聚力减少以及耦合增加。
JSON是一种方便的格式, 非常适合简单的应用程序, 但是将其与围绕数据交换的复杂要求结合使用会带来更高的软件风险。 JSON的基本简洁性似乎吸引人, 但是特别是对于企业系统, JSON的使用可能会导致不良的模式, 从而妨碍系统的代码质量, 稳定性以及对未来未知数据的适应性。
相关:深入了解JSON与XML, 第3部分:XML和JSON的未来
在本文的第3部分中, 我们将探索XML作为数据交换的方式, 并评估其与JSON相比的优缺点。在深入了解XML功能之后, 我们将探索数据交换的未来以及将XML的功能带到JSON的当前项目。
参考文献
1. Douglas Crockford:JSON Saga(雅虎, 2009年7月)
2. RFC 4627(克罗福德, 2006年7月)
3.消费者驱动的合同:服务演进模式(MartinFowler.com, 2006年6月)
4.世界银行间金融电信协会(维基百科)
5. ISO 9362(维基百科)
6. OpenAPI-Swagger
7. GraphQL-API的查询语言