Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Facade or Decorator

Context :

I have a REST Service let's say CustomerService which for now has one method getCustomer(id, country). Now requirement is that depending upon country I have to perform different business logic like access different database or some custom rules and then report that I received such a request.

Firstly to solve different implementations depending upon country I used Factory pattern as shown below :

Common interface for all country based implementations

public Interface CustomerServiceHandler{
   Cusomer getCustomer(String id, String country);
}

Then factory as

public class CustomerServiceHandlerFactory{
   public CustomerServiceHandler getHandler(String country){...};
}

Implementation Detail using Facade

Note this facade is called from REST class i.e.CustomerService

public CustomerServiceFacade{
   public Customer getCustomer(String id, String country){
        //use factory to get handler and then handler.getCustomer
        Customer customer = factory.getHandler(country).getCustomer(id,country);
        //report the request
        reportingService.report('fetch-customer',....);
        return customer;
   }
}

Going by the SRP(Single Responsibility Principle), this facade is not achieving a single objective. It is fetcing customer as well as reporting that such request was received. So I thought of decorator pattern as follow.

Implementation using Decorator Pattern:

//this is called from Rest layer
public ReportingCustomerHandler implements CustomerServiceHandler{
    //this delegate is basically the default implementation and has factory too                    
    private CustomerServiceHandler delegate;
    private ReportingService reporting;
    public Customer getCustomer(String id, String country){
           Customer customer = delegate.getCustomer(id, country);
           reporting.report(....);
           return customer;
    }
}


 //this is called from ReportingCustomerHandler           
 public DefaultCustomerServiceHandler implements CustomerServiceHandler{
     private CustomerServiceHandlerFactory factory;                    

     public Customer getCustomer(String id, String country){
         //get factory object else use itself, even default implementation is provided by factory
         CustomerServiceHandler handler = factory.getHandler(country);
         return handler.getCustomer(id,country);

     }
 }

Note: In second approach I am reusing the interface CustomerServiceHandler(shown in factory code) for Reporting and Default implementations also.

So what is the correct way, or what is the alternative to this if something more suitable is present.

Second part of the question

What if I have to maintain two different interfaces i.e. one CustomerServiceHandler to implement different countries' implementation and one to serve REST Layer. Then what can be the design or alternative. In this case i think facade would fit.

like image 845
Sikorski Avatar asked Nov 10 '22 18:11

Sikorski


1 Answers

So what is the correct way, or what is the alternative to this

You have a solid design here and great use of the factory pattern. What I offer are suggestions on this good work but I think there are many ways to enhance what you have.

I can see where the CustomerServiceFacade method getCustomer is breaking the SRP. It combines retrieving the Customer with the reporting aspect. I agree that it would be cleaner to move reporting out of that method.

Then your object would look like this:

public CustomerServiceFacade{
   public Customer getCustomer(String id, String country){
        return factory.getHandler(country).getCustomer(id,country);
   }
}

So where do we put the reporting?

You could move/manage the reporting through a separate interface. This would allow flexibility in implementing different reporting approaches and make testing easier (ie mock the reporting piece).

public interface ReportService {
   void report(Customer c, String id, String country);
}

How does the REST layer access reporting?

Option 1: REST accesses various Customer functions through multiple objects

The implementation of ReportService can be injected into the REST Controller with the CustomerServiceFacade.

Not sure what framework you are using for REST but here is what that might look like:

@GET
@Path("/customer/{Id}/{country}")
public Response getCustomer(@PathParam("Id") String id, @PathParam("country") String country){

    Response r = null;

    // injected implementation of CustomerServiceFacade
    Customer c = customerServiceFacade.getCustomer(id, country);
    if (c!=null){
        // injected implementation of ReportService
        reportService.report(c, id, country);
    }
    else {
        // handle errors ...
    }

    return r;
}

Option 2: REST accesses various Customer functions through one Facade/Service

You could allow your service facade layer to serve the function of providing a simplified interface to a larger set of objects that provide capabilties. This could be done by having multiple customer servicing methods that enable the REST layer to access various capabilities through one object but still have the benefit of having each method adhere more closely to the SRP.

Here we inject CustomerServiceFacade into the REST Controller and it calls the two methods 1) to get the customer and 2) to handle reporting. The facade uses the implementation of the ReportService interface from above.

public CustomerServiceFacade{
   public Customer getCustomer(String id, String country){
        // call the correct CustomerServiceHandler (based on country)
        return factory.getHandler(country).getCustomer(id,country);
   }

   public void report(Customer c, String id, String country){
        // call the reporting service 
        reportService.report(c, id, country);
   }
}

I think this is a reasonable use of the Facade pattern while still having SRP within the actual methods.

If the reporting implementation differs by country in the same way that the Customer does you could use another factory.

   public void report(Customer c, String id, String country){
        // call the correct reporting service (based on country)
        rptFactory.getInstance(country).report(c,id,country);
   }
like image 72
Mike Barlotta Avatar answered Nov 15 '22 12:11

Mike Barlotta