9. Configure, Document, and Review

Your service works. On this last page, you finish it the way a real service is finished: you describe it in its CapabilityStatement, review its configuration, document it, fill the remaining gaps in its tests, and present it to the onboarding team.

Describe Your FHIR Server

Look at the software element of the CapabilityStatement at /metadata: your server is still called "HAPI FHIR Server". Add the following to src/main/resources/application.properties, after va.mobile.fhir.server.path:

# Name and description of the FHIR server, published in the CapabilityStatement (software.name, implementation.description)
va.mobile.fhir.server.server-name=FHIR Starter Service
va.mobile.fhir.server.implementation-description=FHIR Starter Project: patient demographics from MVI and encounters from Oracle Health

After skaffold dev redeploys, /metadata describes your server:

"software":{"name":"FHIR Starter Service","version":"8.12.1"},"implementation":{"description":"FHIR Starter Project: patient demographics from MVI and encounters from Oracle Health","url":"http://localhost:8080/fhirstarter/v1/fhir"}

The Swagger UI, at http://localhost:8080/fhirstarter/v1/fhir/swagger-ui/, shows the server name in its heading.

The framework has more properties for the FHIR server, such as page sizes and pretty-printed JSON; see FHIR Server Properties in the appendix.

When you debug, turn on request logging in application-dev.properties. The following logs every request to your server, and a summary of every request to Oracle Health and its response:

va.mobile.fhir.server.logging.enabled=true
va.mobile.fhir.client.registration.cerner.logging.enabled=true
va.mobile.fhir.client.registration.cerner.logging.request-summary=true
va.mobile.fhir.client.registration.cerner.logging.response-summary=true

There are also request-headers, request-body, response-headers, and response-body properties. Never turn them on outside your workstation: headers contain tokens, and bodies contain patient data.

Document the Service

Every VA Mobile service documents what it does, what it depends on, and how to deploy it. Update the documents that the archetype generated.

README

Replace the placeholders in README.md:

  • The Summary: describe what the service provides, and to whom. For example:

    The fhir-starter-service is a FHIR R4 server for staff and veteran users. It provides Patient resources with
    demographics and identifiers from the Mobile MVI Service, and Encounter resources from Oracle Health (Cerner). Staff
    users may access any patient; veteran users may access only their own records.
  • The Project Structure table: add the classes you created, and update the descriptions of the providers, which are no longer samples. Then remove the [//]: # (TODO: expand summary) line at the end of the table. Despite its wording, it belongs to the Project Structure section:

    | Location | Description |
    |:---|:---|
    | `resource/PatientProvider.java` | Patient read, search by ICN, and the `$identifiers` operation. Spring beans that implement `IResourceProvider` are registered with the FHIR server automatically |
    | `resource/EncounterProvider.java` | Encounter search by patient and status |
    | `resource/PatientAccess.java` | ICN validation and the veteran access check, shared by the resource providers |
    | `resource/SystemOperationsProvider.java` | System level operation (`$validate-icn`), a plain provider registered in `FhirConfig` |
    | `client/MviClient.java` | Calls the Mobile MVI Service with the `mvi` REST client |
    | `client/CernerClient.java` | Calls Oracle Health with the `cerner` FHIR client |
    | `client/CernerAuthInterceptor.java` | Adds an Oracle Health OAuth 2.0 access token to each request of the `cerner` FHIR client |
    | `mapper/PatientMapper.java` | Maps MVI data to FHIR `Patient` and `Parameters` resources |
    | `interceptor/CacheControlInterceptor.java` | Adds `Cache-Control: no-store` to successful responses |
    | `config/FhirConfig.java` | Registers plain providers and interceptors with the FHIR server |
    | `config/CernerConfig.java` | Registers `CernerAuthInterceptor` with the `cerner` FHIR client |
    | `exception/` | `ErrorMessage` catalog and HAPI FHIR exceptions, which are returned to clients as an `OperationOutcome` |
    | `AppProperties.java` | Service settings, bound from `va.mobile.fhirstarter.*` |
    | `docs/capability-statement*.json` | CapabilityStatement and OpenAPI documents, written by `CapabilityStatementITCase` |
  • The Dependencies table: replace the example rows with your two downstream systems, and remove the [//]: # (TODO: Fill in Dependencies) line:

    | External Dependency                            | Version | Description                                                  |
    |:-----------------------------------------------|:--------|:-------------------------------------------------------------|
    | Mobile MVI Service (`mobile-mvi-service`)      | v1      | Provides patient demographics and identifiers, by ICN        |
    | Oracle Health (Cerner) Millennium FHIR R4 API  | R4      | Provides patient encounters; requires an OAuth 2.0 client    |
  • The Service Interface table: add the $identifiers operation and the Encounter search, and remove "stub" from the Patient rows:

    | Endpoint | Description |
    |:---|:---|
    | `GET /fhirstarter/v1/fhir/metadata` | FHIR CapabilityStatement ([docs/capability-statement.json](docs/capability-statement.json)) |
    | `GET /fhirstarter/v1/fhir/api-docs` | OpenAPI document ([docs/capability-statement-openapi.json](docs/capability-statement-openapi.json)) |
    | `GET /fhirstarter/v1/fhir/swagger-ui/` | Swagger UI |
    | `GET /fhirstarter/v1/fhir/Patient?identifier=urn:oid:2.16.840.1.113883.4.349\|{icn}` | Patient search by ICN (staff, or a veteran for their own ICN) |
    | `GET /fhirstarter/v1/fhir/Patient/{icn}` | Patient read (staff, or a veteran for their own ICN) |
    | `GET /fhirstarter/v1/fhir/Patient/{icn}/$identifiers` | Identifiers of the patient, from MVI (staff, or a veteran for their own ICN) |
    | `GET /fhirstarter/v1/fhir/Encounter?patient={icn}&status={status}` | Encounter search by patient, with an optional status (staff, or a veteran for their own ICN) |
    | `GET /fhirstarter/v1/fhir/$validate-icn?icn={icn}` | Checks whether an identifier is a valid ICN (staff only) |
  • Under Health Check APIs, add a sentence about readiness, for example: "The readiness group (/actuator/health/readiness) includes the mvi and cerner health indicators, so the service only receives requests while the Mobile MVI Service and Oracle Health are reachable."

  • Delete the Infrastructure Notes and Atypical Patterns sections, which don’t apply to this service, and the italic instructions in Additional Documentation. Keep the line about the NGSS defect process.

The build copies README.md to docs/index.md, with its links adjusted for the docs directory, so commit both.

Service Design Document

docs/SRVDD.md is the service design document. A real service completes all of it with the tech lead before its first release. For this project, fill in its two most important tables, in your own words. The three short sections after them are optional.

  • Data Access / Storage Overview: replace the placeholder row with one row for each kind of data your service reads: patient demographics and patient identifiers from the Mobile MVI Service, encounters from Oracle Health, and the Oracle Health credentials and access tokens. Classify the patient data as PHI and the credentials as Sensitive. The template’s example table shows the format; delete it when your rows are done.

  • Logical Interactions: replace the example rows with one row for each endpoint of your service, with the external endpoints it calls. Use the CapabilityStatement and your clients, MviClient and CernerClient, to find them.

Optional:

  • Service Information: the repository, on your branch: https://coderepo.mobilehealth.va.gov/projects/CKM/repos/starter-project/browse/fhir-starter-service?at=fhir_starter_<firstname>_<lastname>. The service doesn’t exist on main, so a link without ?at= leads nowhere.

  • Purpose: one bullet that describes the service, and "This is the initial release of the service."

  • Intended Service Availability: check both Veteran and Staff. Your service is registered with both API gateways, as docs/dibr.md says.

Leave the other sections as they are, including the sequence diagrams under Logical Interface Design; a real service adds those with its tech lead.

When you’re done, compare your tables with the example answers below.

Example answer: Data Access / Storage Overview
| **Data Type** | **VAMF/VA Source** | **CRUD Operations** | **DB Type** | **DB Name** | **Data Classification** | **Reason / Requirement** |
|:---|:---:|:---:|:---:|:---:|:---:|:---:|
|Patient demographics (name, gender, birth date, address, phone) | mobile-mvi-service | R | N/A | N/A | PHI | Return FHIR Patient resources |
|Patient identifiers (ICN and correlated identifiers) | mobile-mvi-service | R | N/A | N/A | PHI | Return the result of the Patient `$identifiers` operation |
|Encounters | Oracle Health Millennium FHIR R4 API | R | N/A | N/A | PHI | Return FHIR Encounter resources |
|Oracle Health client ID, client secret, and access tokens | Vault; Oracle Health token endpoint | R | N/A | N/A | Sensitive | Authenticate the service to Oracle Health; the access token is held in memory only |
Example answer: Logical Interactions
|Endpoint|HTTP Action|External System Endpoint (table mapping for databases) | Authorization Type |Description|
|---|---|---|--------------------|---|
|/fhirstarter/v1/fhir/Patient?identifier={system}\|{icn}|GET|mobile-mvi-service: GET /mvi/v1/patients/ICN/{icn}/mvi/person| JWT Role-Based |Search for a patient by ICN|
|/fhirstarter/v1/fhir/Patient/{icn}|GET|mobile-mvi-service: GET /mvi/v1/patients/ICN/{icn}/mvi/person| JWT Role-Based |Read a patient|
|/fhirstarter/v1/fhir/Patient/{icn}/$identifiers|GET|mobile-mvi-service: GET /mvi/v1/patients/ICN/{icn}/mvi/identifiers| JWT Role-Based |Return the identifiers that MVI correlates with a patient|
|/fhirstarter/v1/fhir/Encounter?patient={icn}&status={status}|GET|Oracle Health: POST {token URL} (OAuth 2.0 client credentials)<br/>Oracle Health: GET {FHIR URL}/Patient?identifier={system}\|{icn}<br/>Oracle Health: GET {FHIR URL}/Encounter?subject=Patient/{id}&status={status}| JWT Role-Based |Search for the encounters of a patient|
|/fhirstarter/v1/fhir/$validate-icn?icn={icn}|GET|None| JWT Role-Based |Check whether an identifier is a valid ICN|
|/fhirstarter/v1/fhir/metadata<br/>/fhirstarter/v1/fhir/api-docs<br/>/fhirstarter/v1/fhir/swagger-ui/|GET|None| Unauthenticated |CapabilityStatement, OpenAPI document, and Swagger UI|

Change Log and Deployment Documentation

Describe what release 1.0.0 contains in CHANGELOG.md. Replace CKM-## with the Jira issue of your onboarding work, if you have one:

# fhir-starter-service Changelog

## Release 1.0.0

* CKM-####: Initial release
  * Patient read and search by ICN, with demographics from the Mobile MVI Service
  * Patient `$identifiers` operation, with the identifiers that MVI correlates with the patient
  * Encounter search by patient and status, with encounters from Oracle Health
  * `$validate-icn` system operation
  * `Cache-Control: no-store` on successful responses
  * HTTP 503 when the Mobile MVI Service is unavailable

You documented the Consul settings and Vault secrets in metadata.yaml on page 7. Check that docs/dibr.md, which the build generates from it, lists the Vault secrets.

The integration tests write the CapabilityStatement and OpenAPI documents to docs/capability-statement.json and docs/capability-statement-openapi.json. They are the contract of your service, so review their changes before you commit them.

Complete the Tests

To check your tests, compare them with the test classes in the "FHIR starter page 9" commit of the reference branch; see When You Get Stuck.

Check the Server Description

In CapabilityStatementITCase, add two assertions to verifyCapabilityStatement:

  • capabilityStatement.getSoftware().getName() is FHIR Starter Service.

  • The Encounter resource declares the patient and status search parameters. Select the Encounter resource the way the assertion for the Patient identifiers operation selects the Patient resource. getSearchParam() returns the search parameters of a resource, and each one has a getName(). Their class is nested in CapabilityStatement, which the test already imports, so you can refer to the method as CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent::getName.

Fill the Gaps in the Test Matrix (Optional)

This section is optional. Nothing later in the guide depends on it. The five tests follow patterns you have already practiced, so if you are short on time, skip them, or pair with a teammate on them before your review.

Your integration tests check each interaction for each kind of user and each kind of failure. The table below shows which test covers each case. The five tests in bold don’t exist yet:

Interaction Staff Veteran, own ICN Veteran, other ICN Unauthenticated Invalid input Not found Downstream problem

Patient search

searchByIcn_staff

searchByIcn_veteranSelf

searchByIcn_veteranOther

searchByIcn_unauthenticated

searchByIcn_invalidIcn

searchByIcn_notFound

searchByIcn_mviError

Patient read

readById_staff

readById_veteranSelf

readById_veteranOther

readById_unauthenticated

readById_invalidIcn

readById_notFound

readById_mviError, readById_mviUnreachable

$identifiers

identifiers_staff

identifiers_veteranSelf

identifiers_veteranOther

identifiers_unauthenticated

identifiers_invalidIcn

identifiers_notFound

Same code as the read

Encounter search

search_staff, search_status

search_veteranSelf

search_veteranOther

search_unauthenticated

search_missingPatient, search_invalidStatus, search_invalidIcn

search_noOracleHealthRecord

search_ambiguousPatient

Add the five tests. Each one follows a test you already have:

Test Class Request Expected result

readById_unauthenticated

PatientResourceITCase

Read VETERAN_ICN with a JWT built with authenticated(false)

AuthenticationException

identifiers_veteranSelf

PatientOperationsITCase

$identifiers of VETERAN_ICN, with a veteran JWT for VETERAN_ICN

Two identifier parameters

identifiers_invalidIcn

PatientOperationsITCase

$identifiers of not-an-icn, with a staff JWT

InvalidRequestException with a message that contains expected an ICN

identifiers_unauthenticated

PatientOperationsITCase

$identifiers of VETERAN_ICN, with a JWT built with authenticated(false)

AuthenticationException

search_invalidIcn

EncounterResourceITCase

Search for the encounters of not-an-icn, with a staff JWT

InvalidRequestException with a message that contains expected an ICN

There is no integration test for an Oracle Health failure, because cerner-mock can’t be made to fail for one patient. CernerClient.send handles failures the same way as MviClient.send, which the MVI error stubs test.

Run the Full Build

./mvnw clean install -Pwith-skaffold

The reference solution of this guide has 43 unit tests and 40 integration tests, and your build should report the same numbers, or 35 integration tests if you skipped the optional gap tests. Parameterized tests count once for each case, so if your unit test count is different, compare your toGender cases with the hint on page 4. Open target/site/jacoco-aggregate/index.html and look at the coverage of the classes you wrote.

Commit and Push

git add .
git commit -m "Describe and document the service, and complete its tests"
git push

Review / Acceptance

Make sure all your changes are committed and pushed before you start your review.

When you have completed the FHIR Starter Project, work with the onboarding team to review it. The review is informal, and focuses on making sure you understand how a FHIR service is built, and the tools we use to build it. Be ready to:

  • Show your CapabilityStatement at /metadata, and the Swagger UI.

  • Call each interaction with curl or Postman: the Patient search, read, and $identifiers operation, the Encounter search, and $validate-icn. Show that $VETERAN_JWT can read PATIENT FIFTEEN’s records, and no one else’s.

  • Show one error of each kind, and explain where it comes from: HTTP 400 (Patient/not-an-icn), 401 (no JWT), 403 (Patient/10132V467385 with $VETERAN_JWT), 404 (Patient/1012845331V153043), 500 (Encounter?patient=10180V273016), and 503 (Patient/10108V420871).

  • Show /actuator/health, with the mvi and cerner components.

  • Walk through your test classes, and explain how the test matrix covers the access rules.

  • Explain how a request flows from HAPI through your provider to the Mobile MVI Service or Oracle Health, and back to the client as a FHIR resource or an OperationOutcome.

Questions and open discussion are encouraged; this is your opportunity to get answers and provide feedback.

You’re Done When

  • /metadata names and describes your server

  • README.md, docs/SRVDD.md, CHANGELOG.md, and metadata.yaml describe what you built

  • The full build passes (and, if you did the optional section, the test matrix has no gaps)

  • Your branch is pushed

  • You have scheduled your review with the onboarding team

Congratulations, you have built a FHIR service! Return to the New Hire Checklist for your next onboarding steps.