Using services that extract data from web pages as an example (for instance Mercury https://mercury.postlight.com/web-parser/ or Diffobt https://www.diffbot.com/products/automatic/#article) I want to ask a question about how to construct the interfaces and class implementations for a dependency injection (DI)/inversion of control (IoC) scenario.

I'm trying to determine how to best create an IPageExtractor interface and the subsequent concrete implementations MercuryPageExtractor and DiffbotPageExtractor that implement the IPageExtractor interface given that the actual implementations are quite different.

Obviously this is the entire point of implementing an interface in the first place but I'm having a mental block I need help getting past.

For instance, Mercury uses a key and secret for authentication whereas Diffbot uses a token only. Ok maybe this isn't so different but where I get confused is I have to accommodate this in the interface and of course interfaces are lowest common denominator. Would I for example pass in a collection of auth data and let the concrete implementations sort it out?

Another different is that Mercury returns 3 pieces of data about a web page whereas Diffbot returns like 20 (this may not be technically accurate but the example of possible differences is what I'm getting at). How would I design the interface and concrete implementations to handle this?

A final significant difference is what constitutes success and failure is very different in each case and the message types (format, structure, content) returned for success or failure vary widely as well.

Can you please help think about this problem?

I'm working in C#/.NET but thinking about this in a language-independent way.

1

There are 1 best solutions below

0
On BEST ANSWER

Lets take in another scenario for interface driven abstract factory

Before starting, we need to be clear that an interface specifies what needs to be done, with a subtle indication of input and desired output. That said, it does not enforce that the input and output both need to be well defined, in that, they can also be taken as contracts themselves. If we are not completely clear about what the input and output be structured as, we can use marker interfaces; those without any structure at all..

Lets say we have a shopping cart and we need to provide for payment options. The common choices are netbanking, credit cards and paypal. Lets have the definitions of this scenario

public interface ICustomerAccountInformation {  }

public interface IPaymentClient {  }  // Identifies whom the payment is intended for

public interface ITransactionDetails : IPaymentClient {  }  // Identifies amount, beneficiary details to be reflected in customer account

public interface IPaymentStatus { }

public interface IPaymentProvider  { 
    IPaymentStatus ProcessPayment(ICustomerAccountInformation customerInfo, ITransactionDetails transaction);

}


public class PayPal : IPaymentProvider  {

    public IPaymentStatus ProcessPayment(ICustomerAccountInformation customerInfo, ITransactionDetails transaction)  {
           /* Open paypal's login page, 
             do its own checks and process payment. 
             Once successfull, send back to referrer with the payment status
           */
    }

public class NetbankingProvider : IPaymentProvider  {

    public IPaymentStatus ProcessPayment(ICustomerAccountInformation customerInfo, ITransactionDetails transaction)  {
           /* Redirect to bank selection
             redirect to bank's login page, 
             do its own checks and process payment. 
             Once successfull, send back to referrer with the payment status
           */
    }

In the entire flow, each of the providers are free to ask for whatever information is required (some just take the credentials while others ask for OTP as additional). They also return status back as per their own structure. However, at a logical level, they take user's credentials and return back a message if the payment was successful or not

You can design the factories to be independent of any other provider design. Just have the marker interfaces over each contract.

When doing the DI code (say NInject), use the "WhenInjectedInto" or similar construct to inject the correct implementation into each provider being used. Your factories will be available for use based on user's selection, without impacting your core business flow.

The main rule of DI is to to entirely abstract out the non-core business from the core one. The core business should not care about what each provider expects, just to have a faint idea about expected functionality. Marker interfaces suit this requirement best, especially when you have started design but are not completely clear. You can later improve the marker interfaces as you get more clarity into the process involved.

Let me know if you need further clarity into design.