LinuxForHealth / FHIR

The LinuxForHealth FHIR® Server and related projects
https://linuxforhealth.github.io/FHIR
Apache License 2.0
332 stars 157 forks source link

Remove (or restore) virtual resources feature #153

Closed lmsurpre closed 5 years ago

lmsurpre commented 5 years ago

In our DSTU2 release, we supported a feature called "virtual resources" where the REST layer had a facade (implemented via servlet filters) for accepting custom JSON at endpoints beyond the scope of the FHIR Resources.

When the JSON hit the server, we'd convert it to a Basic resource with extensions, and we'd set the code of the Basic resource to the name of the virtual resource. Similarly, we'd support some level of retrieval for the virtual resources so that clients could "pretend" like they were using real FHIR resources.

Unfortunately, over time, this feature was hard to maintain and the abstraction was quite leaky. For example, a query for Basic resources would return all the virtual resources and so in some cases the users needed to understand the "magic" being performed on the server.

Our theory is that, with the expanded set of Resources available in FHIR R4, then need for virtual resources should be much less and so our plan is to remove the feature.

Currently, the feature is sprinkled throughout the code, but almost certainly fails to work due to new code that was added which assume its removal.

Unless we want to revisit the need for this feature, we should remove all traces of it from our codebase, including but not limited to:

elsalant commented 5 years ago

We have a use case for a European Union sponsored project where patients are reporting on what they ate via an application. This information is used by a dietician to help track their progress. We can imagine that together with this, the patients could be monitored by IoT devices, (e.g. fitbits etc), and therefore wanted to use FHIR to store this information.
It turns out, that there are no FHIR v4 resources that come close to reporting on food consumed (e.g. 250 grams of fish, 1 cup of milk for lunch), and therefore we experimented creating a "Meal" virtual resource. This was really handy to have and certainly presented a more logical representation to the application than our alternative, which is using an "Observation" resource and adding resources - not to mention that there were several fields in the Observation resource that are required fields, but not relevant to the use case. That being said, virtual resources in their current form cannot be used by FHIR client code which is a major limitation. My vote would be to keep virtual resources, but to autogenerate the class file required by the client API.

lmsurpre commented 5 years ago

Thanks Eliot. John opened https://chat.fhir.org/#narrow/stream/179166-implementers/topic/observation.20about.20food.20consumed for bringing this use case to the FHIR community.

My vote would be to keep virtual resources, but to autogenerate the class file required by the client API.

I like this line of thought but would prefer to restrict the "virtual resource" abstraction to the client side if possible. That would provide a couple key benefits:

  1. it would enable you to use this feature/concept with any FHIR-compliant fhir server
  2. it keeps the leaky abstraction out of the server code

@JohnTimm and I have kicked some ideas around for generating Java model objects for FHIR Profiles and this might be a good use case for fleshing out that idea. Basically, it would work like this:

  1. We update our code generator in fhir-tools to better support generating java model objects that extend the fhir-model from FHIR profiles.
  2. We update our Parser/Generator/Validator to support parsing/generating/validating those model extension classes
  3. You define the Profile using any FHIR profiling tool you want (e.g. Forge or Trifolio or other)
  4. You generate a java object model using the updated code generator in fhir-tools
  5. You use your custom resource with our existing FHIR Model Parser/Generator/Validator and our existing fhir-client/fhir-provider
JohnTimm commented 5 years ago

Here's an example using Observation:

        Observation foodIntakeObservation = Observation.builder()
                .status(ObservationStatus.FINAL)
                .code(CodeableConcept.builder()
                    .coding(Coding.builder()
                        .system(Uri.of("http://snomed.info/sct"))
                        .code(Code.of("226379006"))
                        .display(string("Food intake"))
                        .build())
                    .build())
                .component(Component.builder()
                    .code(CodeableConcept.builder()
                        .coding(Coding.builder()
                            .system(Uri.of("http://snomed.info/sct"))
                            .code(Code.of("226441002"))
                            .display(string("Fish intake"))
                            .build())
                        .build())
                    .value(Quantity.builder()
                        .value(Decimal.of(250))
                        .system(Uri.of("http://unitsofmeasure.org"))
                        .code(Code.of("g"))
                        .unit(string("grams"))
                        .build())
                    .build())
                .component(Component.builder()
                    .code(CodeableConcept.builder()
                        .coding(Coding.builder()
                            .system(Uri.of("http://snomed.info/sct"))
                            .code(Code.of("226404003"))
                            .display(string("Milk intake"))
                            .build())
                        .build())
                    .value(Quantity.builder()
                        .value(Decimal.of(1))
                        .system(Uri.of("http://unitsofmeasure.org"))
                        .code(Code.of("[cup_us]"))
                        .unit(string("cup"))
                        .build())   
                    .build())
                .build();

which yields:

{
    "resourceType": "Observation",
    "status": "final",
    "code": {
        "coding": [
            {
                "system": "http://snomed.info/sct",
                "code": "226379006",
                "display": "Food intake"
            }
        ]
    },
    "component": [
        {
            "code": {
                "coding": [
                    {
                        "system": "http://snomed.info/sct",
                        "code": "226441002",
                        "display": "Fish intake"
                    }
                ]
            },
            "valueQuantity": {
                "value": 250,
                "unit": "grams",
                "system": "http://unitsofmeasure.org",
                "code": "g"
            }
        },
        {
            "code": {
                "coding": [
                    {
                        "system": "http://snomed.info/sct",
                        "code": "226404003",
                        "display": "Milk intake"
                    }
                ]
            },
            "valueQuantity": {
                "value": 1,
                "unit": "cup",
                "system": "http://unitsofmeasure.org",
                "code": "[cup_us]"
            }
        }
    ]
}

which isn't terribly complex. That said, I agree with @lmsurpre that we could make this easier on the client side through generated APIs, etc.

elsalant commented 5 years ago

Yes, but if we are talking about doing a POST via REST, I can compose a clean object like: { "resourceType": "Meal", "references": "Patient/123", "specifics": { "type": "Breakfast", "amount": 123.4, "units": "grams", "foodEaten": "Hot dogs and rice" }, "date": "December 10, 2019", "comment": "It was good" }

elsalant commented 5 years ago

Actually, if you do this on the client side, behind the scenes, then doesn't that mean that the user will be unaware of exactly how you are doing the translation? In this case, this will mean that if the user wants to use ReST he/she will have no idea of how the the virtual resource should be composed (for the POST for example) or how to query on the GET...