3

私たちの会社には、製品と対話するためのほぼ 160 の異なる REST API を提供する Web 製品があります。現在、API は内部クライアント製品でのみ使用されていますが、最終的に公開される予定です。RestSharp ライブラリを使用してこれらの REST API を呼び出す ac# ラッパーを作成していますが、これまでのところ問題なく動作しています。たとえば、アカウント情報を取得する GET API の 1 つは次のとおりです。

/api/account/{id}

次のような JSON データが返されます。

{ “Id” : “12345”, “Name” : “Test Account” }

データを受信したら、JSON 文字列を適切な DTO に逆シリアル化し、オブジェクトを返します。したがって、API ラッパーの私の関数は次のとおりです。

Public Account GetAccount ( int accountId )
{
      //create restsharp client and request
      return restClient.Execute<Account> ( restRequest )
}

しかし、現在の問題は、API が変化していることです。新しいバージョンの API を導入しています。新しいバージョンでは、エンドポイントは同じままです。唯一の違いは、異なるデータを返すことです。

たとえば、アカウントを取得するための V1 API は次のとおりです: (バージョンが設定されていない場合、デフォルトでサーバーは V1 を使用します)

GET - /api/V1/account/{id} 

次のような JSON データが返されます。

{ “Id” : “12345”, “Name” : “Test Account” }

アカウントを取得するための V2 API は次のとおりです。

GET - /api/V2/account/{id} 

次のような JSON データが返されます。

{ “Id” : “12345”, “Name” : “Test Account”, “Company” : “Some Company”, “Status” : “Some Status” }

将来、同じ API の新しいバージョンが別のデータを返す可能性があります。また、API のバージョンが変わっても、新しいバージョンのすべての API が変わるわけではありません。非常に多くの API が引き続き V1 オブジェクト データを送信します。V1 バージョンと比較して、特定の数の API のみが異なるデータを送信します。

私のジレンマは、既存の API ラッパーをリファクタリングする方法です。

  • 最初のアプローチは、API の新しいバージョンごとに新しいメソッドを作成することです。例: Public AccountV1 GetAccountV1 ( int accountId )- V1 API を呼び出し、JSON を V1 オブジェクトに逆シリアル化して返します。- URL で V2 と同じ API を呼び出し、 Public AccountV2 GetAccountV2 ( int accountId )JSON を V2 オブジェクトに逆シリアル化して返します。

このアプローチの問題点は、160 API 用の V2 関数を作成するためにほぼ同じコードを書き直さなければならないことです。また、新しいバージョンの API が登場した場合、同じことをもう一度行う必要があります。V3 用に 160 個のメソッドを書き直します。

  • 2 つ目は、Generics、Abstract Factory、動的プロキシ、またはその他の設計を使用することです。これにより、異なるバージョンの API に対してほぼ同じコードを書き直す必要がなくなります。

2番目のアプローチについては、実装方法がわかりません。私の目標は、最小限のコード変更で済むような方法でラッパー コードをリファクタリングすることです。拡張可能です。つまり、API バージョンを変更してデータを返した場合に、将来、大量のことを書き直す必要はありません。

私のコードをリファクタリングし、正しいデザインパターンを選択するのに役立つものは本当に役に立ちます. どんな例でも役に立ちます。

4

2 に答える 2

1

それがうまくいくと私が考えることができる2つの方法があります:

  1. DTO を動的オブジェクトから継承させ、具体的なインスタンスではなく動的オブジェクトを返します。

  2. RealProxyクラスを使用して正しいタイプを返します。

于 2012-10-19T15:51:14.610 に答える
1

最初に行うことは、他のすべてのアカウント (バージョン) が認識する IAccount というインターフェイスを作成することです。最も単純な例は次のようになります。

public interface IAccount
{
   bool load(int id);
}

これを行う理由は、すべてのアカウント バージョンが同じタイプになるようにするためです。これがどのように機能するかがわかります。

次に、このインターフェイスを実現するベース アカウント タイプを作成します。

このクラスをよく見てください。ロードを実装し、これまでに知っている値のみをロードします: name

また、db から値をロードすることをエミュレートするload()を偽造しますが、これは単なる文字列です。

public class Account: IAccount
{
protected int id;

public int Id
{
    get { return id; }
}
protected string name;

public string Name
{
    get { return name; }
}

protected Account()
{

}

public Account(int id)
{
    this.load(id);
}

 public bool load(int id)
 {
     // fake method to load class from a resource (database, file, etc).
     string [] accountRecord = getItemById(id);
     this.name = accountRecord[0];

     return true;
 }

  private string[] getItemById(int id)
  {
    // fake method to "load" from the database.  just emulates getting a value from db.
     string[] allItems = new string[1];
     allItems[0] = "First Account";
     return allItems;
  }

次に、バージョン 2 アカウントが必要です。今は簡単です。なぜなら、インターフェイスを再び認識したからです。しかし、今回はベース Account から派生するため、再度記述したくない元のメソッドをすべて取得します。

これら 2 つのことが、ファクトリ メソッドを作成するときにこれを強力にします。

繰り返しますが、これをよく見てください。

何が重要か?

AccountV2 は独自の load() メソッドも実装しています。基本クラスの load を使用することもできますが、さらに多くの値を読み込む必要があるため、base.load() を呼び出してから、新しい値 (Company と状態) .

 public class AccountV2 : Account, IAccount
    {
        private string company;

        public string Company
        {
          get { return company; }
        }
        private string status;

        public string Status
        {
          get { return status; }
        }

        public AccountV2(int id) :base(id)
        {
            this.load(id);
        }

        public AccountV2(int id, string name, string company, string status)
        {
            this.id = id;
            this.name = name;
            this.company = company;
            this.status = status;
        }

        new bool load(int id)
        {
            // loads all the base items
            base.load(id);
            // now load all your version 2 items here
            string [] accountRecord = getItemById(id);
            this.company = accountRecord[0];
            this.status = accountRecord[1];
            return true;
        }

        public string[] getItemById(int id)
        {
            string [] allItems = new string [3];
            allItems[0] = "Big Company, Inc";
            allItems[1] = "ONLINE"; // status
            return allItems;
        }
    }

私たちが持っているもの:ここまでのまとめ

この時点で、同じタイプの 2 つのクラスがあり、バージョン 2 用にすべてのメソッドを再度記述する必要なく、同じ機能をすべて簡単に実装できます。これは、ベースですべてのメソッドを protected に設定するためです。 (Account) クラスと派生クラス (AccountV2) はそれらを取得できます。

エンター、ステージ左、ファクトリ メソッド 静的メソッドを使用して静的クラスに実装される単純なファクトリを作成したので、どこからでも呼び出すことができます。次のようになります。

public static class AccountFactory
    {
        public static IAccount BuildAccount(int version, int id)
        {
            switch (version)
            {
                case 1: 
                    {
                        Account tempAcct = new Account( id);
                        return tempAcct;
                        break;
                    }
                case 2:
                    {
                        AccountV2 newAccount = new AccountV2(id);
                        return newAccount;
                        break;
                    }
                default:
                    {
                        return null;
                        break;
                    }
            }
        }
    }

あとは、誰かが API に投稿したときに AccountFactor.BuildAccount(version, id) を呼び出すだけです。swicth ステートメントは、送信されたバージョン番号に基づいて、ビルドするバージョンを決定します。

追加コードなしで返される JSON 一致タイプ

優れた点は、返された JSON に、その型に期待される名前/値が含まれるようになったことです。

したがって、バージョン 1 の 1 を投稿すると、返された JSON は次のようになります。 アカウント JSON

バージョンに 2 を投稿すると、返された JSON は次のようになります。 AccountV2 JSON

サンプルコード

このサンプル全体が例としてあり、共有したいのですが、安全な方法が思いつきません。送信または保存する場合は、ここに返信してください。

于 2014-04-23T15:09:50.440 に答える