0

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.

Robert
  • 353
  • 3
  • 14
  • Are you trying to consolidate those two implementations into the one class/interface? so you don't have to inject both ? – TheGeneral Jun 08 '18 at 02:32
  • No. I know I will have 2 concrete implementations as I said above. What I need help getting over a mental hurdle of is how to design an interface for things that have vastly different implementations. My question may be even more basic than you're thinking. Not sure if you're the downvoter but I'm asking for some patience here as I'm trying to figure out how to ask this question. – Robert Jun 08 '18 at 02:48
  • If i down voted you then you usually would know. Is there anyway reason you want to smash these together into an interface? what value can you see in this? by answering this question it gives everyone a better incite into why you want to do it and whether your expectations are reasonable, and what the contract might look like – TheGeneral Jun 08 '18 at 03:04
  • My thinking was that both Mercury and Diffbot perform the same fundamental service - they parse a web page and extract data I need (a cleansed page). They are two implementations. I wanted to inject the particular implementation I needed. This I thought was the basis of DI, to substitute different implementations at runtime in a loosely coupled way. – Robert Jun 08 '18 at 03:08
  • `For instance, Mercury uses a key and secret for authentication whereas Diffbot uses a token only.` Whatever they need should be provided to them by the IoC container (constructor injected). The caller won't need to know the difference. – mjwills Jun 08 '18 at 03:40
  • `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?` Without knowing the shape of the data returned it would be hard to say exactly. One option might be a Dictionary (with 3 entries or 20 entries). – mjwills Jun 08 '18 at 03:41

1 Answers1

1

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.

NitinSingh
  • 2,029
  • 1
  • 15
  • 33