Reputation: 14122
I have one feignClient with this interface:
@FeignClient(
name = "kmr-api",
url = "${client.kmr.url}",
configuration = KmrClient.KmrClientConfiguration.class
)
public interface KmrClient {
@Configuration
class KmrClientConfiguration {
@Value("${client.kmr.access-token-uri}")
private String accessTokenUri;
@Value("${client.kmr.client-id}")
private String clientId;
// ...
@Bean
public Feign.Builder feignBuilder() {
return Feign.builder()
.retryer(Retryer.NEVER_RETRY)
.options(new Request.Options(connectTimeoutMillis, readTimeoutMillis));
}
private OAuth2ProtectedResourceDetails resource() {
ClientCredentialsResourceDetails resourceDetails = new ClientCredentialsResourceDetails();
resourceDetails.setAccessTokenUri(this.accessTokenUri);
resourceDetails.setClientId(this.clientId);
resourceDetails.setClientSecret(this.clientSecret);
// ...
return resourceDetails;
}
@Component
class KmrApiBrandRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header("brand", "renault");
}
}
}
@RequestMapping(value = "/vehicle/summary", method = RequestMethod.POST)
VehicleSummary getVehicleSummary(@RequestBody KmrVehicleId2 kmrVehicleId);
}
And associated properties such as
# Feign service
client.kmr.url=https://example/api/v1
client.kmr.access-token-uri=https://example/oauth2/access_token
client.kmr.client-id=myclientid
Now, I need several instances of this KmrClient, with the same exact interface and configuration, but with different properties. I'm trying to figure out something which could work out like this for example :
# Feign service
client.kmr[0].url=https://example/api/v1
client.kmr[0].access-token-uri=https://example/oauth2/access_token
client.kmr[0].client-id=myclientid
# Feign service
client.kmr[1].url=https://example/api/v1
client.kmr[1].access-token-uri=https://example/oauth2/access_token
client.kmr[1].client-id=myclientid
# there are 4 instances in total, one per region supported in app
From here I don't how I would load each property in client.kmr[1] and wire them into different configurations and clients.
How can I achieve this result ?
Upvotes: 5
Views: 3742
Reputation: 14122
This is the solution I finally found:
First, I created my own config classes for holding the properties values:
@ConfigurationProperties(prefix = "client.kdiag")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class KdiagClientsProperties {
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public static class KdiagClientProperties {
String url;
String accessTokenUri;
String clientId;
String clientSecret;
String scope;
int connectTimeoutMillis = 10000;
int readTimeoutMillis = 15000;
}
Map<String, KdiagClientProperties> regions;
}
The .properties
look like this:
client.kdiag.regions.EU.url=https://example.io/diag/api/v1
client.kdiag.regions.EU.access-token-uri=https://example.org/auth/oauth2/stuff/access_token
client.kdiag.regions.EU.client-id=redacted
client.kdiag.regions.EU.client-secret=redacted
client.kdiag.regions.EU.scope=scope1,scope2
client.kdiag.regions.RU.url=https://example.io/diag/api/v1
client.kdiag.regions.RU.access-token-uri=https://example.org/auth/oauth2/stuff/access_token
client.kdiag.regions.RU.client-id=redacted
client.kdiag.regions.RU.client-secret=redacted
client.kdiag.regions.RU.scope=scope1,scope2
Then I created a class called KmrClients for accessing all client instances which reads these properties:
/**
* Allow requesting each Kdiag region.
*
* Get the kdiag client with @{@link KdiagClients#clientByRegion(KdisRegion)}
*/
@Order(SecurityProperties.IGNORED_ORDER)
@Configuration
@EnableConfigurationProperties(value = {KdiagClientsProperties.class})
@Import(FeignClientsConfiguration.class) // We need these to reinject decoder, encoder, contract in constructor
@Slf4j
public class KdiagClients {
private Map<String, KdiagClient> clients;
/**
* builds the OAuth2 configuration for a client
*/
private OAuth2ProtectedResourceDetails resource(KdiagClientsProperties.KdiagClientProperties properties) {
ClientCredentialsResourceDetails resourceDetails = new ClientCredentialsResourceDetails();
resourceDetails.setAccessTokenUri(properties.getAccessTokenUri());
resourceDetails.setClientId(properties.getClientId());
resourceDetails.setClientSecret(properties.getClientSecret());
resourceDetails.setScope(Arrays.asList(properties.getScope().split(",")));
resourceDetails.setGrantType("client_credentials");
return resourceDetails;
}
@Autowired
public KdiagClients(
KdiagClientsProperties properties,
Decoder decoder,
Encoder encoder,
Contract contract
) {
Assert.notEmpty(properties.regions, "No KDiag region clients defined in properties (ex: kdiag.client.regions.EU etc) ");
log.info("found kdiag clients for regions:" + properties.regions.keySet().toString());
this.clients = properties.getRegions().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, clientPropertiesEntry -> {
return Feign.builder()
.retryer(Retryer.NEVER_RETRY)
.options(new Request.Options(clientPropertiesEntry.getValue().getConnectTimeoutMillis(), clientPropertiesEntry.getValue().getReadTimeoutMillis()))
.requestInterceptor(new OAuth2FeignRequestInterceptor(new DefaultOAuth2ClientContext(), resource(clientPropertiesEntry.getValue())))
.encoder(encoder)
.decoder(decoder)
.contract(contract)
.target(KdiagClient.class, clientPropertiesEntry.getValue().getUrl());
}));
}
public KdiagClient clientByRegion(KdisRegion region) {
if (this.clients.containsKey(region.name())) {
return this.clients.get(region.name());
} else {
throw new NoKdiagClientForRegionException(region);
}
}
}
In the end you can use it by injecting KdiagClients:
this.kdiagClients.clientByRegion(KdisRegion.EU)
This solution is a bit specific to a domain but one can adapt it to suit its need. So in the end the solution was:
Upvotes: 2