For a project, we have a Controller/Service/DAO architecture. We implement calls to different providers' APIs so we ended up with some boilerplate code like this in every controller class:
enum {
PARTNER_A, PARTNER_B, PARTNER_C
}
public class MyController {
#Resource PartnerASearchService partnerASearchService;
#Resource PartnerBSearchService partnerBSearchService;
#Resource PartnerCSearchService partnerCSearchService;
public search(InputForm form) {
switch(form.getPartnerName()) {
case PARTNER_A: partnerASearchService.search();
break;
case PARTNER_B: partnerBSearchService.search();
break;
case PARTNER_C: partnerCSearchService.search();
break;
}
}
public otherMethod(InputForm form) {
switch(form.getProvider()) {
case PARTNER_A: partnerAOtherService.otherMethod();
break;
case PARTNER_B: partnerBOtherService.otherMethod();
break;
case PARTNER_C: partnerCOtherService.otherMethod();
break;
}
}
}
Which design pattern can I use to get rid of this switch in every controller? I would prefer the code to be something like the below:
public class MyController {
#Resource ServiceStrategy serviceStrategy;
public search(InputForm form){
serviceStrategy.search(form.getPartnerName())
// or
serviceStrategy.invoke(SEARCH, form.getPartnerName())
}
public otherMethod(InputForm form){
serviceStrategy.other(form.getPartnerName())
// or
serviceStrategy.invoke(OTHER, form.getPartnerName())
}
}
letting the serviceStrategy decide which service implementation to be called, and thus having the partner's switch in a single place.
I've used the term "strategy" because I've been told this design pattern could make it, but I'm not sure of the best way to use it or if there is a better approach to solve this problem.
EDIT: I've updated the question as the term provider is misleading. What I have in the input form is the name of the partner for which we do the request. I want a pattern that decides which is the correct implementation (which one of the several services) to use based on the partner's name in the form
Generally, the form shouldn't need any knowledge of what "provider" is going to handle it. Instead, the providers should be able to explain which kinds of inputs they can handle.
I recommend using a form of Chain of Responsibility (incorporating the refactoring Replace Conditional with Polymorphism) that looks something like this (Groovy for simplicity):
interface ProviderService {
boolean accepts(InputForm form)
void invoke(String foo, InputForm form)
void other(InputForm form)
}
Each implementation of ProviderService implements accepts to indicate whether it can handle a particular form, and your controller stores a List<ProviderService> services instead of individual references. Then, when you need to process a form, you can use:
ProviderService service = services.find { it.accepts(form) }
// error handling if no service found
service.other(form)
See the Spring conversion service for a comprehensive example of this pattern that's used in a major framework.
First remove the duplication of the provider lookup code by extracting it to an own method.
public class MyController {
#Resource
ProviderService searchServiceProvider1;
#Resource
ProviderService searchServiceProvider2;
#Resource
ProviderService searchServiceProvider3;
public void search(InputForm form) {
String provider = form.getProvider();
ProviderService providerService = lookupServiceProvider(provider);
providerService.search();
}
public void other(InputForm form) {
String provider = form.getProvider();
ProviderService providerService = lookupServiceProvider(provider);
providerService.other();
}
private ProviderService lookupServiceProvider(String provider) {
ProviderService targetService;
switch (provider) {
case PROVIDER_1:
targetService = searchServiceProvider1;
break;
case PROVIDER_2:
targetService = searchServiceProvider2;
break;
case PROVIDER_3:
targetService = searchServiceProvider3;
break;
default:
throw new IllegalStateException("No Such Service Provider");
}
return targetService;
}
}
At least you can improve the lookupServiceProvider method and use a map to avoid the switch.
private Map<String, ProviderService> providerLookupTable;
private Map<String, ProviderService> getProviderLookupTable(){
if(providerLookupTable == null){
providerLookupTable = new HashMap<String, ProviderService>();
providerLookupTable.put(PROVIDER_1, searchServiceProvider1);
providerLookupTable.put(PROVIDER_2, searchServiceProvider2);
providerLookupTable.put(PROVIDER_3, searchServiceProvider3);
}
return providerLookupTable;
}
private ProviderService lookupServiceProvider(String provider) {
Map<String, ProviderService> providerLookupTable = getProviderLookupTable();
ProviderService targetService = providerLookupTable.get(provider);
if(targetService == null){
throw new IllegalStateException("No Such Service Provider");
}
return targetService;
}
Finally you will recognize that you can introduce a ProviderServiceLocator, move the lookup logic to this class and let MyController use the ProvicerServiceLocator.
A detailed explanation and example code about service provider interfaces and service provider lookup with standard java can be found in my blog A plug-in architecture implemented with java.
Indeed, you can use the Strategy pattern here. It will look like something like this.
Here, you have to get the designated ServiceProvider from InputForm.
You can have StrategyMaker class something like this.
Public class StrategyMaker{
public SeriveProvider getProviderStrategy(InputForm inputForm){
return inputForm.getProvider();
}
}
And inside controllers you can do something like this.
public class MyController{
StrategyMaker strategyMaker;
public search(InputForm form){
strategyMaker.getProviderStategy(form).search();
}
}
This will be an ideal solution, if you know list of all the provider strategies forehand. Strategy pattern unable to keep Open-Close-Princple when the list is continuously growing.
And one other thing is when you refer a pattern, always try to get the big picture. Don't rigidly look into the implementation example any source provides. Always remember that it's an implementation, not the implementation.
One possible solution when the Partners are not frequently changing:
class ServiceFactory {
#Resource PartnerService partnerASearchService;
#Resource PartnerService partnerBSearchService;
#Resource PartnerService partnerCSearchService;
public static PartnerService getService(String partnerName){
switch(partnerName) {
case PARTNER_A: return partnerASearchService;
case PARTNER_B: return partnerBSearchService;
case PARTNER_C: return partnerCSearchService;
}
}
public class MyController {
#Resource ServiceFactory serviceFactory;
public search(InputForm form) {
serviceFactory.getService(form.getProvider()).search()
}
public otherMethod(InputForm form) {
serviceFactory.getService(form.getProvider()).otherMethod()
}
}
Mixing ideas from different answers I came up to
ServiceProvider.java A superclass for all the service providers. Contains a map of the different services for each partner
public abstract class ServiceProvider implements IServiceProvider {
private final Map<ServiceType, IService> serviceMap;
protected ServiceProvider() {
this.serviceMap = new HashMap<>(0);
}
protected void addService(ServiceType serviceType, IService service) {
serviceMap.put(serviceType, service);
}
public IService getService(ServiceType servicetype, PartnerType partnerType) throws ServiceNotImplementedException {
try {
return this.serviceMap.get(serviceType);
} catch (Exception e) {
throw new ServiceNotImplementedException("Not implemented");
}
}
}
ServiceProviderPartnerA.java there is a service provider for each partner, which are injected with the actual service classes for the different methods.
#Service("serviceProviderPartnerA")
public class ServiceProviderPartnerA extends ServiceProvider {
#Resource(name = "partnerASearchService")
private ISearchService partnerASearchService;
#Resource(name = "partnerABookingService")
private IBookingService partnerABookingService;
#PostConstruct
public void init() {
super.addService(ServiceType.SEARCH, partnerASearchService);
super.addService(ServiceType.BOOKING, partnerABookingService);
}
}
ServiceStrategy.java Injected with the different partners' service providers, it implements the only switch needed in the code and returns the correct service for the correct partner to be used in the controller
#Service("serviceStrategy")
public class ServiceStrategy implements IServiceStrategy {
#Resource(name = "serviceProviderPartnerA")
IServiceProvider serviceProviderPartnerA;
#Resource(name = "serviceProviderPartnerB")
IServiceProvider serviceProviderPartnerB;
#Resource(name = "serviceProviderPartnerC")
IServiceProvider serviceProviderPartnerC;
public IService getService(ServiceType serviceType, PartnerType partnerType) throws PartnerNotImplementedException {
switch (partnerType) {
case PARTNER_A:
return serviceProviderPartnerA.getService(serviceType, partnerType);
case PARTNER_B:
return serviceProviderPartnerB.getService(serviceType, partnerType);
case PARTNER_C:
return serviceProviderPartnerC.getService(serviceType, partnerType);
default:
throw new PartnerNotImplementedException();
}
}
}
SearchController.java finally, in my controllers, I just need to inject the serviceStrategy class and use it to recover the correct service.
#Resource(name = "serviceStrategy")
IServiceStrategy serviceStrategy;
#RequestMapping(value = "/search", method = RequestMethod.GET, produces = "text/html")
#ResponseBody
public String search(#RequestParam(value = "partner", required = true) String partnerType, String... params) {
ISearchService service = (ISearchService) serviceStrategy.getService(ServiceType.SEARCH, partnerType);
return service.search(params);
}
So, switch off! Hope this helps someone
Related
I have 2 APIs
1. localhost:8080/myservice/foo/1/0/updatesStatus
2. localhost:8080/myservice/bar/1/0/updatesStatus
I am not allowed to have different controllers for each API. So both the APIs are pointing to the same controller method where I have if-else check, but the code looks very bad in that, is there any better way to handle this.
#PostMapping(value = UPDATE_STATUS_API_PATH)
public Response updateStatus(#PathVariable("group") String group , #RequestBody UpdateStatusRequest updateStatusRequest, HttpServletRequest request) {
try {
if(APIUrl.FOO_GROUP.equals(group)){
//code spefic to foo
}
else{
//code spefic to bar
}
//common code
}
The same conditional checks also have to be performed on the service layer as well.
Is there any way I can avoid this conditional checks without having separate controller methods.
I can think of this.
Create an interface for service.
public interface UpdateService {
void updateStatus(UpdateStatusRequest updateStatusRequest);
}
Then you create different implementations.
public class FooUpdateService implements UpdateService {
void updateStatus(UpdateStatusRequest updateStatusRequest) {
// foo specific logic
}
}
public class BarUpdateService implements UpdateService {
void updateStatus(UpdateStatusRequest updateStatusRequest) {
// Bar specific logic
}
}
Create a UpdateServiceFactory
public class UpdateServiceFactory {
#Autowired
private UpdateService fooUpdateService;
#Autowired
private UpdateService fooUpdateService;
public UpdateService getUpdateService(String group) {
// Move the if-else logic here
if(APIUrl.FOO_GROUP.equals(group)){
return fooUpdateService;
}
else{
//code spefic to bar
return barUpdateService;
}
}
}
Controller:
#PostMapping(value = UPDATE_STATUS_API_PATH)
public Response updateStatus(#PathVariable("group") String group , #RequestBody UpdateStatusRequest updateStatusRequest, HttpServletRequest request) {
updateServiceFactory.getUpdateService(group).updateStatus(updateStatusRequest);
//common code
}
I want implement strategy design pattern in spring boot application. I create BeanPostProcessor for construct strategy resolver:
#Component
public class HandlerInAnnotationBeanPostProcessor implements BeanPostProcessor {
private final UnpHandlersResolver unpHandlersResolver;
public HandlerInAnnotationBeanPostProcessor(UnpHandlersResolver unpHandlersResolver) {
this.unpHandlersResolver = unpHandlersResolver;
}
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
Annotation[] annotations = bean.getClass().getAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof HandlerIn) {
if (bean.getClass() != UnpHandler.class)
throw new RuntimeException("Not UnpHandler bean annotated by HandlerIn");
SmevMessageType[] type = ((HandlerIn) annotation).type();
for (SmevMessageType smevMessageType : type) {
unpHandlersResolver.setHandler(smevMessageType, (UnpHandler) bean);
}
}
}
return bean;
}
}
And I create resolver:
#Slf4j
#Component
public class UnpHandlersResolverImpl implements UnpHandlersResolver {
private Map<SmevMessageType, UnpHandler> map = new HashMap<>();
#Override
public void setHandler(SmevMessageType messageType, UnpHandler unpHandler) {
map.put(messageType, unpHandler);
}
#Override
public UnpHandler getUnpHandler(SmevMessageType type) {
UnpHandler sendRequestHandler = map.get(type);
if (sendRequestHandler == null)
throw new IllegalArgumentException("Invalid SendRequestHandler type: " + type);
return sendRequestHandler;
}
}
My BeanPostProcessor scan all beans with annotation HandlerIn and add to resolver's mup. I think it's wrong to do that:
unpHandlersResolver.setHandler(smevMessageType, (UnpHandler) bean);
But I not understand how can I add find beans to resolver. Before this implementation I faind beans in #Postconstruct method of resolver like:
context.getBeansWithAnnotation(HandlerIn.class);
But in this solution I have context in resolver and I think is bad.
Tell me how to properly implement what I want? In short, I want to have a set of classes that implement different behaviors. And the class that controls them. Give the class a parameter so that he chooses the right strategy and gives it to me. Like this:
Handler handler = handlersResolver.getHandler(messageType);
Result result = handler.somthing(param);
I'm going to try to make a simple example.
Interface Greeting {
void sayHello();
String getSupportedLanguage();
}
Then you have X number of implementations and you can loop through them in your "resolver"'s constructor to build the map. (I've seen this called a Proxy or a Decorator in code though, i.e. GreetingProxy or GreetingDecorator)
#Service
public GreetingResolver {
private Map<String, Greeting> languageToGreetingMap = new HashMap<>();
#Autowired
public GreetingResolver(List<Greeting> greetings) {
for (Greeting greeting : greetings) {
languageToGreetingMap.put(greeting.getSupportedLanguage(), greeting);
}
}
public void sayGreetingForLanguage(String language) {
languageToGreetingMap.get(language).sayHello();
}
}
This is a basic example of how one can do the strategy pattern in Spring. Every interface implementation of "Greeting" only knows about itself and what it can support. We then autowire all implementations in a list and loop through to create the map once and then during runtime only the relevant entry from the map in retrieved and used.
Note: this was typed "free hand" directly in the web page so please forgive any typos in the code.
I am creating a project which will respond to collect multiple bean object, save it to the database and return the status of the transaction. There can be multiple objects that can be sent from the client. For each object, they are having separate database thus separate controller.
So I planned to create a framework that can accept multiple objects from multiple controllers and send only one centralized object. But I am not sure how to use a centralized object as a return type in the controller(currently I returned them as Object). Below is my code:
Controller:
#RestController
#RequestMapping("/stat/player")
public class PlayerController {
#Autowired
private StatService<PlayerValue> statPlayer;
#RequestMapping("/number/{number}")
public Object findByNumber(#PathVariable String number) { // Here returning Object seem odd
return statPlayer.findByNumber(number);
}
}
Service:
#Service
#Transactional(isolation = Isolation.READ_COMMITTED)
public class PlayerServiceImpl implements StatService<PlayerValue> {
#Autowired
private PlayerRepository repository;
#Override
public PlayerValue findByNumber(String number) {
Optional<PlayerEntity> numberValue = repository.findByNumber(number);
return numberValue.map(PlayerEntity::toValue).orElse(null);
}
}
In service I returned the PlayerValue object but I want to wrap this object into a centralized bean ResponseValue. I created an aspect for that
#Aspect
#Component
public class Converter {
private static final Logger LOG = LoggerFactory.getLogger(Converter.class);
#Pointcut("within(#org.springframework.web.bind.annotation.RestController *)")
public void restControllerClassMethod() {}
private <T> ResponseValue<T> convert(List<T> results) {
String message = results.isEmpty() ? "No result found" : ResponseValueStatus.OK.toString();
return new ResponseValue<>(ResponseValueStatus.OK, message, results);
}
#Around("restControllerClassMethod()")
#SuppressWarnings("unchecked")
public <T> ResponseValue<T> convert(ProceedingJoinPoint joinPoint) {
ResponseValue value;
try {
Object findObject = joinPoint.proceed();
List<Object> objects = toList(findObject);
value = convert(objects);
} catch (NullPointerException e) {
throw new StatException(String.format("Exception thrown from %s from %s method with parameter %s", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(), joinPoint.getArgs()[0].toString()));
//this exception will go in a controller advice and create a response value with this message
} catch (Throwable e) {
LOG.error("Exception occurred while converting the object", e);
throw new StatException(String.format("Exception thrown from %s from %s method with parameter %s with exception message %s", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(), joinPoint.getArgs()[0].toString(), e.getMessage()));
}
return value;
}
private List<Object> toList(Object findObject) {
List<Object> objects = new ArrayList<>();
if (findObject instanceof List) {
((List) findObject).forEach(item -> objects.add(findObject));
} else {
objects.add(findObject);
}
return objects;
}
}
To sum up, There could be multiple entity similar to PlayerValue. I need a way to return the result in a centralized bean. Above process work, BUT for this I have to give return type as Object in Controller. Does anybody has an idea how can I use return type as List or T in controller. Also I know it can be done by implementing a ValueConverter interface, but this conversion is straightforward. So it would be nice if any other developer don't have to implement the ValueConverter everytime he want to add a different controller.
Also feel free to review the implementation and let me know if anyone has some alternative idea or some comments.
Note: I reduce a lot of code in the question so that it can be easier to understandable without understanding the actual requirement context. Please do let me know if anyone need more info.
After some research I came across to a better design solution for the framework (but of course with flaws) to achieve conversion to a centralized bean for multiple domain objects is to use a marker interface.
Marker interface can provide a centralized type for all the bean. The main rule need to be followed by the client is to implement that marker interface. So the basic solution is
Marker interface:
public interface StateResponseValue<T> {
}
Implement the interface in all the bean.
public class PlayerValue implements StateResponseValue<PlayerValue> {
}
public class ResponseValue<T> implements StateResponseValue<T> {
//fields and their getter and setter
}
Change the return type in service and controller.
public interface StatService<T> {
StateResponseValue<T> findByNumber(String number);
}
Change the return type in controller
#RestController
#RequestMapping("/stat/player")
public class PlayerController {
#Autowired
private StatService<PlayerValue> statPlayer;
#RequestMapping("/number/{number}")
public StateResponseValue<T> findByNumber(#PathVariable String number) { // Here returning Object seem odd
return statPlayer.findByNumber(number);
}
}
Note: The main drawback I feel is that whenever we want to access the field client need to explicitly cast the object to ResponseValue which is still pretty ugly.
What if you create an AbstractStatController which is generic ?
Generic interface StatService:
public interface StatService<T> {
T findByNumber(String number);
}
Generic abstract class AbstractStatController:
public abstract class AbstractStatController<T> {
abstract StatService<T> getStatService();
#RequestMapping("/number/{number}")
public T findByNumber(#PathVariable String number) {
return getStatService().findByNumber(number);
}
}
Concrete class PlayerController:
#RestController
#RequestMapping("/stat/player")
public class PlayerController extends AbstractStatController<Player> {
private final PlayerService playerService;
public PlayerController(PlayerService playerService) {
this.playerService = playerService;
}
#Override
StatService<Player> getStatService() {
return playerService;
}
}
I have different class types, and depending on some conditions, I want to delegate to the appropriate service that can handle those class types.
Example:
I have several classes as follows.
class Student;
class Prof;
...
For each class there is a service, implementing:
interface IPersonService {
void run();
}
And I have a mode that is found by some conditions:
enum PersonType {
STUDENT, PROF;
}
When I delegate:
#Autowired
private StudentService studentService;
#Autowired
private ProfService profService;
//#param mode assume known
public void delegate(PersonType mode) {
//assume there are several of those switch statements in my business code
switch (mode) {
case STUDENT: studentService.run(); break;
case PROF: profService.run(); break;
default: break;
}
}
Problem: When introducing additional classes, I have to both modify the PersonType and add an additional enum (which is no problem), but I also have to extend any switch statement and add calls to additional delegation services. Also I have to explicit autowire those services to the switch delegator.
Question: how could I optimize this code, to just implementing new Services for any additional class, and not having to touch any of the switch statements?
Add a method to IPersonService so that the implementation of the method can tell the program what type of persons it handles:
interface IPersonService {
PersonType supportedPersonType();
void run();
}
In the service that does the delegation, inject a List<IPersonService>, which Spring will fill with all the implementations of IPersonService that it can find. Then implement the delegate method to look through the list to find the first IPersonService that can handle the specific type.
#Autowired
private List<IPersonService> personServices;
public void delegate(PersonType mode) {
for (IPersonService personService : personServices) {
if (personService.supportedPersonType().equals(mode)) {
personService.run();
break;
}
}
}
This way, you can add new implementations of IPersonService without having to change the service that does the delegation.
To avoid having to go through the loop each time delegate is called, you could build a Map beforehand so that the right IPersonService can be looked up quickly:
class DelegatingService {
#Autowired
private List<IPersonService> personServices;
private Map<PersonType, IPersonService> personServiceMap;
#PostConstruct
public void init() {
personServiceMap = new HashMap<>();
for (IPersonService personService : personServices) {
personServiceMap.put(personService.supportedPersonType(), personService);
}
}
public void delegate(PersonType mode) {
personServiceMap.get(mode).run();
}
}
(Error handling omitted for simplicity).
In my application, we solved similar problem, by putting services into a map. Consider Map<PersonType,IPersonService> serviceMap defined as bean and injected into your class.
Then delegate method simple do
public void delegate(PersonType mode) {
IPersonService service = serviceMap.get(mode);
if (service!=null){
service.run();
}else{
//do something if service is null
}
}
You can store the service bean name (or class type) in the enum, and fetch the beans from the application context using getBean by name (or by class type respectively).
Also, all the services will need to implement a interface which has the run method.
interface ModeService {
void run();
}
enum PersonType {
STUDENT("studentService"), PROF("profService");
private String serviceBean;
public PersonType(String serviceBean) {
this.serviceBean = serviceBean);
}
public String getServiceBean() {
return serviceBean;
}
}
in delegate then the following can be used. ((ModeService)applicationContext.getBean(mode.getServiceBean()).run()
This way only the enum needs to be updated with the service type that is to be used, and no change to delegate method is required.
I created one factory to decide what best implementation should be returned, based in some conditional check.
// Factory
#Component
public class StoreServiceFactory {
#Autowired
private List<StoreService> storeServices;
public StoreService getService(){
if(isActiveSale){
return storeServices.get("PublicStoreService")
}
return storeServices.get("PrivateStoreService")
}
}
//Service Implementations
#Service
#Qualifier("PublicStoreService")
public class PublicStoreService implements StoreService {
public getStoreBalanceScore(){
Do Stuff....
}
}
#Service
#Qualifier("PrivateStoreService")
public class PrivateStoreService implements StoreService {
public getStoreBalanceScore(){
Do Stuff....
}
}
// Controller
#Autowired
StoreServiceFactory storeServiceFactory;
#Override
public StoreData getStoreBalance(String storeId) {
StoreService storeService = storeServiceFactory.getService();
return simulationService.simulate(sellerId, simulation);
}
Is this approach good? If yes, how can i get my service from an elegant way?
I would like to use only annotations, without configurations.
You should use a map instead of a List and pass a string parameter to the getService method.
public class StoreServiceFactory {
#Autowired
private Map<String,StoreService> storeServices = new HashMap<>();
public StoreService getService(String serviceName){
if(some condition...){
// want to return specific implementation on storeServices map, but using #Qualifier os something else
storeServices.get(serviceName)
}
}
}
You can prepopulate the map with supported implementations. You can then get an appropriate service instance as follows :
// Controller
#Autowired
StoreServiceFactory storeServiceFactory;
#Override
public StoreData getStoreBalance(String storeId) {
StoreService storeService = storeServiceFactory.getService("private");//not sure but you could pass storeId as a parameter to getService
return simulationService.simulate(sellerId, simulation);
}
If you don't like using Strings, you can define an enum for the supported implementations and use that as the key for your map.
You don't need to create a list or map on your code. You can retrieve it directly from Spring context using GenericBeanFactoryAccessor. This has various method to retrieve a specific bean like based on name, annotation etc. You can take a look at javadoc here. This avoids unnecessary complexity.
http://docs.spring.io/spring-framework/docs/2.5.6/api/org/springframework/beans/factory/generic/GenericBeanFactoryAccessor.html