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:
- 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.
- 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.