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属性が「キックイン」し、突然、これらのコードブロックを指すコンパイラ警告がいくつか表示されます...誰かがこのようなことをしましたか?または、これを管理するためのより良い方法はありますか?

(2)などのハードコードされたバージョンを見るたびにうずくまりますnew Version(2, 1, 3)。さらに悪いことに、開発中はリリースされる最終バージョンがわからないため、バージョンチェックは、開発者がチェックを追加したときの現在のビルド番号+1に基づいています。これは機能しますが、あまりきれいではありません。これをどのように改善できるかについてのアイデアはありますか?

ありがとう!

4

2 に答える 2

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); }
    }
}
于 2012-04-04T13:10:24.893 に答える
0

別の方法は、サービスコントラクトのバージョン管理を実装することです。その時点で、このバージョン管理戦略ページにリストされているように、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>

これで、サービス側ができました。興味深い部分です。同じインターフェイスを使用してサービスと通信したいと思います。この場合、古いものを使用しますが、間にアダプターを配置する必要がある場合があります。理想的には、クライアントコードで、次のようなことを行います。

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クライアントが実行する必要のある追加の作業から抽象化され、使用するクライアントに関する決定はファクトリに委任されます。このサンプルのバリエーション/拡張があなたのニーズに合うことを願っています。

于 2012-04-04T14:16:29.420 に答える