兼容性向前还是向后
When building a client-server application, the client and server need to agree on how to talk to each other. For instance, if sending JSON, then the client and server have to agree on field names and data types. For databases, the concept is similar; without a schema, the only information you could get back would be an ordered bag of values. Values are meaningless without context.
在构建客户端-服务器应用程序时,客户端和服务器需要就如何相互通信达成共识。 例如,如果发送JSON,则客户端和服务器必须就字段名称和数据类型达成一致。 对于数据库,此概念相似。 如果没有模式,您唯一可以获取的信息将是有序的值袋。 没有上下文,价值是毫无意义的。
It’s easy to build the first version when the application is still in development. Coming up with an agreement on the fields and types of data is relatively straightforward. You can break things without consequence until it works. But, the initial version of your application will only last so long. Eventually, you will need to change the data to support new features or remove unsupported ones. This is called evolving a schema.
当应用程序仍在开发中时,构建第一个版本很容易。 在数据的字段和类型上达成协议相对简单。 您可以毫无后果地破坏事物,直到它起作用。 但是,您的应用程序的初始版本只能使用这么长时间。 最终,您将需要更改数据以支持新功能或删除不支持的功能。 这称为演化模式。
The naive approach to evolving a schema would be to simply make arbitrary changes and update everything at once. This is impractical for production applications unless you are okay with your product breaking. Let’s say you make an incompatible change like renaming a column in the database from “productNum” to “productId”. After migrating the database to “productId”, the existing application will still be looking for “productNum”. When it doesn’t find a column with that name, it breaks.
演化模式的幼稚方法是简单地进行任意更改并立即更新所有内容。 除非您对产品的损坏还满意,否则这对于生产应用是不切实际的。 假设您进行了不兼容的更改,例如将数据库中的列从“ productNum”重命名为“ productId”。 将数据库迁移到“ productId”之后,现有应用程序仍将在寻找“ productNum”。 如果找不到具有该名称的列,则会中断。
A better way would be to make only compatible changes where different systems that talk to each other can be deployed in any order. In the above example, instead of renaming the column, create an alias to the old name so that either would work.
更好的方法是仅进行兼容的更改 ,以便可以以任何顺序部署相互通信的不同系统。 在上面的示例中,不要重命名该列,而是为旧名称创建一个别名,以便两者都可以使用。
It can be challenging to understand which kinds of changes are compatible and which ones are incompatible. Further complicating things is that there are two different categories of compatibility: backward compatibility and forward compatibility.
了解哪些更改是兼容的,哪些更改是不兼容的,可能是具有挑战性的。 更为复杂的是,有两种不同的兼容性类别:向后兼容性和前向兼容性。
This post dives into the difference between the two.
这篇文章深入探讨了两者之间的区别。
搭建舞台 (Setting the stage)
Here are some terms to familiarize yourself with:
以下是一些使您熟悉的术语:
Schema — Definition of the types of data and any context needed to understand it. Schemas are independent of how the data is encoded as multiple serialization options are possible (JSON, binary, etc.). Schemas can also be versioned, something which is crucial to understanding backward and forward compatibility.
模式 -定义数据类型以及理解数据所需的任何上下文。 模式与数据的编码方式无关,因为可以使用多个序列化选项(JSON,二进制等)。 模式也可以版本化,这对于理解向后和向前的兼容性至关重要。
Reader — The service that parses the data. In the case of a client-server application, this is the client whenever the server has sent back some interesting data. However, when talking about what data the client sends the server (e.g. input arguments to a function), the roles are reversed: the client becomes the writer.
读取器 —解析数据的服务。 对于客户端-服务器应用程序,只要服务器发回一些有趣的数据,便是客户端。 但是,当谈论客户端向服务器发送什么数据(例如,函数的输入参数)时,角色将互换:客户端成为编写者。
Writer — The service that creates the data. In the case of a client-server application, this is the server, but just as before sometimes the roles are reversed.
编写器 —创建数据的服务。 对于客户端-服务器应用程序,这是服务器,但就像以前一样,有时角色会互换。
For databases, the writer is the service that saved the row to the database initially, whereas the reader is the service that retrieves it.
对于数据库,writer是最初将行保存到数据库中的服务,而reader是检索该行的服务。
要记住的一个图: (The one diagram to remember:)
This diagram fully explains the difference between backward and forward compatibility.
该图充分说明了向后兼容性和向前兼容性之间的区别。
Backward compatibility means that readers with a newer schema can correctly parse data from writers with an older schema.
向后兼容性意味着,在新的模式读者可以从旧的模式的作家正确分析数据。
Forwards compatibility means that readers with an older schema can correctly parse data from writers with a newer schema.
前锋兼容性意味着与旧的模式的读者可以从一个新的架构作家正确分析数据。
向后兼容 (Backward Compatibility)
Backward compatibility is important because:
向后兼容性很重要,因为:
- For the case of input parameters: you can upgrade servers without having to upgrade clients 对于输入参数:您可以升级服务器而不必升级客户端
- For return types: you can upgrade clients without having to upgrade servers 对于退货类型:您可以升级客户端,而不必升级服务器
- For databases: you don’t encounter any data loss (without backward compatibility you wouldn’t be able to read any data written by an older version) 对于数据库:您不会遇到任何数据丢失(没有向后兼容性,您将无法读取由旧版本写入的任何数据)
For JSON here is an incomplete list of backward-compatible changes:
对于JSON,以下是不完整的向后兼容更改列表:
- Adding a field with a default value. Older writers will be unaware of that field so the default value will be used instead. 添加具有默认值的字段。 较早的作者将不知道该字段,因此将使用默认值。
- Adding an optional field. Older writers will be unaware of that field so null will be used instead. 添加一个可选字段。 年长的作家将不会意识到该字段,因此将使用null。
- Widening a numerical type (e.g. int to float). Older writers will always use ints, which are a subset of floats. 扩展数值类型(例如,将int转换为float)。 年长的作家将始终使用整数,后者是浮点数的子集。
- Adding a value to an enum string. Older writers will just use one of the existing enum strings. 向枚举字符串添加值。 较老的作家将只使用现有的枚举字符串之一。
- Removing a field. Newer readers will ignore whatever was previously written in this field. (Note: this is not true of many binary serialization formats!) 删除字段。 较新的读者将忽略该领域以前编写的任何内容。 (注意:对于许多二进制序列化格式而言,情况并非如此!)
前向兼容性 (Forward Compatibility)
Forward compatibility is important because:
前向兼容性很重要,因为:
- For the case of input parameters: you can upgrade clients without having to upgrade servers 对于输入参数:您可以升级客户端而不必升级服务器
- For return types: you can upgrade servers without having to upgrade clients 对于退货类型:您可以升级服务器而不必升级客户端
- For databases: you can run your schema migrations before deploying the new code to read it 对于数据库:在部署新代码以读取它之前,可以运行模式迁移
For JSON here is an incomplete list of forward-compatible changes:
对于JSON,这是前向兼容更改的不完整列表:
- Adding a new required field. Older readers will simply ignore it. 添加一个新的必填字段。 较老的读者只会忽略它。
- Narrowing a numerical type (e.g. float to int). Older readers will assume ints, which are a subset of floats. 缩小数值类型(例如,浮点数到整数)。 较老的读者将假定为整数,这是浮点数的子集。
- Removing a value from an enum string. Older readers can handle the full breadth of enums. 从枚举字符串中删除值。 年长的读者可以处理所有枚举。
- Adding a value to an enum string if and only if the reader has implemented a proper “else” case. (See note on enums) 当且仅当读者已实现适当的“其他”情况时,才向枚举字符串添加值。 (请参阅枚举说明)
完全相容 (Full Compatibility)
If a change is both forward and backward compatible, then it is called fully compatible. This means you can run any combination of readers and writers without breaking anything.
如果一个变化是向前和向后兼容的,那么它被称为完全兼容 。 这意味着您可以运行读取器和写入器的任意组合,而不会破坏任何功能。
For JSON here is an incomplete list of fully compatible changes (some are repeated from above):
对于JSON,这是不完全兼容的更改的不完整列表(有些重复自上而下):
- Adding a field with a default value 添加具有默认值的字段
- Adding an optional field 添加可选字段
- Adding a value to an enum string if and only if the reader has implemented a proper “else” case. (See note on enums) 当且仅当读者已实现适当的“其他”情况时,才向枚举字符串添加值。 (请参阅枚举说明)
不兼容 (Incompatibility)
If a change is neither forward nor backward compatible, then it is an incompatible change.
如果更改既不向前也不兼容,那么它就是不兼容的更改 。
For JSON here is an incomplete list of incompatible changes:
对于JSON,这是不兼容更改的不完整列表:
- Renaming a field 重命名字段
- Changing the type of a field (other than the numeric conversions mentioned above) 更改字段的类型(上述数字转换除外)
[关于枚举的特别说明] ([Special Note on Enums])
It is important to write code in a way that allows for the introduction of new enums. For instance, imagine you wrote this code:
以允许引入新枚举的方式编写代码很重要。 例如,假设您编写了以下代码:
if process.status == “STARTED”:
print(“The process has started”)
else: # assumes process.status == “FINISHED”
print(“The process hasfinished”)
Now, if the new status “CANCELLED” was added, this code would be incorrect since it would print that the job is finished even though it’s not. Instead, consider this code:
现在,如果添加了新状态“ CANCELLED”,则此代码将是错误的,因为即使未完成,它也会打印出作业已完成。 相反,请考虑以下代码:
if process.status == “STARTED”:
print(“The process has started”)
elif process.status == "FINISHED":
print(“The process hasfinished”)
else:
raise ValueError("Unexpected status: " + process.status)
This code is much more future proof and allows the writer to add new enum values without breaking the existing code. (Well, technically this code now throws an exception which is at least better than returning the wrong answer).
这段代码更加适合将来使用,允许编写者在不破坏现有代码的情况下添加新的枚举值。 (嗯,从技术上讲,这段代码现在抛出一个异常,至少比返回错误答案更好)。
结论 (Conclusion)
For clients and servers:
对于客户端和服务器:
- Backward compatibility on the server ensures that older clients can still parse the results you return 服务器上的向后兼容性可确保较旧的客户端仍可以解析您返回的结果
- Forward compatibility ensures that older clients can still call your methods 前向兼容性可确保较旧的客户端仍然可以调用您的方法
For databases:
对于数据库:
- Backward compatibility is essential if you want to avoid modifying existing data 如果要避免修改现有数据,则向后兼容性至关重要
- Forward compatibility is a nice bonus but not necessary if you control all the readers 前向兼容性是一个不错的选择,但如果您控制所有读者,则不必这样做
When making a change to your schema, ask yourself the following questions:
更改架构时,请问自己以下问题:
Does this change need to be backward compatible? Are you sure?
此更改是否需要向后兼容? 你确定吗?
(The majority of changes that come up in practice need to be at least backward compatible)
(实践中出现的大多数更改至少必须向后兼容)
- Does this change need to be forward compatible? If not, are you sure all your readers will be aware of the change? 此更改是否需要向前兼容? 如果不是,您确定所有读者都会意识到这一变化吗?
And never forget: the safest schema change is no change at all.
永远不要忘记: 最安全的架构更改根本就没有更改 。
进一步阅读 (Further Reading)
Compatibility is a huge topic. There are many details I glossed over to keep this article short. There is also another even larger topic on code compatibility that is even more complicated. (For instance, why is renaming a parameter in Java a compatible change but an incompatible change in Python?)
兼容性是一个巨大的话题。 为了使本文简短,我详细介绍了许多细节。 关于代码兼容性还有另一个更大的话题,那就是更加复杂。 (例如,为什么在Java中重命名参数是兼容的更改而在Python中是不兼容的更改?)
Most of my understanding of schema evolution was learned several years ago at LinkedIn. Here are two articles written by former LinkedIn engineers:
我对模式演化的大部分了解是几年前在LinkedIn上获得的。 这是前LinkedIn工程师写的两篇文章:
翻译自: https://medium.com/@stevenheidel/backward-vs-forward-compatibility-9c03c3db15c9
兼容性向前还是向后