Service Locator Factory Pattern
Introduction
Service Locator Factory pattern helps you to locate services at runtime based on certain criteria. In this article, I will be mainly focusing on spring provided service locator pattern & its implementation. There are many ways to implement service locator pattern but spring service locator pattern helps you to avoid boiler plate code & implement it efficiently & quickly.
From Martin Fowler’s article talking about locator pattern :
“The basic idea behind a service locator is to have an object that knows how to get hold of all of the services that an application might need. So a service locator for this application would have a method that returns a ‘service’ when one is needed.”
Problem Statement
Let’s take a simple example of a payment system where you have different payment processors like Visa/Amex/MasterCard. These payment processors have different methods to pay. We need to build a controller that calls the appropriate method based on type provided by client. Let’s try to understand & build a very basic code structure around it. Before that, we will take a look at the components involved in locator pattern.
Components
- Client: Consumer that requires the service at runtime.
- Service Locator: Service locator is responsible for returning the service on-demand to the client. It abstracts the lookup or creation of the service.
- Initial Context: It creates, registers and caches the service. This is the starting point of the lookup and creation.
- Service Factory: The service factory provides lifecycle management for the service with support to create, lookup, or remove a service.
- Service: Concrete implementation of the service desired by the client.
Implementation
- Let’s start with building the generic interface for our payment processor as below :
public interface PaymentProcessor {
public void pay();
}
2. Concrete implementation of interface PaymentProcessor with Visa. In the below class, we have specified the name as “Visa” for the component.
@Slf4j
@Component("Visa")
public class VisaPaymentProcessor implements PaymentProcessor{
@Override
public void pay() {
log.info("Paying through Visa Network!");
}
}
3. Concrete implementation of interface PaymentProcessor with Amex. In the below class, we have specified the name as “Amex” for the component.
@Slf4j
@Component("Amex")
public class AmexPaymentProcessor implements PaymentProcessor{
@Override
public void pay() {
log.info("Paying through Amex Network!");
}
}
4. Now let’s write service locator interface. It has a method which takes a type as input param & returns a PaymentProcessor type as below :
public interface PaymentRegistry {
public PaymentProcessor findProcessor(String type);
}
5. Using the above service locator interface, I will now add it to the spring factory bean config to use this as service locator interface.
@Configuration
public class ServiceLocatorConfig {
@Bean("paymentRegistry")
public FactoryBean<?> getBean() {
ServiceLocatorFactoryBean bean = new ServiceLocatorFactoryBean();
bean.setServiceLocatorInterface(PaymentRegistry.class);
return bean;
}
}
6. Sample controller to test our changes
@RestController
@RequestMapping("/payment-service")
@Slf4j
public class TestController {
@Autowired
private PaymentRegistry paymentRegistry;
@GetMapping("/v1/pay")
public void payViaProcessor(@RequestParam String type) {
log.info("Paying thru type : {} ", type );
paymentRegistry.findProcessor(type).pay();
}
}
Output
http://localhost:8080/payment-service/v1/pay?type=Visa
http://localhost:8080/payment-service/v1/pay?type=Amex
Take Aways
- The pattern helps you to remove tight coupling making it complaint to Open Closed Principle.
- Removal of boiler plate code to build caches & factory of services since spring takes care of it for you :)
Happy Learning! ;)