2012-04-04 199 views
2

我正在研究必须支持向后兼容性的客户端服务器应用程序(.NET 4,WCF)。换句话说,老客户应该与新服务器兼容,反之亦然。因此,我们的客户代码中充斥着诸如以下语句:客户端兼容性检查管理

if (_serverVersion > new Version(2, 1, 3)) 
{ 
    //show/hide something or call method Foo()... 
} 
else 
{ 
    //show/hide something or call method Foo2()... 
} 

很明显,这成为了维护的一个噩梦。幸运的是,我们被允许向后兼容每个次要版本。当我们达到可以打破兼容性的地步时,我想清理上面例子中的代码。

我的问题:

(1)有没有一种方法可以轻松地识别代码块,如这些,当他们不再是“有效”?我最初的想法是以某种方式有条件地应用基于程序集版本的Obsolete属性。当我们遇到一个新的次要版本时,Obsolete属性会“启动”,并且突然之间我们会有几个编译器警告指向这些代码块......有没有人做过类似的事情?还是有更好的方法来管理这个? (2)每当我看到硬编码的版本,例如new Version(2, 1, 3),我都会畏缩不前。更糟糕的是,在开发过程中,我们不知道最终发布的版本,所以当开发人员添加检查时,版本检查基于当前版本号+ 1。虽然这可行,但它不是很干净。任何想法如何可以改善?

谢谢!

回答

1

我建议至少创建一个方法,你可以像这样的逻辑:

public static class ServerUtilities 
{ 
    public static bool IsValidToRun(Version desiredVersion) 
    { 
     if (_serverVersion >= desiredVersion) 
      return true; 
     else if (/* your other logic to determine if they're in some acceptable range */) 
      return true; 

     return false; 
    } 
} 

然后,使用这样的:

if (ServerUtilities.IsValidToRun(new Version(2, 1, 3))) 
{ 
    // Do new logic 
} 
else 
{ 
    // Do old logic 
} 

如果您需要集中的版本,有一个静态的版本映射功能库,以便您可以拨打:

if (ServerUtilities.IsValidToRun(ServerFeatures.FancyFeatureRequiredVersion)) 
{ 
    ... 
} 

public static class ServerFeatures 
{ 
    public static Version FancyFeatureRequiredVersion 
    { 
     get { return new Version(2, 1, 3); } 
    } 
} 
+0

感谢您的快速响应!至少,我认为我们应该用IsValidToRun这样的方法来集中这个逻辑。我认为ServerFeatures类是一个好主意,并且可以帮助我们跟踪哪些检查可以在新版本发布时取消。 – 2012-04-04 13:31:16

+0

@JohnRussell,这也将给你一个很好的方式来打破目的。当你不再需要检查,编译,然后修复所有依赖它的被破坏的地方时,你可以删除一个'ServerFeatures'版本。 – 2012-04-04 13:38:56

+0

准确!我喜欢!我想我将实际上使ServerFeature成为一个枚举,然后用辅助方法执行Version to ServerFeature映射,例如一个大转换语句。这将允许我们重复使用相同版本的多个功能,并将所有这些逻辑放在一个位置。然后,IsValidToRun可以将ServerFeature作为参数,并有望阻止消费者使用动态版本对象。谢谢您的帮助! – 2012-04-04 15:08:29

0

另一种选择wo您应该实施服务合约的版本控制:此时,您可以利用WCF自己的功能忽略不会中断客户端的微小更改,如Versioning Strategies页面上列出的那样。

在图1中,您可以看到,向操作签名添加新参数时,从操作签名中删除参数并添加新操作,客户端不受影响。

如果仍然发生重大更改或您的客户端必须支持这两个版本(请纠正我,如果我错了,因为我不知道您的部署策略),您可以在不同端点提供不同版本的服务并在您的客户端代码中拥有一个WCF客户端工厂,然后可以将其配置为返回适当端点的客户端。

在这一点上,你已经隔离了不同客户端的不同实现,这可能比现在更清洁,更少维护噩梦。


非常基本的示例实现以澄清一些事情:假设我们有我们的服务,旧的和新的两种不同的合同。

[ServiceContract(Name = "Service", Namespace = "http://stackoverflow.com/2012/03")] 
public interface IServiceOld 
{ 
    [OperationContract] 
    void DoWork(); 
} 

[ServiceContract(Name = "Service", Namespace = "http://stackoverflow.com/2012/04")] 
public interface IServiceNew 
{ 
    [OperationContract] 
    void DoWork(); 

    [OperationContract] 
    void DoAdditionalWork(); 
} 

请注意两个服务如何具有相同的名称但名称空间不同。

让我们来处理让客户端必须能够同时支持扩展和新服务以及旧服务的问题。假设我们以前只是称为DoWork,并且我们想要处理客户端的情况,我们想调用DoAdditionalWork方法,因为假设DoAdditionalWork可能需要来自客户端的一些额外参数。那么服务的配置可以是这样的:

<service name="ConsoleApplication1.Service"> 
    <endpoint address="http://localhost:8732/test/new" binding="wsHttpBinding" contract="ConsoleApplication1.IServiceNew" /> 
    <endpoint address="http://localhost:8732/test/old" binding="wsHttpBinding" contract="ConsoleApplication1.IServiceOld" /> 
    ... 
</service> 

好,我们有服务端,现在到了有趣的部分:我们愿与使用相同的接口服务进行通信。在这种情况下,我会使用旧的,但您可能需要在其间插入适配器。理想情况下,我们的客户端代码,我们会做这样的事情:

IServiceOld client = *Magic* 

client.DoWork(); 

在这种情况下,神奇的是一个简单的工厂是这样的:

internal class ClientFactory 
{ 
    public IServiceOld GetClient() 
    { 
     string service = ConfigurationManager.AppSettings["Service"]; 
     if(service == "Old") 
      return new ClientOld(); 
     else if(service == "New") 
      return new ClientNew(); 

     throw new NotImplementedException(); 
    } 
} 

我委托的使用到客户端的决定app.config,但是你可以在那里插入你的版本检查。 ClientOld的实现仅仅是IServiceOld一个普通WCF客户端:

public class ClientOld : IServiceOld 
{ 
    private IServiceOld m_Client; 

    public ClientOld() 
    { 
     var factory = new ChannelFactory<IServiceOld>(new WSHttpBinding(), "http://localhost:8732/test/old"); 
     m_Client = factory.CreateChannel(); 
    } 

    public void DoWork() 
    { 
     m_Client.DoWork(); 
    } 

    ... 
} 

ClientNew而是实现我们希望的行为,即调用DoAdditionalWork操作:

public class ClientNew : IServiceOld 
{ 
    private IServiceNew m_Client; 

    public ClientNew() 
    { 
     var factory = new ChannelFactory<IServiceNew>(new WSHttpBinding(), "http://localhost:8732/test/new"); 
     m_Client = factory.CreateChannel(); 
    } 

    public void DoWork() 
    { 
     m_Client.DoWork(); 
     m_Client.DoAdditionalWork(); 
    } 
    ... 
} 

就是这样,现在我们的客户可以用在下面的例子中:

var client = new ClientFactory().GetClient(); 
client.DoWork(); 

我们取得了什么成果?使用客户端的代码是从实际的WCF客户端必须做的额外工作中抽象出来的,并决定将使用哪个客户端委派给工厂。我希望这个样本的一些变化/扩展适合您的需求。