From 9f68cf5990f35b5abf14088b4d1ae693fbfb8e0d Mon Sep 17 00:00:00 2001 From: Loup Theron Date: Tue, 17 Sep 2024 17:55:19 +0200 Subject: [PATCH] Fix caching of control units API request --- .../repositories/ControlUnitRepository.kt | 4 +- .../control_units/GetAllControlUnits.kt | 5 +- .../cache/CaffeineConfiguration.kt | 2 +- .../monitorenv/APIControlUnitRepository.kt | 9 +-- .../monitorenv/APIMissionRepository.kt | 21 ++++++- .../APIControlUnitRepositoryITests.kt | 63 +++++++++++++++++++ 6 files changed, 87 insertions(+), 17 deletions(-) create mode 100644 backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/APIControlUnitRepositoryITests.kt diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/ControlUnitRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/ControlUnitRepository.kt index 372debbdca..5bcb3a917a 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/ControlUnitRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/ControlUnitRepository.kt @@ -1,9 +1,7 @@ package fr.gouv.cnsp.monitorfish.domain.repositories import fr.gouv.cnsp.monitorfish.domain.entities.mission.ControlUnit -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Deferred interface ControlUnitRepository { - fun findAll(scope: CoroutineScope): Deferred> + fun findAll(): List } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/control_units/GetAllControlUnits.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/control_units/GetAllControlUnits.kt index cf21d9e409..c36e849066 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/control_units/GetAllControlUnits.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/control_units/GetAllControlUnits.kt @@ -3,7 +3,6 @@ package fr.gouv.cnsp.monitorfish.domain.use_cases.control_units import fr.gouv.cnsp.monitorfish.config.UseCase import fr.gouv.cnsp.monitorfish.domain.entities.mission.ControlUnit import fr.gouv.cnsp.monitorfish.domain.repositories.ControlUnitRepository -import kotlinx.coroutines.runBlocking import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -14,8 +13,6 @@ class GetAllControlUnits( private val logger: Logger = LoggerFactory.getLogger(GetAllControlUnits::class.java) fun execute(): List { - return runBlocking { - return@runBlocking controlUnitsRepository.findAll(this).await() - } + return controlUnitsRepository.findAll() } } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/cache/CaffeineConfiguration.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/cache/CaffeineConfiguration.kt index 7a02beef1b..7543a3c463 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/cache/CaffeineConfiguration.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/cache/CaffeineConfiguration.kt @@ -94,7 +94,7 @@ class CaffeineConfiguration { val findBeaconCache = buildMinutesCache(findBeacon, ticker, 60) // Control Units - val controlUnitsCache = buildMinutesCache(controlUnits, ticker, oneWeek) + val controlUnitsCache = buildMinutesCache(controlUnits, ticker, oneDay) // FAO Areas val faoAreasCache = buildMinutesCache(faoAreas, ticker, oneWeek) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/APIControlUnitRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/APIControlUnitRepository.kt index 59c3e527c0..764de431ce 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/APIControlUnitRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/APIControlUnitRepository.kt @@ -6,9 +6,7 @@ import fr.gouv.cnsp.monitorfish.domain.entities.mission.ControlUnit import fr.gouv.cnsp.monitorfish.domain.repositories.ControlUnitRepository import io.ktor.client.call.* import io.ktor.client.request.* -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.async +import kotlinx.coroutines.runBlocking import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.cache.annotation.Cacheable @@ -22,8 +20,8 @@ class APIControlUnitRepository( private val logger: Logger = LoggerFactory.getLogger(APIControlUnitRepository::class.java) @Cacheable(value = ["control_units"]) - override fun findAll(scope: CoroutineScope): Deferred> { - return scope.async { + override fun findAll(): List = + runBlocking { val missionsUrl = "${monitorenvProperties.url}/api/v1/control_units" try { @@ -34,5 +32,4 @@ class APIControlUnitRepository( listOf() } } - } } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/APIMissionRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/APIMissionRepository.kt index 90d96e7a89..76c23b6e90 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/APIMissionRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/APIMissionRepository.kt @@ -1,5 +1,6 @@ package fr.gouv.cnsp.monitorfish.infrastructure.monitorenv +import com.github.benmanes.caffeine.cache.Caffeine import fr.gouv.cnsp.monitorfish.config.ApiClient import fr.gouv.cnsp.monitorfish.config.MonitorenvProperties import fr.gouv.cnsp.monitorfish.domain.entities.mission.ControlUnit @@ -15,10 +16,10 @@ import kotlinx.coroutines.async import kotlinx.coroutines.runBlocking import org.slf4j.Logger import org.slf4j.LoggerFactory -import org.springframework.cache.annotation.Cacheable import org.springframework.stereotype.Repository import java.time.ZonedDateTime import java.time.format.DateTimeFormatter +import java.util.concurrent.TimeUnit @Repository class APIMissionRepository( @@ -28,16 +29,30 @@ class APIMissionRepository( private val logger: Logger = LoggerFactory.getLogger(APIMissionRepository::class.java) private val zoneDateTimeFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.000X") - @Cacheable(value = ["mission_control_units"]) + private val cache = + Caffeine.newBuilder() + .maximumSize(500) + .expireAfterWrite(1, TimeUnit.DAYS) + .build>() + override fun findControlUnitsOfMission( scope: CoroutineScope, missionId: Int, ): Deferred> { + val cacheKey = "control_units_$missionId" + val cachedControlUnits = cache.getIfPresent(cacheKey) + + cachedControlUnits?.let { return@findControlUnitsOfMission scope.async { it } } + return scope.async { val missionsUrl = "${monitorenvProperties.url}/api/v1/missions/$missionId" try { - apiClient.httpClient.get(missionsUrl).body().controlUnits + val controlUnits = apiClient.httpClient.get(missionsUrl).body().controlUnits + + cache.put(cacheKey, controlUnits) + + return@async controlUnits } catch (e: Exception) { logger.error("Could not fetch control units for mission $missionId at $missionsUrl", e) diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/APIControlUnitRepositoryITests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/APIControlUnitRepositoryITests.kt new file mode 100644 index 0000000000..2daeaa1e74 --- /dev/null +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/APIControlUnitRepositoryITests.kt @@ -0,0 +1,63 @@ +package fr.gouv.cnsp.monitorfish.infrastructure.monitorenv + +import fr.gouv.cnsp.monitorfish.config.ApiClient +import fr.gouv.cnsp.monitorfish.config.MonitorenvProperties +import io.ktor.client.engine.mock.* +import io.ktor.http.* +import io.ktor.utils.io.* +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +class APIControlUnitRepositoryITests { + @Test + fun `findAll Should return control units`() { + // Given + val mockEngine = + MockEngine { _ -> + respond( + content = + ByteReadChannel( + """[ + { + "id": 10016, + "administration": "Douane", + "isArchived": false, + "name": "BSN Ste Maxime", + "resources": [], + "contact": null + }, + { + "id": 10017, + "administration": "Douane", + "isArchived": false, + "name": "DF 25 Libecciu", + "resources": [], + "contact": null + }, + { + "id": 10018, + "administration": "Douane", + "isArchived": false, + "name": "DF 61 Port-de-Bouc", + "resources": [], + "contact": null + } + ]""", + ), + status = HttpStatusCode.OK, + headers = headersOf(HttpHeaders.ContentType, "application/json"), + ) + } + val apiClient = ApiClient(mockEngine) + val monitorenvProperties = MonitorenvProperties() + monitorenvProperties.url = "http://test" + + // When + val controlUnits = + APIControlUnitRepository(monitorenvProperties, apiClient) + .findAll() + + assertThat(controlUnits).hasSize(3) + assertThat(controlUnits.first().id).isEqualTo(10016) + } +}