diff --git a/apps/user-ms/src/main/java/it/pagopa/selfcare/user/service/UserRegistryService.java b/apps/user-ms/src/main/java/it/pagopa/selfcare/user/service/UserRegistryService.java index 749c048c..811aec8b 100644 --- a/apps/user-ms/src/main/java/it/pagopa/selfcare/user/service/UserRegistryService.java +++ b/apps/user-ms/src/main/java/it/pagopa/selfcare/user/service/UserRegistryService.java @@ -3,11 +3,15 @@ import io.smallrye.mutiny.Uni; import it.pagopa.selfcare.user.model.UpdateUserRequest; import it.pagopa.selfcare.user.model.notification.UserNotificationToSend; -import org.openapi.quarkus.user_registry_json.model.MutableUserFieldsDto; +import org.openapi.quarkus.user_registry_json.model.*; import java.util.List; public interface UserRegistryService { Uni> updateUserRegistryAndSendNotificationToQueue(UpdateUserRequest updateUserRequest, String userId, String institutionId); + Uni findByIdUsingGET( String fl, String id); + Uni saveUsingPATCH(SaveUserDto saveUserDto); + Uni searchUsingPOST(String fl, UserSearchDto userSearchDto); + Uni updateUsingPATCH(String id, MutableUserFieldsDto mutableUserFieldsDto ); } diff --git a/apps/user-ms/src/main/java/it/pagopa/selfcare/user/service/UserRegistryServiceImpl.java b/apps/user-ms/src/main/java/it/pagopa/selfcare/user/service/UserRegistryServiceImpl.java index cf1137d6..431abc9e 100644 --- a/apps/user-ms/src/main/java/it/pagopa/selfcare/user/service/UserRegistryServiceImpl.java +++ b/apps/user-ms/src/main/java/it/pagopa/selfcare/user/service/UserRegistryServiceImpl.java @@ -11,15 +11,18 @@ import it.pagopa.selfcare.user.util.UserUtils; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import jakarta.ws.rs.core.Response; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.rest.client.inject.RestClient; import org.gradle.internal.impldep.org.apache.commons.lang.StringUtils; +import java.time.Duration; import java.util.*; import org.openapi.quarkus.user_registry_json.api.UserApi; -import org.openapi.quarkus.user_registry_json.model.UserResource; +import org.openapi.quarkus.user_registry_json.model.*; import static it.pagopa.selfcare.user.constant.CollectionUtil.MAIL_ID_PREFIX; @@ -35,11 +38,44 @@ public class UserRegistryServiceImpl implements UserRegistryService { private final UserNotificationService userNotificationService; private final UserMapper userMapper; + @ConfigProperty(name = "user-ms.retry.min-backoff") + Integer retryMinBackOff; + + @ConfigProperty(name = "user-ms.retry.max-backoff") + Integer retryMaxBackOff; + + @ConfigProperty(name = "user-ms.retry") + Integer maxRetry; + @RestClient @Inject private UserApi userRegistryApi; + @Override + public Uni findByIdUsingGET(String fl, String id) { + return userRegistryApi.findByIdUsingGET(fl, id) + .onFailure().retry().withBackOff(Duration.ofSeconds(retryMinBackOff), Duration.ofHours(retryMaxBackOff)).atMost(maxRetry); + } + + @Override + public Uni saveUsingPATCH(SaveUserDto saveUserDto) { + return userRegistryApi.saveUsingPATCH(saveUserDto) + .onFailure().retry().withBackOff(Duration.ofSeconds(retryMinBackOff), Duration.ofHours(retryMaxBackOff)).atMost(maxRetry); + } + + @Override + public Uni searchUsingPOST(String fl, UserSearchDto userSearchDto) { + return userRegistryApi.searchUsingPOST(fl, userSearchDto) + .onFailure().retry().withBackOff(Duration.ofSeconds(retryMinBackOff), Duration.ofHours(retryMaxBackOff)).atMost(maxRetry); + } + + @Override + public Uni updateUsingPATCH(String id, MutableUserFieldsDto mutableUserFieldsDto) { + return userRegistryApi.updateUsingPATCH(id, mutableUserFieldsDto) + .onFailure().retry().withBackOff(Duration.ofSeconds(retryMinBackOff), Duration.ofHours(retryMaxBackOff)).atMost(maxRetry); + } + @Override public Uni> updateUserRegistryAndSendNotificationToQueue(UpdateUserRequest updateUserRequest, String userId, String institutionId) { @@ -91,7 +127,7 @@ private Uni findMailUuidAndUpdateUserRegistry(UserResource userResource, .map(Map.Entry::getKey); } - return userRegistryApi.updateUsingPATCH(userResource.getId().toString(), + return updateUsingPATCH(userResource.getId().toString(), userMapper.toMutableUserFieldsDto(userDto, userResource, mailAlreadyPresent.isPresent() ? null : idMail)) .replaceWith(mailAlreadyPresent.orElse(idMail)); } diff --git a/apps/user-ms/src/main/java/it/pagopa/selfcare/user/service/UserServiceImpl.java b/apps/user-ms/src/main/java/it/pagopa/selfcare/user/service/UserServiceImpl.java index 4551531f..cb68813a 100644 --- a/apps/user-ms/src/main/java/it/pagopa/selfcare/user/service/UserServiceImpl.java +++ b/apps/user-ms/src/main/java/it/pagopa/selfcare/user/service/UserServiceImpl.java @@ -59,9 +59,7 @@ @RequiredArgsConstructor public class UserServiceImpl implements UserService { - @RestClient - @Inject - private UserApi userRegistryApi; + private UserRegistryService userRegistryService; private final UserMapper userMapper; private final UserInstitutionMapper userInstitutionMapper; @@ -109,7 +107,7 @@ public Uni> getUsersEmails(String institutionId, String productId) var productFilters = OnboardedProductFilter.builder().productId(productId).status(ACTIVE).build().constructMap(); Multi userInstitutions = userInstitutionService.findAllWithFilter(userUtils.retrieveMapForFilter(userInstitutionFilters, productFilters)); return userInstitutions - .onItem().transformToUni(userInstitution -> userRegistryApi.findByIdUsingGET(WORK_CONTACTS, userInstitution.getUserId()) + .onItem().transformToUni(userInstitution -> userRegistryService.findByIdUsingGET(WORK_CONTACTS, userInstitution.getUserId()) .map(userResource -> Objects.nonNull(userResource.getWorkContacts()) && userResource.getWorkContacts().containsKey(userInstitution.getUserMailUuid()) ? userResource.getWorkContacts().get(userInstitution.getUserMailUuid()) : null)) .merge() @@ -123,7 +121,7 @@ public Uni> getUsersEmails(String institutionId, String productId) public Multi getUserProductsByInstitution(String institutionId) { Multi userInstitutions = UserInstitution.find(UserInstitution.Fields.institutionId.name(), institutionId).stream(); return userInstitutions.onItem() - .transformToUni(userInstitution -> userRegistryApi.findByIdUsingGET(USERS_WORKS_FIELD_LIST, userInstitution.getUserId()) + .transformToUni(userInstitution -> userRegistryService.findByIdUsingGET(USERS_WORKS_FIELD_LIST, userInstitution.getUserId()) .map(userResource -> UserProductResponse.builder() .id(userResource.getId().toString()) .name(userResource.getName().getValue()) @@ -145,7 +143,7 @@ public Uni retrievePerson(String userId, String productId, String log.error(String.format(USER_NOT_FOUND_ERROR.getMessage(), userId)); return new ResourceNotFoundException(String.format(USER_NOT_FOUND_ERROR.getMessage(), userId), USER_NOT_FOUND_ERROR.getCode()); }) - .onItem().transformToUni(userInstitution -> userRegistryApi.findByIdUsingGET(USERS_WORKS_FIELD_LIST, userInstitution.getUserId())) + .onItem().transformToUni(userInstitution -> userRegistryService.findByIdUsingGET(USERS_WORKS_FIELD_LIST, userInstitution.getUserId())) .onFailure(UserUtils::checkIfNotFoundException).transform(t -> new ResourceNotFoundException(String.format(USER_NOT_FOUND_ERROR.getMessage(), userId), USER_NOT_FOUND_ERROR.getCode())); } @@ -220,14 +218,14 @@ public Uni getUserById(String userId, String institutionId, log.error(String.format(USER_NOT_FOUND_ERROR.getMessage(), userId)); return new UserInstitution(); }) - .onItem().transformToUni(userInstitution -> userRegistryApi.findByIdUsingGET(fields, userId) + .onItem().transformToUni(userInstitution -> userRegistryService.findByIdUsingGET(fields, userId) .map(userResource -> userMapper.toUserDetailResponse(userResource, Optional.ofNullable(institutionId).map(ignored -> userInstitution.getUserMailUuid()).orElse(null)))) .onFailure(UserUtils::checkIfNotFoundException).transform(t -> new ResourceNotFoundException(String.format(USER_NOT_FOUND_ERROR.getMessage(), userId), USER_NOT_FOUND_ERROR.getCode())); } @Override public Uni searchUserByFiscalCode(String fiscalCode, String institutionId) { - Uni userResourceUni = userRegistryApi.searchUsingPOST(USERS_FIELD_LIST_WITHOUT_FISCAL_CODE, new UserSearchDto(fiscalCode)) + Uni userResourceUni = userRegistryService.searchUsingPOST(USERS_FIELD_LIST_WITHOUT_FISCAL_CODE, new UserSearchDto(fiscalCode)) .onFailure(UserUtils::checkIfNotFoundException).transform(t -> new ResourceNotFoundException("User not found", USER_NOT_FOUND_ERROR.getCode())); return userResourceUni @@ -294,7 +292,7 @@ private Uni retrieveUser } private Uni retrieveUserFromUserRegistryAndAddToPrepareNotificationData(PrepareNotificationData.PrepareNotificationDataBuilder prepareNotificationDataBuilder, String userId) { - return userRegistryApi.findByIdUsingGET(USERS_WORKS_FIELD_LIST, userId) + return userRegistryService.findByIdUsingGET(USERS_WORKS_FIELD_LIST, userId) .onItem().transform(prepareNotificationDataBuilder::userResource); } @@ -307,7 +305,7 @@ public Uni> findPaginatedUserNotificationToSend(Int queryParameter = OnboardedProductFilter.builder().status(VALID_USER_PRODUCT_STATES_FOR_NOTIFICATION).build().constructMap(); } return userInstitutionService.paginatedFindAllWithFilter(queryParameter, page, size) - .onItem().transformToUniAndMerge(userInstitution -> userRegistryApi + .onItem().transformToUniAndMerge(userInstitution -> userRegistryService .findByIdUsingGET(USERS_FIELD_LIST_WITHOUT_FISCAL_CODE, userInstitution.getUserId()) .map(userResource -> userUtils.buildUsersNotificationResponse(userInstitution, userResource, productId))) .collect().in(ArrayList::new, List::addAll); @@ -331,7 +329,7 @@ public Uni retrieveBindings(String institutionId, String userId, Strin /** * The createOrUpdateUserByFiscalCode method is a method that either creates a new user or updates an existing one based on the provided CreateUserDto object. - * The method starts by calling the searchUsingPOST method on the userRegistryApi object, this is an asynchronous operation that searches for a user in the user registry. + * The method starts by calling the searchUsingPOST method on the userRegistryService object, this is an asynchronous operation that searches for a user in the user registry. * If the search operation fails with a UserNotFoundException, it recovers by returning a Uni that fails with a ResourceNotFoundException. * This is a way of transforming one type of exception into another. * If the search operation is successful, it call method to Update user on userRegistry and UserInstitution collection. @@ -342,7 +340,7 @@ public Uni retrieveBindings(String institutionId, String userId, Strin */ @Override public Uni createOrUpdateUserByFiscalCode(CreateUserDto userDto, LoggedUser loggedUser) { - return userRegistryApi.searchUsingPOST(USERS_WORKS_FIELD_LIST, new UserSearchDto(userDto.getUser().getFiscalCode())) + return userRegistryService.searchUsingPOST(USERS_WORKS_FIELD_LIST, new UserSearchDto(userDto.getUser().getFiscalCode())) .onFailure(UserUtils::isUserNotFoundExceptionOnUserRegistry).recoverWithUni(throwable -> Uni.createFrom().failure(new ResourceNotFoundException(throwable.getMessage()))) .onItem().transformToUni(userResource -> updateUserOnUserRegistryAndUserInstitutionByFiscalCode(userResource, userDto)) .onFailure(ResourceNotFoundException.class).recoverWithUni(throwable -> createUserOnUserRegistryAndUserInstitution(userDto)) @@ -374,7 +372,7 @@ private Uni updateUserOnUserRegistryAndUserInstitutionB workContactToSave.put(mailUuid, workContact); userResource.setWorkContacts(workContactToSave); - return userRegistryApi.updateUsingPATCH(userResource.getId().toString(), userMapper.toMutableUserFieldsDto(userResource)) + return userRegistryService.updateUsingPATCH(userResource.getId().toString(), userMapper.toMutableUserFieldsDto(userResource)) .onFailure().invoke(exception -> log.error("Error during update user on userRegistry: {} ", exception.getMessage(), exception)) .onItem().invoke(response -> log.info("User with id {} updated on userRegistry", userResource.getId())) .onItem().transformToUni(response -> userInstitutionService.findByUserIdAndInstitutionId(userResource.getId().toString(), userDto.getInstitutionId())) @@ -402,14 +400,14 @@ private Uni createUserOnUserRegistryAndUserInstitution( String mailUuid = randomMailId.get(); Map workContacts = new HashMap<>(); workContacts.put(mailUuid, UserUtils.buildWorkContact(userDto.getUser().getInstitutionEmail())); - return userRegistryApi.saveUsingPATCH(userMapper.toSaveUserDto(userDto.getUser(), workContacts)) + return userRegistryService.saveUsingPATCH(userMapper.toSaveUserDto(userDto.getUser(), workContacts)) .onFailure().invoke(exception -> log.error("Error during create user on userRegistry: {} ", exception.getMessage(), exception)) .onItem().invoke(userResource -> log.info("User created with id {}", userResource.getId())) .onItem().transform(userResource -> userResource.getId().toString()) .onItem().transform(userId -> updateOrCreateUserInstitution(userDto, mailUuid, null, userId)) .onItem().invoke(userInstitution -> log.info("start persist userInstititon: {}", userInstitution)) .onItem().transformToUni(userInstitutionService::persistOrUpdate) - .onItem().transformToUni(userInstitution -> userRegistryApi.findByIdUsingGET(USERS_WORKS_FIELD_LIST, userInstitution.getUserId()) + .onItem().transformToUni(userInstitution -> userRegistryService.findByIdUsingGET(USERS_WORKS_FIELD_LIST, userInstitution.getUserId()) .map(resource -> PrepareNotificationData.builder().userResource(resource).userInstitution(userInstitution).queueEvent(QueueEvent.ADD))) .onItem().transformToUni(prepareNotificationDataBuilder -> retrieveProductAndAddToPrepareNotificationData(prepareNotificationDataBuilder, userDto.getProduct().getProductId())) .map(PrepareNotificationData.PrepareNotificationDataBuilder::build) @@ -419,14 +417,14 @@ private Uni createUserOnUserRegistryAndUserInstitution( /** * The createOrUpdateUserByUserId method is a method that either add to existingUser a new user Role. - * The method starts by calling the findByIdUsingGET method on the userRegistryApi object, + * The method starts by calling the findByIdUsingGET method on the userRegistryService object, * If the search operation fails with a UserNotFoundException, it recovers by returning a Uni that fails with a ResourceNotFoundException. * If the search operation is successful, it call method to Update user on UserInstitution collection. * Finally, if any operation fails, it logs an error message and returns a Uni that emits a failure. */ @Override public Uni createOrUpdateUserByUserId(AddUserRoleDto userDto, String userId, LoggedUser loggedUser) { - return userRegistryApi.findByIdUsingGET(USERS_FIELD_LIST_WITHOUT_FISCAL_CODE, userId) + return userRegistryService.findByIdUsingGET(USERS_FIELD_LIST_WITHOUT_FISCAL_CODE, userId) .onFailure(UserUtils::isUserNotFoundExceptionOnUserRegistry).recoverWithUni(throwable -> Uni.createFrom().failure(new ResourceNotFoundException(throwable.getMessage()))) .onItem().transformToUni(userResource -> updateUserInstitutionByUserId(userResource, userDto, loggedUser)) .onFailure().invoke(exception -> log.error("Error during retrieve user from userRegistry: {} ", exception.getMessage(), exception)); @@ -517,7 +515,7 @@ public Multi retrieveUsersData(String institutionId, String pe .onItem().invoke(userInstitution -> applyFiltersToRemoveProducts(userInstitution, states, products, roles, productRoles)) .onItem().invoke(userInstitution -> log.info("userInstitution found: {}", userInstitution)) .onItem().transformToUniAndMerge(userInstitution -> - userRegistryApi.findByIdUsingGET(USERS_WORKS_FIELD_LIST, userInstitution.getUserId()) + userRegistryService.findByIdUsingGET(USERS_WORKS_FIELD_LIST, userInstitution.getUserId()) .map(userResource -> userMapper.toUserDataResponse(userInstitution, userResource))); } diff --git a/apps/user-ms/src/main/resources/application.properties b/apps/user-ms/src/main/resources/application.properties index 9628bbc6..a0fc2ab2 100644 --- a/apps/user-ms/src/main/resources/application.properties +++ b/apps/user-ms/src/main/resources/application.properties @@ -68,3 +68,7 @@ user-ms.aws.ses.secret-key=${AWS_SES_SECRET_ACCESS_KEY:secret-key-example} user-ms.aws.ses.region=${AWS_SES_REGION:eu-south-1} quarkus.smallrye-openapi.store-schema-directory=src/main/docs + +user-ms.retry.min-backoff=${USER-MS-RETRY-MIN-BACKOFF:10} +user-ms.retry.max-backoff=${USER-MS-RETRY-MAX-BACKOFF:12} +user-ms.retry=${USER-MS-RETRY:3} diff --git a/apps/user-ms/src/test/java/it/pagopa/selfcare/user/service/UserServiceTest.java b/apps/user-ms/src/test/java/it/pagopa/selfcare/user/service/UserServiceTest.java index 42b5b8bc..d48d0c10 100644 --- a/apps/user-ms/src/test/java/it/pagopa/selfcare/user/service/UserServiceTest.java +++ b/apps/user-ms/src/test/java/it/pagopa/selfcare/user/service/UserServiceTest.java @@ -74,9 +74,7 @@ class UserServiceTest { @InjectMock private UserInfoService userInfoService; - @RestClient - @InjectMock - private UserApi userRegistryApi; + private UserRegistryService userRegistryApi; @InjectMock private UserMapper userMapper; diff --git a/infra/container_apps/user-ms/env/dev-pnpg/terraform.tfvars b/infra/container_apps/user-ms/env/dev-pnpg/terraform.tfvars index 95c585a3..e9b9ff75 100644 --- a/infra/container_apps/user-ms/env/dev-pnpg/terraform.tfvars +++ b/infra/container_apps/user-ms/env/dev-pnpg/terraform.tfvars @@ -54,7 +54,19 @@ app_settings = [ { name = "EVENTHUB-SC-USERS-SELFCARE-WO-KEY-LC" value = "string" - } + }, + { + name = "USER-MS-RETRY-MIN-BACKOFF" + value = 10 + }, + { + name = "USER-MS-RETRY-MAX-BACKOFF" + value = 12 + }, + { + name = "USER-MS-RETRY" + value = 3 + } ] secrets_names = { diff --git a/infra/container_apps/user-ms/env/dev/terraform.tfvars b/infra/container_apps/user-ms/env/dev/terraform.tfvars index cc7d5607..768189b3 100644 --- a/infra/container_apps/user-ms/env/dev/terraform.tfvars +++ b/infra/container_apps/user-ms/env/dev/terraform.tfvars @@ -53,6 +53,18 @@ app_settings = [ { name = "USER_MS_EVENTHUB_USERS_ENABLED" value = true + }, + { + name = "USER-MS-RETRY-MIN-BACKOFF" + value = 10 + }, + { + name = "USER-MS-RETRY-MAX-BACKOFF" + value = 12 + }, + { + name = "USER-MS-RETRY" + value = 3 } ] diff --git a/infra/container_apps/user-ms/env/prod-pnpg/terraform.tfvars b/infra/container_apps/user-ms/env/prod-pnpg/terraform.tfvars index b13f8188..6e3b83a4 100644 --- a/infra/container_apps/user-ms/env/prod-pnpg/terraform.tfvars +++ b/infra/container_apps/user-ms/env/prod-pnpg/terraform.tfvars @@ -54,6 +54,18 @@ app_settings = [ { name = "USER_REGISTRY_URL" value = "https://api.pdv.pagopa.it/user-registry/v1" + }, + { + name = "USER-MS-RETRY-MIN-BACKOFF" + value = 10 + }, + { + name = "USER-MS-RETRY-MAX-BACKOFF" + value = 12 + }, + { + name = "USER-MS-RETRY" + value = 3 } ] diff --git a/infra/container_apps/user-ms/env/prod/terraform.tfvars b/infra/container_apps/user-ms/env/prod/terraform.tfvars index a1b0a271..153ac629 100644 --- a/infra/container_apps/user-ms/env/prod/terraform.tfvars +++ b/infra/container_apps/user-ms/env/prod/terraform.tfvars @@ -57,6 +57,18 @@ app_settings = [ { name = "EVENT_HUB_BASE_PATH" value = "https://selc-p-eventhub-ns.servicebus.windows.net/sc-users" + }, + { + name = "USER-MS-RETRY-MIN-BACKOFF" + value = 10 + }, + { + name = "USER-MS-RETRY-MAX-BACKOFF" + value = 12 + }, + { + name = "USER-MS-RETRY" + value = 3 } ] diff --git a/infra/container_apps/user-ms/env/uat-pnpg/terraform.tfvars b/infra/container_apps/user-ms/env/uat-pnpg/terraform.tfvars index f70cf34c..e1daf480 100644 --- a/infra/container_apps/user-ms/env/uat-pnpg/terraform.tfvars +++ b/infra/container_apps/user-ms/env/uat-pnpg/terraform.tfvars @@ -47,6 +47,18 @@ app_settings = [ { name = "STORAGE_CONTAINER_PRODUCT" value = "selc-u-product" + }, + { + name = "USER-MS-RETRY-MIN-BACKOFF" + value = 10 + }, + { + name = "USER-MS-RETRY-MAX-BACKOFF" + value = 12 + }, + { + name = "USER-MS-RETRY" + value = 3 } ] diff --git a/infra/container_apps/user-ms/env/uat/terraform.tfvars b/infra/container_apps/user-ms/env/uat/terraform.tfvars index 3cf62074..f847b82a 100644 --- a/infra/container_apps/user-ms/env/uat/terraform.tfvars +++ b/infra/container_apps/user-ms/env/uat/terraform.tfvars @@ -46,6 +46,18 @@ app_settings = [ { name = "STORAGE_CONTAINER_PRODUCT" value = "selc-u-product" + }, + { + name = "USER-MS-RETRY-MIN-BACKOFF" + value = 10 + }, + { + name = "USER-MS-RETRY-MAX-BACKOFF" + value = 12 + }, + { + name = "USER-MS-RETRY" + value = 3 } ]