I had a controller with 4 very similar methods, calling an API on a remote server to perform different actions on different types of users. What changed between these API calls are just the endpoint and some parameters.
Therefore, these 4 methods all called services with very similar code: they got a token from the server, set the parameters, return the API's response. Since more actions will be added later, I decided to use create a ServiceFactory using the Factory Method pattern and use the Template pattern on the services to avoid code duplication.
My problem is that in order for the factory to autowire the services, it needs to be coupled to them, I have to @Autowire
every implementation. Is there a better solution?
Here's the code I have so far:
Rest Controller
@RestController
public class ActionController {
@Autowired
private SsoService ssoService;
// this is the factory
@Autowired
private ServiceFactory factory;
@PostMapping("/action")
public MyResponse performAction(@RequestBody MyRequest request, HttpServletRequest req) {
// template code (error treatment not included)
request.setOperator(ssoService.getOperator(req));
request.setDate(LocalDateTime.now());
return serviceFactory.getService(request).do();
}
}
Service Factory
@Component
public class ServiceFactory {
@Autowired private ActivateUserService activateUserService;
@Autowired private Action2UserType2Service anotherService;
//etc
public MyService getService(request) {
if (Action.ACTIVATE.equals(request.getAction()) && UserType.USER.equals(request.getUserType()) {
return activateUserService;
}
// etc
return anotherService;
}
}
Service Base, implementing the MyService interface
public abstract class ServiceBase implements MyService {
@Autowired private ApiService apiService;
@Autowired private ActionRepository actionRepository;
@Value("${api.path}") private String path;
@Override
public MyResponse do(MyRequest request) {
String url = path + getEndpoint();
String token = apiService.getToken();
Map<String, String> params = getParams(request);
// adds the common params to the hashmap
HttpResult result = apiService.post(url, params);
if (result.getStatusCode() == 200) {
// saves the performed action
actionRepository.save(getAction());
}
// extracts the response from the HttpResult
return response;
}
}
Service Implementation (there are 4)
@Service
public class ActivateUserService extends ServiceBase {
@Value("${api.user.activate}")
private String endpoint;
@Override
public String getEndpoint() {
return endpoint;
}
@Override
public Map<String,String> getParams(MyRequest request) {
Map<String, String> params = new HashMap<>();
// adds custom params
return params;
}
@Override
public Action getAction() {
return new Action().type(ActionType.ACTIVATED).userType(UserType.USER);
}
}
UserService
implementation in theServiceFactory
,but from what you described and the codes you post , seems like that all 4MyService
implementations actually has the same logic , what are differences are just their configuration data. If yes , you can create a single implementation that accept a configuration parameter .... – EsquimaugetParams
method also changes. So having a single Service would also require a lot of ifs, or using a strategy pattern depending on which action is performed - which would be similar to the solution I've accepted. – Conferva