2012-04-04 18 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

私は、少なくともあなたは、このようなロジック行うことができます方法作成することをお勧め:

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

あなたはバージョンを集中する必要がある場合には、持っている:このように使用、その後

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(ServerFeatures.FancyFeatureRequiredVersion)) 
{ 
    ... 
} 

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

クイックレスポンスありがとう!最低限、私はIsValidToRunのようなメソッドでこのロジックを集中させるべきだと思います。私はServerFeaturesクラスは素晴らしいアイデアだと思うし、新しいビルドが出されたときにどのチェックを排除できるかを追跡するのに役立つかもしれない。 –

+0

@JohnRussell、それはまたあなたの目的を達成するための良い方法をビルドすることができます。チェックが不要になったときに 'ServerFeatures'バージョンを削除し、コンパイルして、それに依存する壊れた場所をすべて修正します。 –

+0

まさに!私はそれが好きです!私は実際にServerFeatureを列挙して、ServerFeatureへのバージョンマッピングをヘルパーメソッドで実行すると思います。大きなswitch文。これにより、同じバージョンを複数の機能に再利用し、すべてのロジックを1つの場所に持たせることができます。 IsValidToRunはServerFeatureをパラメータとして受け取り、うまくいけば、消費者がフライバージョンのオブジェクトで使用するのを防ぎます。助けてくれてありがとう! –

0

uldはサービス契約のバージョン管理を実装する必要があります。その時点で、このVersioning Strategiesページに記載されているように、WCF自身の機能を利用してクライアントを壊さない小さな変更を無視することができます。

図1では、操作シグネチャに新しいパラメータを追加したり、操作シグネチャからパラメータを削除したり、新しい操作を追加したりすると、クライアントは影響を受けません。

まだ突然の変更がある場合や、お客様のクライアントが両方のバージョンをサポートする必要がある場合(私はあなたの導入戦略がわからないので間違っている場合は修正してください)、異なるバージョンのサービスを別々のエンドポイントクライアントコードにWCFクライアントファクトリがあり、適切なエンドポイントのクライアントを返すように構成できます。

この時点で、さまざまなクライアントでさまざまな実装が分離されています。これはおそらく、現在よりもクリーンでメンテナンスの悪いことではありません。私たちは私たちのサービスのための2つの異なる契約、古いものと新しいものを持っていることとします物事をクリアする


非常に基本的なサンプル実装。両方のサービスが同じ名前で異なる名前空間を持っているか

[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> 

ファイン、我々は今、興味深い部分に、サービス側を持っている:我々は、同じインターフェイスを使用してサービスと通信したいと思います。この場合は古いものを使用しますが、その間にアダプタを置く必要があります。

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(); 
    } 
} 

私がするために使用するクライアントの決定を委任:

IServiceOld client = *Magic* 

client.DoWork(); 

この場合、魔法は、このような単純な工場がある:理想的には、私たちのクライアントコードでは、我々はこのような何かをするだろう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クライアントがどのような追加作業をしなければならないか、どのクライアントを使用するかの決定が工場に委任されたものから抽象化されます。私はこのサンプルのいくつかのバリエーション/拡張があなたのニーズに合うことを願っています。