I'm writing a rest api client that needs to connect to some APIs (same API) on different endpoints, all serving the same data. For that, I need to dynamically set each calls url and auth header. Since I'm using spring as a framework, my plan was to use feign as the rest client.
Below is what I need to do in code
Feign Client:
@FeignClient(
name = "foo",
url = "http://placeholderThatWillNeverBeUsed.io",
fallbackFactory = ArticleFeignClient.ArticleClientFallbackFactory.class
)
public interface ArticleFeignClient {
@GetMapping(value = "articles/{id}", consumes = "application/json", produces = "application/json")
public ArticleResponse getArticles(URI baseUrl, @RequestHeader("Authorization") String token, @PathVariable Integer id);
@GetMapping(value = "articles", consumes = "application/json", produces = "application/json")
public MultiArticleResponse getArticles(URI baseUrl, @RequestHeader("Authorization") String token);
}
ArticleClient that enriches the parameter manually:
@Service
public class ArticleClient extends AbstractFeignClientSupport {
private final ArticleFeignClient articleFeignClient;
@Autowired
public ArticleClient(ArticleFeignClient articleFeignClient, AccessDataService accessDataService) {
super(accessDataService);
this.articleFeignClient = articleFeignClient;
}
public ArticleResponse getArticles(String connection, Integer id) {
var accessData = getAccessDataByConnection(connection);
return articleFeignClient.getArticles(URI.create(accessData.getEndpoint()), "Basic " + getAuthToken(accessData),id);
}
public MultiArticleResponse getArticles(String connection) {
var accessData = getAccessDataByConnection(connection);
return articleFeignClient.getArticles(URI.create(accessData.getEndpoint()), "Basic " + getAuthToken(accessData));
}
}
Client support that holds the enricher
public abstract class AbstractFeignClientSupport {
private final AccessDataService accessDataService;
public AbstractFeignClientSupport(AccessDataService accessDataService) {
this.accessDataService = accessDataService;
}
final public AccessData getAccessDataByConnection(@NotNull String connection) {
return accessDataService.findOneByConnection(connection).orElseThrow();
}
}
As you can see there will be many repetitions of
var accessData = getAccessDataByConnection(connection);
return clientToCall.methodToCall(URI.create(accessData.getEndpoint()), "Basic " + getAuthToken(accessData),id);
That simply adds the request's URI and Auth Header to the method call for the actual feign client.
I'm wondering if there's a better way and have been looking into using AOP or annotations that would intercept my method call, add the two parameters for every call in a given package (or annotated method), so that I'll only have to worry about it once and don't need to repeat that for 40 or so methods.
Is there? If so, how?
Aspects tend to be a pretty dirty business, typesafety-wise.
To manipulate, say, a List
passed to a method, you first need to extract it from the meta-information provided by the join point. This looks a bit like this:
@Pointcut("within(@com.your.company.SomeAnnotationType *)")
public void methodsYouWantToAdvise() {};
@Aspect
public class AddToList {
@Around("methodsYouWantToAdvise()")
public Object addToList(ProceedingJoinPoint thisJoinPoint) throws Throwable {
Object[] args = thisJoinPoint.getArgs();
// you know the first parameter is the list you want to adjust
List l = (List) args[0];
l.add("new Value");
thisJoinPoint.proceed(args);
}
This can definitely be done better, but it's pretty much the gist of how you can implement such an aspect.
Maybe check out this article to get at least the foundations going.