...
Code Block | ||
---|---|---|
| ||
<dependency> <groupId>se.sundsvall.dept44</groupId> <artifactId>dept44-starter-feign</artifactId> </dependency> |
Lägg även till annoteringen @EnableFeignClients
på main-klassen.
Alternativ 1 -
...
FeignMultiCustomizer
FeignMultiCustomizer
är en ersättare för den utfasade klassen FeignBuilder
, beskriven under alternativ 2. Den är en hjälpklass för att bygga en REST-klient vars metoder definieras i ett klienter med hjälp av interface som annoteras med Spring:s annoteringar för request-mappning, t.ex.:
Code Block | ||
---|---|---|
| ||
@FeignClient(name = "GithubClient", url = "${integration.github.url}", configuration = GithubConfiguration.class) public interface GithubClient { @GetMapping("users/{user}/repos"?tab=repositories", produces = APPLICATION_JSON_VALUE) List<Repo> getRepositoriesgetRepositoriesForUser(@PathVariable("user") String user); } |
FeignBuilder
kan sedan användas för att skapa upp en faktisk klient:Med hjälp av klassen FeignMultiCustomizer
genereras en böna som kedjar ihop flera FeignBuilderCustomizer
till en, som sedan används när klienten skapas. Värt att notera är att FeignBuilderCustomizer
kan blandas med bönor som plockas upp av feign (se alternativ 3) så länge de två alternativen inte krockar med varandra.
Code Block | ||
---|---|---|
| ||
var githubClient = new FeignBuilder@Import(FeignConfiguration.class) [1] public class GithubConfiguration { @Bean FeignBuilderCustomizer feignBuilderCustomizer() { return FeignMultiCustomizer.create() .withCustomizer(...) [2] .withBaseUrl("https://api.github.com/") [1] .withLogbookwithErrorDecoder(...) [3] .withRequestInterceptor(...) [4] .withRequestOptions(...) [5] .withRequestTimeoutsInSeconds(...) [6] .withRetryableOAuth2InterceptorForClientRegistration(...) [7] .composeCustomizersToOne(); [8] } } |
Interfacet kan sedan användas för att anropa integrationer i andra klasser:
Code Block |
---|
@Service public class RepositoryService { @Autowired private GithubClient githubClient; public List<Repo> getCustomer(String user) { return githubClient.getRepositoriesForUser(user); [2] .withOAuth2Client(...) } } |
[1]
Ifall FeignConfiguration
från Dept-44 importeras så läggs funktionalitet för loggning av request och response, requestId-hantering samt hantering av truststore för domän sundsvall.se (för hantering av anrop mot andra mikrotjänster i WSO2) till.[2]
Möjliggör ytterligare anpassning av Feign-klienten i de fall stöd saknas i ramverket.[3]
Anger den error decoder som ska användas för att översätta de fel som kastas av integrationen.[4]
Anger de interceptorer som ska användas för att förändra det utgående anropet före det skickas.[5]
Anger de anrops-alternativ som ska gälla för klienten avseende timeouts och follow-redirects[6]
En alternativ metod till [5]
där follow-redirects default sätts till true[7]
Anger att ett nytt anrop med förnyat access token ska ske ifall autentiseringsuppgifterna har gått ut. [8]
Skapar den FeignBuilderCustomizer
som sedan används av OpenFeign-ramverket när klienten skapas.
([4]
kan användas repetitivt för all lägga till multipla RequestInterceptor
).
Alternativ 2 - Annoteringar
Först och främst behövs ett klient-interface:
Code Block | ||
---|---|---|
| ||
@FeignClient( name = "myGithubClient", [31] url = .withBasicAuthentication(...)"https://api.github.com/", [42] configuration .withConnectTimeout(...) = GithubClientConfiguration.class [3] ) public interface GithubClient { @GetMapping("users/{user}/repos") [5] List<Repo> getRepositories(@PathVariable("user") String user); .withReadTimeout(...) } |
[1]
Anger namn på Feign-klienten[2]
Anger bas-URL för klienten. Kan utgöras av Spring EL-uttryck/properties, t.ex. från konfiguration (t.ex. "${some.url}"
)[3]
Anger en konfigurationsklass för klienten - en vanlig Spring-konfiguration där alla ingående bönor används för att konfigurera klienten.
Exempel på klient-konfiguration:
Code Block | ||
---|---|---|
| ||
@Import(FeignConfiguration.class) class GithubClientConfiguration { @Bean RequestInterceptor basicAuthInterceptor(String username, String password) { [61] .withFollowRedirects(...) return FeignHelper.basicAuthInterceptor(username, password); } @Bean [7] FeignBuilderCustomizer feignBuilderCustomizer() { .withClient(...) |8][2] return FeignHelper.withContractcustomizeRequestOptions(...) .withConnectTimeout(Duration.ofSeconds(60)) [9] .withEncoderwithReadTimeout(...)Duration.ofSeconds(5)) .build(); } @Bean [10] ErrorDecoder .withDecodererrorDecoder(...)) { [113] return new ProblemErrorDecoder("GithubClient"); } .withRetryer(...) } |
Ifall konfigurationen definierar någon böna av typen RequestInterceptor
, likt [1]
, kommer den automatiskt att knytas in i Feign-klienten. I exemplet används en utility-klass från ramverket - FeignHelper
- för att skapa en interceptor för Basic-autentisering. FeignHelper
har även stöd för att jacka in exempelvis en interceptor för OAuth2 och för att anpassa timeouts likt [2]
.
I exemplet ovan knyts även en generisk ErrorDecoder in i Feign-klienten [3]
. Detta är allt som krävs för att klienten skall kunna hantera error-response av Problem-typ (läs mer i befintlig RFC).
Utifrån ovan kan sedan klienten @Autowire
:as in som en vanlig Spring-komponent där den behövs:
Code Block | ||
---|---|---|
| ||
@Component public class MyClass { private GithubClient ghClient; public MyClass(GithubClient ghClient) { [12] .build(GithubClient.class); this.ghClient = ghClient; } public [13] List<Repo> getRepositories(String user) { ... var repos =return githubClientghClient.getRepositories("Sundsvallskommun"user); } |
Generisk ErrorDecoder
Ramverket erbjuder en generisk ErrorDecoder. D.
...
[1]
Anger bas-URL för REST-klienten som skapas.[2]
Om angiven sätts request- och response-loggning upp.[3]
Om angiven, sätts OAuth2-autentisering upp. Tar in en ClientRegistration
, t.ex.v.s. en mappning mellan “error-response” från tjänsten som anropas och exceptions i den egna tjänsten.
Genom att använda ProblemErrorDecoder
eller JsonPathErrorDecoder
behöver man inte implementera en egen. Har anropad tjänst implementerat felhantering enligt https://datatracker.ietf.org/doc/html/rfc7807 behöver man inte göra någonting mer än att skapa ProblemErrorDecoder
bönan i sin konfigurationsklass (se första exemplet nedan).
Om anropad tjänst däremot har uppfunnit en egen modell för fel, kan man behöva peka ut den data man vill mappa med hjälp av klassen JsonPathErrorDecoder
och JSON-path.
Konfiguration som krävs när anropad tjänst implementerat RFC-7807:
Code Block | ||
---|---|---|
| ||
@Import(FeignConfiguration.class) class GithubClientConfiguration { @Bean @Bean ClientRegistration ErrorDecoder clientRegistrationerrorDecoder() { return new ClientRegistration.withRegistrationIdProblemErrorDecoder("someId") .clientId("someClientId") GithubClient"); } } |
Konfiguration för fel som avviker från ovan beskriven standard. Genom använda JsonPathErrorDecoder
och ange sökvägen via JSON-path till den data vi vill mappa kan vi hantera vilka strukturer på felmeddelandet som helst. Se exempel nedan:
Error-response:
Code Block | ||
---|---|---|
| ||
{
"errorMessage": "This is an example error",
"extraInfo": {
"details": "This is example details",
"status": "500"
}
} |
Konfiguration:
Code Block | ||
---|---|---|
| ||
@Import(FeignConfiguration.class) class GithubClientConfiguration { @Bean ErrorDecoder .clientSecreterrorDecoder("someClientSecret")) { .tokenUri("https://somehost.com/token") .authorizationGrantType("client_credentials") .build(); new JsonPathErrorDecoder("GithubClient", } |
[4]
Om angiven (användarnamn och lösenord) sätts Basic-autentisering upp.[5]
Anger connect timeout. Default-värde är 10 sekunder.[6]
Anger read timeout. Default-värde är 60 sekunder.[7]
Anger om REST-klienten ska följa omdirigeringar. Default-värde är true
.[8]
-[12]
Kan, om så önskas, användas för att exempelvis använda en alternativ HTTP-klient eller för att konfigurera retry-policy och liknande. [13]
Skapar själva REST-klienten, av given typ.
...
TODO
TODO:
...
beskrivning
...
konfiguration
...
exempel på hur buildern används
...
new JsonPathSetup(
"$['errorMessage']", "$['extraInfo']['details']", "$['extraInfo']['status']"))
}
} |