3

我们公司有一个网络产品,它提供了近 160 种不同的 REST API 来与我们的产品进行交互。目前,API 仅供内部客户端产品使用,但最终会公开。我正在编写 ac# 包装器以使用 RestSharp 库调用这些 REST API,到目前为止一切正常。例如,获取账户信息的 GET API 之一是:

/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 对象并返回它;并且, Public AccountV2 GetAccountV2 ( int accountId )- 它将在 URL 中调用与 V2 相同的 API,并将 JSON 反序列化为 V2 对象并返回它。

这种方法的问题在于——我必须重写几乎相同的代码来为 160 个 API 创建 V2 函数。此外,如果有更新版本的 API 出现,我将不得不再次做同样的事情 - 为 V3 重写 160 个方法。

  • 其次是使用泛型、抽象工厂、动态代理或其他设计,这样我就不必为不同版本的 API 重写几乎相同的代码

对于第二种方法,我不确定如何实现它。我的目标是重构我的包装器代码,以便需要最少的代码更改,它是可扩展的——这意味着,如果我们更改 API 版本和返回的数据,我以后不必再次重写大量的东西。

在重构我的代码和帮助我选择正确的设计模式方面的任何帮助都会非常有帮助。任何示例都会有所帮助。

4

2 回答 2

1

我能想到有两种可行的方法:

  1. 让您的 DTO 从动态对象继承并返回动态对象而不是具体实例。

  2. 使用RealProxy类返回正确的类型。

于 2012-10-19T15:51:14.610 回答
1

首先要做的是创建一个名为 IAccount 的接口,您的所有其他帐户(版本)都将实现该接口。这是最简单的示例。

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

我们这样做的原因是您的所有帐户版本都是相同的类型。你会看到这是如何工作的。

接下来,您要创建实现此接口的基本帐户类型。

仔细看看这堂课。它实现了加载并仅加载您目前知道的值:name

我还伪造了一个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 帐户。现在很容易,因为我们只是再次实现了接口,但这次是从基本帐户派生的,因此我们获得了您不想再次编写的所有原始方法。

当我们创建工厂方法时,这两件事使它变得强大。

再次,仔细看看这个。

什么是重要的?

AccountV2 也实现了自己的 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 再次编写所有方法。这是因为您将在基础中将所有方法设置为受保护(Account) 类,然后派生类 (AccountV2) 将能够获取它们。

Enter,Stage Left,Factory Method 我创建了一个简单的 Factory,它在带有静态方法的静态类中实现,因此您可以从任何地方调用它。它如下所示:

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)。swith 语句根据传入的版本号确定要构建的版本。

返回的 JSON 匹配类型没有额外的代码

美妙之处在于返回的 JSON 现在将包含您期望的类型的名称/值。

因此,当我为版本 1 发布 1 时,我返回的 JSON 如下所示: 账户 JSON

当我发布 2 版本时,我返回的 JSON 看起来像: AccountV2 JSON

示例代码

我以整个示例为例,我想分享它,但想不出一种安全的方法来做到这一点。如果您希望我将其发送或保存在某处,请在此处回复。

于 2014-04-23T15:09:50.440 回答