FHIR Chat · Version independence and ITypedElement · dotnet

Stream: dotnet

Topic: Version independence and ITypedElement


view this post on Zulip Angus Millar (Aug 02 2019 at 01:28):

Hi Guys, I have been playing around with the ISourceNode and ITypedElement as described here in the documentation for the .NET FHIR API http://docs.simplifier.net/fhirnetapi/parsing/elementmodel-overview.html
I managed to get ITypedElement from ISourceNode to work where I can provide the type information either from a POCO instance or the specification.zip and the ZipSource as an IResourceResolver. So it all worked as it was documented.

However, I seem to be missing the use-case for all this. I had thought I could use ITypedElement as a way of making my implementation FHIR version agnostic where I would provide the appropriate Stu3 or R4 specification.zip depending on which version of a resource I wanted to parse and work with. However, it now occurs to me that when supplying the Stu3 specification.zip to the R4 tooling that the R4 tooling expects R4 FHIR resources (StructuredDefinitions) in the specification.zip.

I guess the end game I was hoping to achieve was to be able to use ISourceNode and ITypedElement to run FHIRPath statements to get the search parameter index data in a way that was FHIR version independent. I'm now not sure that this goal is achievable. I was wondering if @Michel Rutten or @Ewout Kramer could comment on this?
Am I chasing Unicorns?

view this post on Zulip Michel Rutten (Aug 05 2019 at 09:00):

Hi @Angus Millar, our Vonk server developers @Christiaan Knaap and @Marco Visser can probably give you some guidance, as they are facing similar challenges. However, both are currently on a well deserved holiday break. I'll ask my colleagues to respond when they are back in the office.

view this post on Zulip Ewout Kramer (Aug 12 2019 at 11:50):

Hi Angus - yes, the ITypedElement and ISourceNode related functionality is there to support working with data that has no precompiled POCO's, and thus would be one part of the solution to work with multiple versions of FHIR. In fact, the parsers and more and more parts of the API are working with these classes and have recently moved to a new repository (fhir-net-common) that has NO dependency on a specific FHIR version.

Then why is there a dependence on StructureDefinition? Well, there's not, there's a dependency on IStructureDefinitionSummaryProvider. I'll elaborate:

  • You can read data with ISourceNode (in fact, that's what the xml and json parsers implement). This does not know ANYTHING about FHIR versions and has no type information. Hence, when you use ISourceNode to parse, you'll work with strings, and element names like "Observation.valueQuantity"
  • One level up, there's ITypedElement, this is useful when you DO need type information, e.g. you need to know that a certain element is a bool or a HumanName. Almost all higher-level functionality (like the validator, and the FhirPath engine) need type information and run on top of ITypedElement.

How does a type-info-less ISourceNode turn into an ITypedElement? By adding type information. This is supplied by the aforementioned IStructureDefinitionSummaryProvider. This interface itself has no dependency on a specific FHRI release. We currently ship two implementations of this interface: one which implements this interface by getting information using reflection from POCO's and one that reads this information from StructureDefinition resources in specification.zip (or any other source).

So, both these implementations of the interface do depend on POCO's. This is what we figured most people needed first. But the fact that all of this is hidden behind this interface enables us to supply concrete implementations that do not depend on POCO's (e.g. by reading it directly from the StructureDefinition XML, or from a Protobuf-stored representation or -using ISourceNode- from a StructureDefinition in xml/json). Also, you could choose to have the definitions for STU3 and R4 both present in R4 StructureDefinitions (;-)) - in which case you would depend on the R4 POCO's only.

What Vonk (and others) have done, is use the StructureDefinition POCO-based implementations. They use both implementations in one AppDomain by loading both versions of the assembly. That's another possible solution.

So in summary, no you're not chasing Unicorns - you have correctly understood what these interfaces are meant for. It's just that at some point you might need type information - and the only two sources we currently have support for are POCO's and StructureDefinitions (which itself are POCO's)....but principally in the design, this need not be the case.

If you look at the implementation using StructureDefinition (I think it's called StructureDefinitionSummaryProvider), you can imagine how this could be done by just reading raw json files, or just the very thin ISourceNode-based parsers).

At little bit more background on this can be found here: http://docs.simplifier.net/fhirnetapi/parsing.html

view this post on Zulip Angus Millar (Aug 18 2019 at 23:57):

Thank you very much for your help Ewout. I seem to be making progress. I have gone with two separate projects R4 and STU3 within my solution where each has a dependency on the corresponding hl7.fhir.Specification (R4 & STU3). Each of these implements an interface called IFhirR4SpecificationServices and IFhirSTU3SpecificationServices which both inherit from a third interface called IFhirSpecificationServices. The third interface just allows simple registering of services in my Dependency Injection library. These then provide the methods:

public ITypedElement ToTypedElement(ISourceNode resourceSourceNode);
public List<Hl7.Fhir.Utility.ExceptionNotification> GetErrors(ITypedElement resourceTypedElement);
public IEnumerable<ITypedElement> GetFhirPathItem(ITypedElement resourceTypedElement, string FhirPathExpression);

Then with this, I can do this logic in my main app domain to get the appropriate ITypeElement based on the required FHIR Version :

if (command is UpdateResourceCommand UpdateResourceCommand)
      {
        switch (UpdateResourceCommand.FhirVersion)
        {
          case Smoke.Common.FhirVersion.R4:
            {
              ITypedElement resourceTypedElement = this.IFhirR4VersionSchemaService.ToTypedElement(UpdateResourceCommand.ResourceSourceNode);
              UpdateResourceCommand.SchemaExceptionList = this.IFhirR4VersionSchemaService.GetErrors(resourceTypedElement);
              break;
            }
          case Smoke.Common.FhirVersion.STU3:
            {
              ITypedElement resourceTypedElement = this.IFhirStu3VersionSchemaService.ToTypedElement(UpdateResourceCommand.ResourceSourceNode);
              UpdateResourceCommand.SchemaExceptionList = this.IFhirStu3VersionSchemaService.GetErrors(resourceTypedElement);
              break;
            }
          default:
            break;
        }
      }

I did this to avoid the extern alias R4; and extern alias STU3; all over the place. I plan on working with the ITypedElement throughout my main Domain for resource manitpulations.

Still, a work in progress but I'm beginning to see my Unicorn may not have a horn.

view this post on Zulip Ewout Kramer (Aug 19 2019 at 09:11):

This is certainly close the way we've done things in our software too!

view this post on Zulip Angus Millar (Aug 21 2019 at 08:10):

So I have now run into the problem of updating the resource. I had incorrectly thought I could manipulate a given resource using ISourceNode or possibly ITypedElement interfaces. I can certainly create a new resource using SourceNode.Resource() & SourceNode.Node() & SourceNode.Valued(). However, I have just discovered that both interface only allow get; and not set;. For instance, I wanted to set the resource.meta.lastUpdated value, something like below.
Are there any options here without going to the POCOs?

    ISourceNode metaSourceNode = resource.Children().FirstOrDefault(x => x.Name == "meta");
      if (metaSourceNode != null)
      {
        ISourceNode lastUpdatedSourceNode = metaSourceNode.Children().FirstOrDefault(x => x.Name == "lastUpdated");
        if (lastUpdatedSourceNode != null)
        {
          lastUpdatedSourceNode = SourceNode.Valued("lastUpdated", dateTime.ToString());
        }
      }

view this post on Zulip Ewout Kramer (Sep 09 2019 at 14:49):

Yes, ITypedElement is read-only, since it can represent json/xml files - in fact it will not read them into memory completel, but as you navigate through them it will parse them element by element. So, that's why you cannot write to an ITypedElement. There is an updateable implementation of ITypedElement however, it is called ElementNode. This represents a DOM-like tree of data in-memory, so it can be updated. Also, you can clone any ITypedElement into it, and then manipulate the tree and then serialize or whatever!

view this post on Zulip Angus Millar (Sep 11 2019 at 04:20):

Wow, thank you very much @Ewout Kramer . I will go investigate this ElementNode implementation.


Last updated: Apr 12 2022 at 19:14 UTC