Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ESWE-1181] Fix API clients, JPA auditing #25

Merged
merged 2 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ services:
- API_BASE_URL_JOBSBOARD=https://jobs-board-api-dev.hmpps.service.justice.gov.uk
- API_BASE_URL_MNJOBBOARD=https://testservices.sequation.net/sequation-job-api
- MN_JOBBOARD_API_TOKEN=<OVERRIDE_THIS>
- API_CLIENT_ID=<OVERRIDE_THIS>
- API_CLIENT_SECRET=<OVERRIDE_THIS>

hmpps-auth:
image: quay.io/hmpps/hmpps-auth:latest
networks:
- hmpps
container_name: hmpps-auth
container_name: integration-hmpps-auth
Comment on lines -27 to +29
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename container for avoiding name clash to hmpps-auth instance (standalone at 9090)

ports:
- "8090:8080"
healthcheck:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package uk.gov.justice.digital.hmpps.jobsboardintegrationapi.config

import org.springframework.context.annotation.Configuration
import org.springframework.data.jpa.repository.config.EnableJpaAuditing

@Configuration
@EnableJpaAuditing
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enable JPA auditing for audit fields (create and last updated timestamps)

class JpaConfig
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add client logging for troubleshooting, disabled by default.
enable this by setting api.client.logging.enabled to true, when needed

Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package uk.gov.justice.digital.hmpps.jobsboardintegrationapi.config

import io.netty.handler.logging.LogLevel
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.client.reactive.ReactorClientHttpConnector
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager
import org.springframework.web.reactive.function.client.WebClient
import reactor.netty.http.client.HttpClient
import reactor.netty.transport.logging.AdvancedByteBufFormat
import uk.gov.justice.hmpps.kotlin.auth.authorisedWebClient
import uk.gov.justice.hmpps.kotlin.auth.healthWebClient
import java.time.Duration
Expand All @@ -15,6 +19,7 @@ class WebClientConfiguration(
@Value("\${hmpps-auth.url}") val hmppsAuthBaseUri: String,
@Value("\${api.health-timeout:2s}") val healthTimeout: Duration,
@Value("\${api.timeout:20s}") val timeout: Duration,
@Value("\${api.client.logging.enabled:false}") val clientLogging: Boolean,
) {
// HMPPS Auth health ping is required if your service calls HMPPS Auth to get a token to call other services
// TODO: Remove the health ping if no call outs to other services are made
Expand All @@ -36,13 +41,30 @@ class WebClientConfiguration(
authorizedClientManager: OAuth2AuthorizedClientManager,
builder: WebClient.Builder,
@Value("\${api.base.url.jobsboard}") jobsboardApiBaseUri: String,
): WebClient = builder.authorisedWebClient(authorizedClientManager, registrationId = "hmpps-jobs-board-api", url = jobsboardApiBaseUri, timeout)
): WebClient = builder.apply {
if (clientLogging) it.clientConnector(clientConnectorWithLogging())
}.authorisedWebClient(
authorizedClientManager,
registrationId = "hmpps-jobs-board-api",
url = jobsboardApiBaseUri,
timeout,
)

@ConditionalOnIntegrationEnabled
@Bean
fun mnJobBoardWebClient(
builder: WebClient.Builder,
@Value("\${api.base.url.mnjobboard}") mnjobboardApiBaseUri: String,
@Value("\${mn.jobboard.api.token}") mnJobBoardToken: String,
): WebClient = builder.defaultHeader("Authorization", "Bearer $mnJobBoardToken").baseUrl(mnjobboardApiBaseUri).build()
): WebClient = builder.defaultHeader("Authorization", "Bearer $mnJobBoardToken").baseUrl(mnjobboardApiBaseUri).apply {
if (clientLogging) it.clientConnector(clientConnectorWithLogging())
}.build()

private fun clientConnectorWithLogging() = ReactorClientHttpConnector(httpClientWireTapLogging())

private fun httpClientWireTapLogging() = HttpClient.create().wiretap(
"reactor.netty.http.client.HttpClient",
LogLevel.DEBUG,
AdvancedByteBufFormat.TEXTUAL,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ class JobsBoardApiWebClient(
return jobsBoardWebClient
.get().uri("/employers/{id}", id).accept(APPLICATION_JSON).retrieve()
.bodyToMono(GetEmployerResponse::class.java)
.onErrorResume(WebClientResponseException.NotFound::class.java) {
log.debug("Employer not found. employerId={}", id)
.onErrorResume(WebClientResponseException.NotFound::class.java) { error ->
val errorResponse = if (error is WebClientResponseException) error.responseBodyAsString else null
log.warn("Employer not found. employerId={}; errorResponse={}", id, errorResponse)
Comment on lines +31 to +33
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error logging - write down the error response, if any

Mono.empty()
}.block()?.employer()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.http.MediaType.APPLICATION_JSON
import org.springframework.stereotype.Service
import org.springframework.web.reactive.function.client.WebClient
import org.springframework.web.reactive.function.client.WebClientResponseException
import reactor.core.publisher.Mono
import uk.gov.justice.digital.hmpps.jobsboardintegrationapi.config.ConditionalOnIntegrationEnabled
import uk.gov.justice.digital.hmpps.jobsboardintegrationapi.shared.domain.MNJobBoardApiClient
Expand All @@ -23,9 +24,15 @@ class MNJobBoardApiWebClient(

override fun createEmployer(request: CreatEmployerRequest): CreatEmployerResponse {
log.debug("Creating employer employerName={}", request.employerName)
return mnJobBoardWebClient.post().uri(EMPLOYERS_ENDPOINT).accept(APPLICATION_JSON).retrieve()
log.trace("Create employer request={}", request)
return mnJobBoardWebClient.post().uri(EMPLOYERS_ENDPOINT)
.accept(APPLICATION_JSON).body(Mono.just(request), request.javaClass)
.retrieve()
.bodyToMono(MNCreatEmployerResponse::class.java)
.onErrorResume { error -> Mono.error(Exception("Fail to create employer", error)) }.block()!!
.onErrorResume { error ->
val errorResponse = if (error is WebClientResponseException) error.responseBodyAsString else null
Mono.error(Exception("Fail to create employer! errorResponse=$errorResponse", error))
Comment on lines +32 to +34
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error logging - write down the error response, if any

}.block()!!
.responseObject
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"properties": [
{
"name": "api.base.url.jobsboard",
"type": "java.lang.String",
"description": "Base URL for Jobs Board APIs"
},
{
"name": "api.base.url.mnjobboard",
"type": "java.lang.String",
"description": "Base URL for MN Job Board APIs"
},
{
"name": "api.integration.enabled",
"type": "java.lang.String",
"description": "Feature flag to switch on/off JobsBoard-to-MN-Job-Board Integration functions"
},
{
"name": "api.client.logging.enabled",
"type": "java.lang.String",
"description": "Enabling client logging at API Web Client; disabled by default"
}
] }
8 changes: 7 additions & 1 deletion src/main/resources/application-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ api:
base.url:
jobsboard: "https://jobs-board-api-dev.hmpps.service.justice.gov.uk"
mnjobboard: "https://testservices.sequation.net/sequation-job-api"

integration:
enabled: false
client.logging:
enabled: false

spring:
datasource:
Expand All @@ -25,3 +26,8 @@ spring:
flyway:
user: 'job-board-intg'
password: 'job-board-intg'

logging.level:
uk.gov.justice.digital.hmpps.jobsboardintegrationapi:
shared.infrastructure: DEBUG
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set this to TRACE for troubleshooting

reactor.netty.http.client: INFO
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set this to DEBUG for troubleshooting