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.
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);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With