Tejas Vora
Tejas Vora

Reputation: 548

What design pattern to use in order to dynamically return desired DTO in c#

Our company has a web product which provides almost 160 different REST APIs to interact with our product. Currently, APIs are being used by internal client products only – but it will be made public eventually. I am writing a c# wrapper to call these REST APIs using RestSharp library and so far things are working fine. For example, one of the GET API is to get account information is:

/api/account/{id}

Which returns JSON data back like:

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

Upon receiving data, I just deserialize JSON string into appropriate DTO and return object back. So, my function in API wrapper is:

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

However, now the problem is that APIs are changing. We are introducing a new version of APIs. In newer version, end points remain the same, the only difference is that they return different data.

For example, V1 API to get account is: (if version is not set, by default server will use V1)

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

Which returns JSON data back like:

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

V2 API to get account is:

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

Which returns JSON data back like:

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

In future, it is possible that newer version of same API may return different data back. Also, even if the version of API is changing, not all APIs are in newer version are changing. So many APIs will continue sending V1 object data. Only certain number of APIs will send different data compare to V1 version.

Now my dilemma is how to I refactor my existing API wrapper:

Problem with this approach is that – I have to rewrite almost same code to create V2 functions for 160 APIs. Also, if newer version of API comes in, I would have to do same thing again - rewrite 160 methods for V3.

For the second approach, I am not sure how to implement it. My goal is to refactor my wrapper code in such a way that minimum code changes are required, it is extensible – meaning, I do not have to rewrite tons of things again in future if we change API version and returned data.

Any help in refactoring my code and help me choose correct design pattern would be really helpful. Any example would be helpful.

Upvotes: 3

Views: 3596

Answers (2)

raddevus
raddevus

Reputation: 9077

First thing to do is create an Interface called IAccount which all your other account (versions) will realize. Here's what the simplest example will look like.

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

The reason we do this is so that all of your Account versions are the same type. You'll see how this works.

Next, You want to create your base Account type, which realizes this Interface.

Take a close look at this class. It implements the load and loads just the values you know about so far: name.

I also fake up a load() which emulates loading the value from the db, but is just a string.

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

Now we need our Version 2 Account. It is easy now, because we just again realize the Interface, but this time derive from the base Account so we get all of its original methods that you don't want to write again.

These two things are what make this powerful, when we create our factory method.

Again, look at this closely.

What's Important?

AccountV2 also implements its own load() method -- it could've just used the base class load, but we need to load more values, so we call the base.load() and then we load up the new values (Company and Status).

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

What We Have : Summary To This Point

At this point we have two classes which are the same type and which can easily implement all of the same functionality without having to write all the methods again for the version 2. That is because you would set all of the methods to protected in the base (Account) class and then the derived class (AccountV2) would be able to get them.

Enter, Stage Left, The Factory Method I have created a simple Factory which is implemented in a static class with a static method so you can call it from anywhere. It looks like the following:

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

Now all we have to do is call AccountFactor.BuildAccount(version, id) when someone posts to our API. The swicth statement determines which version to build, based upon the version number sent in.

Returned JSON Matches Type With No Extra Code

The beauty is that the returned JSON will now contain the name/values that you expect for the type.

So, when I post a 1 for version 1 my returned JSON looks like: Account JSON

When I post a 2 for version my returned JSON looks like: AccountV2 JSON

Sample Code

I have this entire sample as an example and I'd like to share it but can't think of a safe way to do that. Reply here if you want me to send it or save it somewhere.

Upvotes: 1

jeuton
jeuton

Reputation: 543

There are two ways that I can think of that would work:

  1. Make your DTO's inherit from a Dynamic Object and return the Dynamic Object instead of a concrete instance.

  2. Use the RealProxy class to return the correct type.

Upvotes: 1

Related Questions