后向兼容性准则

编写后向兼容代码可能很难且难以测试。 本文讨论了在 .NET Orleans 中编写后向兼容代码的准则。 本文介绍 VersionAttributeObsoleteAttribute 的用法。

绝不更改现有方法的签名

由于 Orleans 序列化程序的工作方式,你绝不应更改现有方法的签名。

以下示例是正确的:

[Version(1)]
public interface IMyGrain : IGrainWithIntegerKey
{
    // First method
    Task MyMethod(int arg);
}
[Version(2)]
public interface IMyGrain : IGrainWithIntegerKey
{
    // Method inherited from V1
    Task MyMethod(int arg);

    // New method added in V2
    Task MyNewMethod(int arg, obj o);
}

以下示例是不正确的:

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

重要

不应在代码中进行此更改,因为这是一个导致非常糟糕的副作用的不良做法示例。 这是在你仅仅重命名参数名称时可能发生的情况示例:假设我们在群集中部署了两个以下接口版本:

[Version(1)]
public interface IMyGrain : IGrainWithIntegerKey
{
    // return a - b
    Task<int> Subtract(int a, int b);
}
[Version(2)]
public interface IMyGrain : IGrainWithIntegerKey
{
    // return a - b
    Task<int> Subtract(int b, int a);
}

此方法似乎完全相同。 但是,如果使用 V1 调用客户端,并且请求由 V2 激活处理:

var grain = client.GetGrain<IMyGrain>(0);
var result = await grain.Subtract(5, 4); // Will return "-1" instead of expected "1"

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

避免更改现有方法逻辑

这似乎是显而易见的,但在更改现有方法的主体时,你应该非常小心。 除非要修复 bug,否则在需要修改代码时最好只是添加新方法。

例如:

// V1
public interface MyGrain : IMyGrain
{
    // First method
    Task MyMethod(int arg)
    {
        SomeSubRoutine(arg);
    }
}
// V2
public interface MyGrain : IMyGrain
{
    // Method inherited from V1
    // Do not change the body
    Task MyMethod(int arg)
    {
        SomeSubRoutine(arg);
    }

    // New method added in V2
    Task MyNewMethod(int arg)
    {
        SomeSubRoutine(arg);
        NewRoutineAdded(arg);
    }
}

请勿从 grain 接口中删除方法

除非你确定不再使用它们,否则不应从 grain 接口中删除方法。 如果要删除方法,应按 2 个步骤完成删除:

  1. 部署 V2 grain,并将 V1 方法标记为 Obsolete

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

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