title: 向后兼容指南 description: 编写向后兼容的代码可能很困难,而且难以测试。


不要改变现有方法的签名

由于Orleans序列化器的工作方式,你不应该改变现有方法的签名。

下面的例子是正确的:

  1. [Version(1)]
  2. public interface IMyGrain : IGrainWithIntegerKey
  3. {
  4. // First method
  5. Task MyMethod(int arg);
  6. }
  1. [Version(2)]
  2. public interface IMyGrain : IGrainWithIntegerKey
  3. {
  4. // Method inherited from V1
  5. Task MyMethod(int arg);
  6. // New method added in V2
  7. Task MyNewMethod(int arg, obj o);
  8. }

这个则不正确:

  1. [Version(1)]
  2. public interface IMyGrain : IGrainWithIntegerKey
  3. {
  4. // First method
  5. Task MyMethod(int arg);
  6. }
  1. [Version(2)]
  2. public interface IMyGrain : IGrainWithIntegerKey
  3. {
  4. // Method inherited from V1
  5. Task MyMethod(int arg, obj o);
  6. }

注意:你不应该在你的代码中做这样的改变,因为这个例子展示了导致非常糟糕的副作用的错误做法。

这个例子解释了如果你只是重命名参数名称会发生什么: 假设我们在集群中部署了以下两个接口版本:

  1. [Version(1)]
  2. public interface IMyGrain : IGrainWithIntegerKey
  3. {
  4. // return a - b
  5. Task<int> Substract(int a, int b);
  6. }
  1. [Version(2)]
  2. public interface IMyGrain : IGrainWithIntegerKey
  3. {
  4. // return y - x
  5. Task<int> Substract(int y, int x);
  6. }

这两个方法似乎是相同的。但是,如果客户端是用V1调用的,而请求是由V2的激活处理的:

  1. var grain = client.GetGrain<IMyGrain>(0);
  2. var result = await grain.Substract(5, 4); // 会返回"-1"而不是期望的"1"

这是由于内部的Orleans序列化器的工作方式造成的。

避免改变现有方法的逻辑

这似乎很显然,但是当你修改一个现有方法的方法体时,你要非常小心。 除非你要修复一个错误,否则如果你需要修改代码,最好是直接添加一个新方法。

示例:

  1. // V1
  2. public interface MyGrain : IMyGrain
  3. {
  4. // First method
  5. Task MyMethod(int arg)
  6. {
  7. SomeSubRoutine(arg);
  8. }
  9. }
  1. // V2
  2. public interface MyGrain : IMyGrain
  3. {
  4. // Method inherited from V1
  5. // Do not change the body
  6. Task MyMethod(int arg)
  7. {
  8. SomeSubRoutine(arg);
  9. }
  10. // New method added in V2
  11. Task MyNewMethod(int arg)
  12. {
  13. SomeSubRoutine(arg);
  14. NewRoutineAdded(arg);
  15. }
  16. }

不要从Grain接口中移除方法

除非你确定方法不再被使用,否则你不应该从Grain接口中移除方法。 如果你想移除方法,这应该分两步进行:

  1. 部署V2的Grain,同时将V1的方法标记为Obsolete

    1. [Version(1)]
    2. public interface IMyGrain : IGrainWithIntegerKey
    3. {
    4. // First method
    5. Task MyMethod(int arg);
    6. }
    1. [Version(2)]
    2. public interface IMyGrain : IGrainWithIntegerKey
    3. {
    4. // Method inherited from V1
    5. [Obsolete]
    6. Task MyMethod(int arg);
    7. // New method added in V2
    8. Task MyNewMethod(int arg, obj o);
    9. }
  2. 当你确定不再有V1的调用时(实际上V1不再部署在运行的集群中),部署V3并删除V1方法。

    1. [Version(3)]
    2. public interface IMyGrain : IGrainWithIntegerKey
    3. {
    4. // New method added in V2
    5. Task MyNewMethod(int arg, obj o);
    6. }