Skip to content

Hdpi 6650 refactor org service to cache responses#2178

Open
m014389b wants to merge 33 commits into
masterfrom
HDPI-6650_refactor_org_service_to_cache_responses
Open

Hdpi 6650 refactor org service to cache responses#2178
m014389b wants to merge 33 commits into
masterfrom
HDPI-6650_refactor_org_service_to_cache_responses

Conversation

@m014389b

@m014389b m014389b commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Jira link

See [HDPI-6550-](https://tools.hmcts.net/jira/browse/HDPI-6650)

Change description

Introduced a cache wrapper around organisationDetailsService.getOrganisationIdentifier.

Organisation details for a given user IdamId is stored in "cached_organisation_response"

Invocations to organisationService now will call the cached solution.

Cache service checks if a response exists for a idamId, if not present then calls rdProfessional.
If present but older than the configurable TTL then will be retrieved again otherwise the cached response is returned.

Testing done

Unit testing
Manual on preview, updating the "dbrefdata.organisation.id" in "rd-professional" for organisation with organisation_identifier='FHBG1F8' for user "possession-org-test-automation@test.com"

Security Vulnerability Assessment

CVE Suppression: Are there any CVEs present in the codebase (either newly introduced or pre-existing) that are being intentionally suppressed or ignored by this commit?

  • Yes
  • No

Checklist

  • commit messages are meaningful and follow good commit message guidelines
  • README and other documentation has been updated / added (if needed)
  • tests have been updated / new tests has been added (if needed)
  • Does this PR introduce a breaking change

tvr-solirius and others added 8 commits June 25, 2026 17:37
…rformance issues in the start method.

This found only the single database connection for the retrieval of the draft.
…aim-Baseline' into HDPI-6650_refactor_org_service_to_cache_responses
@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown

DB breaking change scan

Recommended reading: HMCTS zero-downtime database deployments

No potentially breaking database changes were detected in the changed Flyway migration .sql files.

This check only scans changed .sql files under src/main/resources/db/migration and flags high-risk DDL patterns. It does not replace a full migration review.

Run details: https://github.com/hmcts/pcs-api/actions/runs/30100693784?check_suite_focus=true

@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown

CCD diff summary

👉 Full report: https://github.com/hmcts/pcs-api/actions/runs/30100693688?check_suite_focus=true

No change

@m014389b
m014389b marked this pull request as ready for review July 16, 2026 12:29

@tvr-hmcts tvr-hmcts left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looking good. Some initial comments.

cachedOrganisationResponseRepository.findByIdamId(userIdam);

if (cachedResponse.isEmpty()) {
OrganisationDetailsResponse response = organisationDetailsService.getOrganisationDetails(userId);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

perhaps a log in here to say "cache miss" or something along those lines?

ex.getMessage(), ex);
// Return null instead of throwing to allow graceful degradation
log.error("Error retrieving organisation name and address from rd-professional API. Error: {}",
ex.getMessage(), ex);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please remove the ex.getMessage() Exception on the end is ok to keep though.

See ticket HDPI-7594

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done


} catch (FeignException ex) {
log.error("Feign error retrieving organisation details for userId: {}. Status: {}, Message: {}",
userId, ex.status(), ex.getMessage(), ex);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please remove the ex.getMessage() Exception on the end is ok to keep though.

See ticket HDPI-7594

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

throw new OrganisationDetailsException("Failed to retrieve organisation details", ex);
} catch (Exception ex) {
log.error("Unexpected error retrieving organisation details for userId: {}. Error: {}",
userId, ex.getMessage(), ex);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please remove the ex.getMessage() Exception on the end is ok to keep though.

See ticket HDPI-7594

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

CREATE TABLE public.cached_organisation_response
(
id UUID PRIMARY KEY,
idam_id UUID,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Think this should be NOT NULL

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

id UUID PRIMARY KEY,
idam_id UUID,
organisation_id varchar(64),
last_modified_date TIMESTAMP,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Perhaps NOT NULL here also as it is needed in the checks.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This timestamp 'may' need double checking as to the agreed approach as there is a splattering of different approaches in the code base - which I have gotten wrong before so pls take a look.

@tvr-hmcts tvr-hmcts left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just some minors and small alterations from the save methods.


CachedOrganisationResponseEntity newCachedResponse = mapResponseToEntity(userIdam, response);

cachedOrganisationResponseRepository.save(newCachedResponse);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

normally fine but safer to return from this line itself incase we do anything else lower down.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

if (isDataRequiringResync(existingCachedResponse.getLastModifiedDate())) {
OrganisationDetailsResponse response = getOrganisationDetails(userId);
updateFields(existingCachedResponse, response, userId);
cachedOrganisationResponseRepository.save(existingCachedResponse);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

safest to reasign existingCachedResponse = cachedOrgi...save(existingCachedResponse)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

* @param userId The user ID to get organisation details for
* @return OrganisationDetailsResponse containing organisation information
*/
private OrganisationDetailsResponse getOrganisationDetails(String userId) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

As this can return null then should it be Optional<...> then there can be a test on the Optional being empty as there is no test on the null return at the moment.

import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class CachingOrganisationDetailsServiceTest {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Think these should be assertThat(...).is... in this class

It is a bit missed at the moment.

assertNull(actual.address().getAddressLine1());

verify(cachedOrganisationResponseRepository).save(cachedEntity);
assertNull(cachedEntity.getAddressLine1());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Perhaps all values of the existingCachedResponse can have an assertThat(x.y).isNull() To be sure to be sure.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

CachedOrganisationResponseEntity existingCachedResponse = cachedResponse.get();

if (isDataRequiringResync(existingCachedResponse.getLastModifiedDate())) {
OrganisationDetailsResponse response = getOrganisationDetails(userId);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Minor : log in here to say it is resyncing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

import uk.gov.hmcts.reform.pcs.service.FeatureFlag;
import uk.gov.hmcts.reform.pcs.service.FeatureToggleService;
import uk.gov.hmcts.reform.pcs.testingsupport.model.TestingSupportAccessCode;
import uk.gov.hmcts.reform.pcs.ccd.entity.ClaimEntity;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

unused imports - wonder why the pipeline did not catch them

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hmm yes...

@tvr-hmcts tvr-hmcts left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Think it is good to go :)

if (response.isPresent()) {
updateFields(existingCachedResponse, response.get(), userId);
} else {
clearCachedFields(existingCachedResponse);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This can still leave the NameAndAddress being returned with nulls set.

caseData.setClaimantType(claimantTypeList);

contactPreferences.setOrganisationAddress(organisationService.getOrganisationAddressForCurrentUser());
contactPreferences.setOrganisationAddress(null != nameAndAddress ? nameAndAddress.address() : null);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

There is a slight difference here in that the AddressUK will not be null but the values within it now can be. Is that ok?

@m014389b m014389b added the enable_e2e_test Currently used to test the common e2e test call label Jul 21, 2026
id UUID PRIMARY KEY,
idam_id UUID NOT NULL,
organisation_id varchar(64),
last_modified_date TIMESTAMP NOT NULL,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Doesn't the date need to be with timezone ? In claim_issued_date and claim_submitted_date they're both timestamptz.

AuthTokenGenerator authTokenGenerator,
@Qualifier("prdAdminTokenProvider")
IdamTokenProvider prdAdminTokenProvider) {
this(cachedOrganisationResponseRepository, ttlInMinutes, LocalDateTime::now,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we be using the same approach as in ClaimService to have similar date handling ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants