Skip to content

Fhir_extensions

Bill Majurski edited this page Oct 2, 2017 · 18 revisions

Toolkit on FHIR

Updates

Since this was first presented, several key things have changed.

  • ResDb has gone away. FHIR simulators live in SimDb just like SOAP based simulators.

Goals

  • Integrate an existing FHIR server into Toolkit

  • Simulator as FHIR server

    • One instance of Toolkit is many FHIR server instances

  • Extend Simulator event logging to FHIR

  • Use Test client as FHIR client

  • Extend Client event logging to FHIR

FHIR server

  • Using HAPI open source FHIR implementation

  • Full HAPI server is a complete FHIR implementation - too much for testing

  • Integrate as a collection of JAR files and a Servlet

  • This approach

    • Requires integration with a data server

      • Extension of SimDb

      • Search implemented with Lucene

    • We implement each Resource of interest based on their APIs

  • Built in handling of XML or JSON

Simulator as FHIR server

  • Create FHIR server with same ease as we now create a Registry or Repository

    • Two possible names: FHIR simulator or Resource simulator

  • Data organization is a bit different

    • Use a variation on SimDb

    • FHIR integration is hidden to most of code base

  • We implement per-resource searches in our code

    • Resource indexing/searching based on Apache Lucene

Simulator event logging

  • SimDb extension is (currently) called ResDb (Resource DB)

  • SimDb.groovy is used for both SOAP and FHIR simulators

  • Specific FHIR operations are added

  • SimId class

    • Used for both types of simulators

    • Carries this is FHIR status flag

  • Same utilities used to create SOAP and FHIR simulators

  • =⇒ Same GUI too!

Test client as FHIR client

  • FHIR client is just HTTP client with content rules

  • FHIR defines collection of operations that map to HTTP operations

  • Create Toolkit transactions for each operation

  • Use same logging

Client event logging

  • TBD

FHIR base

  • The base URL of a FHIR server - common term in FHIR spec

  • Examples typically show something like http://example.com/fhir

  • Toolkit needs are more complicated - one URL offers multiple servers (simulators)

  • Toolkit base URL for FHIR is http://example.com/xdstools/fsim/${simId}

  • =⇒ The Simulator ID is built into the base URL

FHIR operations on a Resource type

See FHIR Spec

  • Create

  • Search

FHIR operations on a Resource instance

  • Read

  • Update

  • Delete

(These are a subset of the available operations)

Submission Operations against a server

  • Batch

  • Transaction

This is where you can have multiple Resources in a submission. With Batch a partial sucesss is possible. With transaction it is all or none.

We now have two uses of the term transaction: an IHE reference and a FHIR reference

MHD (XDS on FHIR) Provide is based on FHIR transaction.

Batch and Transaction operations can include multiple Resource types.

Bundle

  • In SOAP we had MTOM to assemble multiple things into one message

  • FHIR uses a Bundle

  • Bundle is defined as part of the FHIR standard

  • Technically it is a Resource (meta-Resource????)

  • Can be coded as XML or JSON

  • Contains zero or more Resources

  • Carries FHIR metadata (details about the bundle)

    • Not to be confused with XDS metadata which is implemented in FHIR using three types of Resources

ResDB

  • Same directory structure as SimDb

  • Separate directory

    External_Cache/
        environment/
        actors/
        simdb/
        TestLogCache/
        resdb/

In resdb

default__test/
    fhir/
       any/
          2017_06_27_06_53_00_157/
              date.ser
              Patient/
                  UUID1.json
              request_body.bin
              request_body.txt
              request_hdr.txt
    sim_type.txt
    simId.txt
    simindex/
  • default__test names the simulator. Same as SimDb.

  • Use SimId class for SOAP or FHIR simulators

    • Has flag indicating which type

    • Difference managed by SimDb.groovy

  • The directory simindex is owned by the Lucene search engine which we use to index the resources in a simulator. Lucene manages all content in this directory.

  • The date named directory is the event (reaction to an incoming transaction). Same as SimDb.

  • fhir and any directories are the equivalent of the actor and transaction directories in SimDb. These may change as the implementation gets more sophisticated. You should only reference them through SimDb.groovy.

  • There is directory for each Resource type. Here is shown Patient. In FHIR resources are managed within their type. So, there can be a Patient with ID of 1 and an Observation with ID of 1. It is the type/ID that must be unique. This is why you frequently see example resource IDs like Patient/8.

  • FHIR spec mandates that resource ids must be no more that’s 64 characters, be alpha-numeric, hyphen, period in construction. So, it is legal to create a resource id using a UUID without the urn:uuid: prefix. Toolkit uses this approach for now…​

  • This is good and bad. We do not have to keep a counter to assign the next id. But you cannot glance at the IDs and tell the order of creation. Also, since I need to test MHD using UUIDs does make some things easier. I cannot comment on whether this will change in the future. Your code should not depend on it.

Implementing support for a Resource in the server

  • Resource Provider

    • A Java/Groovy class that implements the operations on a single Resource type

    • This is part of the HAPI/FHIR architecture

    • Implements IResourceProvider interface

    • Key methods are identified by Java annotations

  • Primary operations

    • Create - a resource instance

    • Read - GET by Resource ID

    • Search - GET by query parameters

  • Secondary issues

    • Versioning

    • Validation

  • Patient Resource example is found here

Implementing Create

  • Implementing method annotated with @Create

  • Most of the implementation is delegated to the support class ToolkitResourceProvider

  • The exception is the resource validation - that is resource specific

  • Submission can be in XML or JSON format

  • Internally (ResDb) all Resource storage is in JSON format

  • Server assigns ID and Version to Resource

  • Create returns status 201 for successful creation

  • Also returns Location HTTP header - the URL of where the newly created Resource can be read from

  • Example:

Location: /xdstools2/sim/default__test/Patient/1ff98d09-r3r4-8883-bd98-994309fce920/_history/1

Note that the host and port are not part of the Location.

Later when this is used in a Read, the formal reference is Patient/1ff98d09-r3r4-8883-bd98-994309fce920

Implementing Read

  • Implementing method annotated with @Read

  • Read is a simple lookup by ID

  • Internally (in Toolkit), this is a search by type and ID because of the way we use Lucene

  • Most of the implementation is delegated to the support class ToolkitResourceProvider

  • The exception is the reading of the file and parsing the JSON into a Resource class for returning

  • HAPI framework handles translating into the requested format

    • XML or JSON

  • Implementing method annotated with @Search

  • Search parameters are specified in spec Patient example

  • In the implementing method, parameters are tagged as Required or Optional

  • Search translates into Lucene Boolean Query

  • PatientResourceProvider shows implementation of required and optional parameters

  • There can be multiple @Search methods

    • First one listed that matches the parameters provided in the URL is used

  • Remember that each simulator has its own Lucene index

    • and it can contain content for multiple Resource types

Test Client

  • Support for the Create, Read, and Query operations have been added to the test client

  • These are demonstrated in the test definition FhirTestClientCreate which has a section for each operation

Create Transaction

<TestPlan>
  <Test>FhirTestClientCreate/create</Test>
  <TestStep id="create">
    <ExpectedStatus>Success</ExpectedStatus>
    <FhirCreateTransaction>
        <ResourceFile>patient.json</ResourceFile>
        <UrlExtension>/Patient</UrlExtension>
    </FhirCreateTransaction>
  </TestStep>
</TestPlan>
  • The transaction name is FhirCreateTransaction

  • It requires two parameters as shown

  • ResourceFile points to an input file in the same directory

  • It contains the JSON definition for a single Resource instance to be submitted

  • UrlExtension is part of the FHIR server URL

    • The endpoint managed by Toolkit provides the base address of the FHIR server

    • In the case of a simulator it looks like

      http://localhost:8889/xdstools2/fsim/bill__myfhirsys
    • Where bill__myfhirsys is the simulator ID

    • The UrlExtension is appended to the base FHIR server address

    • In this case we are creating a Patient resource so /Patient is required

    • The final URL is

      http://localhost:8889/xdstools2/fsim/bill__myfhirsys/Patient
  • A significant output of the transaction is the Reports generated, some of which are required by the READ and QUERY transaction

    <Report name="Url">http://localhost:8889/xdstools2/fsim/bill__myfhirsys/Patient</Report>
    <Report name="FhirIdWithHistory">Patient/36bc13d4-82e2-4407-9d0e-83422afb955f/_history/1</Report>
    <Report name="RefWithHistory">http://localhost:8889/xdstools2/fsim/bill__myfhirsys/Patient/36bc13d4-82e2-4407-9d0e-83422afb955f/_history/1</Report>
    <Report name="FhirId">Patient/36bc13d4-82e2-4407-9d0e-83422afb955f</Report>
    <Report name="Ref">http://localhost:8889/xdstools2/fsim/bill__myfhirsys/Patient/36bc13d4-82e2-4407-9d0e-83422afb955f</Report>
  • Url is the endpoint used (to mix REST and SOAP terminology)

  • FhirId is the formal ID of the resource as it sits on the server (simulator in this case)

  • Ref is the combination of the base server URL and the resource ID (FhirID)

  • The two WithHistory reports include the history information

    • Managing history is optional and not yet supported by Toolkit

Read Transaction

<TestPlan>
  <Test>FhirTestClientCreate/read</Test>
  <TestStep id="read">
    <ExpectedStatus>Success</ExpectedStatus>
    <FhirReadTransaction>
        <UseReport test="FhirTestClientCreate" section="create" step="create" reportName="Ref" useAs="Ref"/>
    </FhirReadTransaction>
  </TestStep>
</TestPlan>
  • Note the UseReport references the Create transaction

  • The reportName references the auto-generated Report from the Create transaction

  • The useAs is Ref but there is not metadata file like with SOAP transactions

    • This value Ref is expected by the FhirReadTransaction implementation.

  • The execution of this transaction produces this Report

    <Report name="Url">http://localhost:8889/xdstools2/fsim/bill__myfhirsys/Patient/36bc13d4-82e2-4407-9d0e-83422afb955f</Report>
  • This is the URL that was read

  • The other significant output is the Result which is the JSON coded resource returned

Query Transaction

<TestPlan>
  <Test>FhirTestClientCreate/query</Test>
  <TestStep id="query">
    <ExpectedStatus>Success</ExpectedStatus>
    <FhirQueryTransaction>
        <UseReport test="FhirTestClientCreate" section="create" step="create" reportName="Url" useAs="Url"/>
        <QueryParams>?family=Chalmers</QueryParams>
    </FhirQueryTransaction>
  </TestStep>
</TestPlan>
  • The above UseReport is required and must have the useAs value of Url as shown

    • Note that it references the create section of the test

  • The QueryParams element is also required - it is the query extension to the Url

  • Taking the Url reported by the Create transaction above, the resulting query (GET) is

    http://localhost:8889/xdstools2/fsim/bill__myfhirsys/Patient?family=Chalmers
  • The transaction generates a single Report which is the URL it used in the HTTP GET

    <Report name="Url">http://localhost:8889/xdstools2/fsim/bill__myfhirsys/Patient?family=Chalmers</Report>
  • And in Result is the JSON encoded response

    • For a query this is always a Bundle

    • In this example the bundle contains a single resource (our query matched a single resource)

Integration tests

  • The Integration Test fhir/TestClientSpec.groovy demonstrates their use

  • FHIR-based Spock tests are a bit different

    class TestClientSpec extends FhirSpecification
  • They extend FhirSpecification

    @Shared SimId simId = new SimId(testSession, simIdName).forFhir()
  • They use a new extension on SimIds shown with the .forFhir() call which sets a boolean

  • SimId and SimDb have been extended to support FHIR

  • There are some new FHIR-specific methods exposed for low-level testing, the .forFhir() should get you all the functionality you need

    startGrizzlyWithFhir('8889')
  • The Grizzly Servlet container is started with HAPI/FHIR integrated

    spi.delete(spiSimId(simId))
  • The Service Programming Interface (SPI) has been extended

  • Note the SimId translation needed (SPI and GWT have to use different implementations)

    spi.createFhirServer(simId.id, simId.user, 'default')
  • Create a FHIR server (simulator) with the SimId shown and the default environment

      def 'do create'() {
        when:
          def sections = ['create']
          def params = [ :]
          List<Result> results = api.runTest(testSession, siteName, testInstance, sections, params, true)
        then:
          results.size() == 1
          results.get(0).passed()
    }
  • Building a test is identical once the proper test environment is created

FHIR Context - expensive to build

  • The HAPI code base relies on a structure called FhirContext

  • With references to this structure the code is made to implement multiple versions of FHIR standard

    FhirContext ourCtx = FhirContext.forDstu3()
  • is the incantation to establish an environment for DSTU3

  • Creating this context is expensive and the server should only do it once

  • Towards that we have

    class ToolkitFhirContext {
      static private FhirContext ourCtx
      public static FhirContext get() {
        if (!ourCtx)
            ourCtx = FhirContext.forDstu3()
        return ourCtx
      }
    }
  • so to get the current context

    ToolkitFhirContext.get()
  • This will need extension as we start to play with multiple versions

Clone this wiki locally