2

I’m struggling to come up with a design (pattern) for an application/service which will hit multiple web services (external 3rd-party APIs), parse, combine and return them in a single response.

The application will have the following behaviors/steps (in no particular order):

  • Search hotels
  • Fetch details of selected hotel
  • Fetch room details of selected hotel
  • Fetch cancellation policies
  • Fetch extra guest charges
  • Block Room Book Room
  • Get booked room details
  • Cancel booked room

Not all APIs will have/implement the above steps. For example, few APIs might not have the 3rd step or 4th step or both. In short, the steps/combination of steps can change per API provider.

Once a search for hotel is initiated from the web application, the request will be sent to this application. It will then fetch all active API providers (common C# interface) from the database and send request to each API. I want to expose a common interface while instantiating the provider classes. The same methodology will be followed for each step.

I’m trying to think of a design pattern and design a solution which will adhere to the SOLID principles. On the basis of the above requirement, I thought about the following patterns but have been unsuccessful so far:

  1. Factory method: Define a common interface with all the above steps/behaviors and use factory method to create the object(s). However, this will lead to violation of Liskov substitution principle since some APIs might not implement everything.
  2. Decorator pattern: Define a component (interface) which will contain the common steps. Add steps/behavior dynamically using decorator. However, with this design, the component interface will have no knowledge about the added steps/behaviors which defeats our purpose of using a common interface.

Is there any other pattern which is ideal for my problem or should I go ahead with the 1st solution and live with the principle violation?

Code example to help clarify the problem (no pattern followed, meant to clarify the involved actors) :

public interface IAPIProvider
{
    void SearchHotels();
    void GetHotelDetails();
    void GetRoomDetails();
    void GetCancellationPolicies();
    void GetExtraGuestChargeDetails();
    void BlockRoom();
    void BookRoom();
    void GetBookingDetails();
    void CancelBooking();
}


public class ConcreteProviderA : IAPIProvider
{
    public void BlockRoom()
    {
        //call api
    }

    public void BookRoom()
    {
        //call api
    }

    public void CancelBooking()
    {
        //call api
    }

    public void GetBookingDetails()
    {
        //call api
    }

    public void GetCancellationPolicies()
    {
        //call api
    }

    public void GetExtraGuestChargeDetails()
    {
        //call api
    }

    public void GetHotelDetails()
    {
        //call api
    }

    public void GetRoomDetails()
    {
        //call api
    }

    public void SearchHotels()
    {
        //call api
    }
}

public class ConcreteProviderB : IAPIProvider
{
    public void BlockRoom()
    {
        throw new NotImplementedException();
    }

    public void BookRoom()
    {
        //call api
    }

    public void CancelBooking()
    {
        //call api
    }

    public void GetBookingDetails()
    {
        //call api
    }

    public void GetCancellationPolicies()
    {
        throw new NotImplementedException();
    }

    public void GetExtraGuestChargeDetails()
    {
        throw new NotImplementedException();
    }

    public void GetHotelDetails()
    {
        //call api
    }

    public void GetRoomDetails()
    {
        //call api
    }

    public void SearchHotels()
    {
        //call api
    }
}

/// <summary>
/// Request from web/mobile will land in this class
/// </summary>
public class Client
{
    public void SearchHotel()
    {
        //get active providers from database & instantiate them
        List<IAPIProvider> providers = new List<IAPIProvider>();            
        foreach(var provider in providers)
        {
            //search hotels from each API provider
            provider.SearchHotels();
        }
    }

    public void GetHotelDetails(int hotelId, int hotelProviderId)
    {
        IAPIProvider provider = null;

        //here, resolve the correct API provider using the input parameters            

        //fetch hotel details from the resolved API provider
        provider.GetHotelDetails();
    }

    //Other methods
}

Code example as a gist.

  • Possible duplicate of Choosing the right Design Pattern – gnat Jun 2 '17 at 11:01
  • A common interface should not lead to any violations of Liskov substitution principle as long as you implement it in a consistent manner. What's wrong with throwing an exception and letting the calling code deal with it? – Goyo Jun 2 '17 at 11:14
  • 1
    I am currently working on producing a library that solves the same or a very similar problem. See this question for a breakdown of the patterns I used. – John Wu Jun 5 '17 at 19:24
3

You can use the Adapter pattern to create a wrapper for each third-party API.
These wrappers will all provide the same interface to (the rest of) your application and should implement some sane default behavior for operations that the third-party API doesn't support.

  • Thanks @Bart, i'll see if adapter would work. For the being, i've updated my question with code example. – Sang Suantak Jun 2 '17 at 10:02
0

Looking at your objection to the Factory pattern it looks to me that instead you could use the Strategy pattern where you can fine tune the functionality that you need.

  • Thanks @arame, i've updated the question with code example. Hope it helps clarifying my problem. – Sang Suantak Jun 2 '17 at 10:06
0

If you want to have an object-oriented solution (I suspect that is why you want to conform to SOLID), and you already have the use-cases you mentioned above, continue with identifying the "things" you want to involve. For example:

  • Hotel
  • Room
  • Reservation (of a Room)
  • Cancellation Policy
  • etc.

Don't start implementing the use-cases directly, that would result in a procedural design (arguably the opposite of object-orientation). Try decompose your use-cases into behavior your "things" could do for you. For example:

  • Room.reserve(): Reservation
  • Reservation.cancel(): ...
  • ...

I would also add Hotel.display(), and similar methods instead of Hotel.getRooms() (which leads to Law of Demeter violations), but that is somewhat controversial.

This will give you a "model", that you can implement using remote calls, webservices or whatever.

So, I would suggest to start with a "good" design (with objects that mean something and do stuff), concentrate less on "design patterns".

0

It looks to me like your code sample is heading in the right direction as it is, pattern or not.

If you want to define a common interface for all of your 3rd party APIs, there's nothing wrong with saying "the APIs should have these capabilities" and then handling the cases where they don't.

Regardless, you will have to account for when the actions you expect to happen end up failing. Like if you try to book a room, but it's too late because someone else blocked it first. Or if the 3rd party API goes down for some reason, or you hit a rate limit on their APIs, or the network goes down, etc. So I don't see anything wrong with adding some custom error handling if the APIs don't exist for certain methods. Your code looks fine as it is.

protected by Community Jun 2 '17 at 11:47

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.