Skip to content

Breaking changes in 2.0

Ewout Kramer edited this page May 21, 2021 · 115 revisions

Moving classes to a common assembly

Starting from version 2.0 of the SDK we will gradually be moving classes from the assemblies that are specific to a FHIR version to a common, not FHIR version related assembly. Although we try to retain the existing public interfaces, it does mean these classes are moved between assemblies (not namespaces!). This will be irrelevant to most, but if you have created aliases referring to specific assemblies (as you might do if you need to support multiple versions of FHIR by loading the different FHIR-specific assemblies in your application concurrently), you will of course be hit by these changes.

Obsolete members and classes removed

  • PartialDateTime.FromDateTime(DateTime) has been removed. Replace with a call to FromDateTimeOffset().
  • The functions in namespace Hl7.Fhir.XPath (Hl7.Fhir.Specificaion assembly) have been dropped. They have been in disuse since DSTU1 and have not been maintained since.
  • The class CanonicalUrlConflictException has been removed.
  • The class XmlSerializationHint has been removed.
  • The class FhirSerializer and FhirParser have been removed. They have been obsolete for quite some time and replaced by FhirXmlParser and FhirJsonParser.
  • The interface IElementNavigator (and all its implementations and referring methods) have been removed. All of these have been obsoleted for quite a while.
  • Base.UserData (this property appears in all FHIR POCO's) has been removed. It has been replaced by the IAnnotated/IAnnotatable/Annotations() approach from .NET since 2016.
  • ContentType.IsValidBundleContentType was used to recognize the old DSTU1 "atom" bundle type. It was time for it to go.
  • All "DSTU1/2 backwards compatibility members". We retained some of the DSTU1/DSTU2 members in StructureDefinition/ElementDefinition just so you would get an Obsolete message on how to upgrade. These have now been removed.
  • We removed the FhirPath extension methods on Base (Select(), Scalar(), Predicate(), IsBoolean()) that did not take an EvaluationContext parameter. These have been replaced since 2016/2017.
  • Bundle.EntryComponent.Base and Bundle.Base have been removed. These have disappeared from the FHIR datamodel since DSTU2.
  • The Bundle.FindEntry() extension method using type and id parameters have been replaced by a function taking a fullUrl for quite a while, and has been removed.
  • ElementDefinition.Name has been renamed to ElementDefinition.SliceName since DSTU2, and has been removed.
  • ModelInfo.GetFhirTypeForType(), GetTypeForResourceName() and `GetResourceNameForType() have been obsoleted in 2016 and are now gone.
  • The OperationOutcome.ForException() and ForMessage() overloads that did not take an IssueType parameter have been removed.
  • ValueSet.Define was renamed to CodeSystem in DSTU2 and then moved to its own proper resource in STU3. This property has now been removed.
  • ResourceIdentity.Endpoint was renamed to ResourceBase in 2015. This property has now been removed.
  • ResourceIdentity.Collection was using the historical term "collection" for what is now a resource. That was a thing in 2014.
  • The static FhirParser and FhirSerializer classes have been deprecated since 2017, nowadays we create an instance of Fhir[Xml/Json]Parser and Fhir[Xml/Json]Serializer and use the method on these instances instead.
  • ModelInfo.IsInstanceTypeFor() will no longer accept variants of string, id and Quantity as instances type for these types.
  • The interface ITerminologyService has been changed as well: the method ValidateCode has been made obsolete and has been replaced by the async method ValueSetValidateCode. Futhermore the interface has been expanded with extra async methods (ValueSetValidateCode, CodeSystemValidateCode, Expand, Lookup, Translate, Subsumes and Closure), which should be implemented when you are implementing this interface.

Resolvers supporting Async

We have started to add async support to the resolvers for the conformance resources (i.e. DirectorySource), which are the most critical I/O parts of the SDK:

  • DirectorySource, ZipSource, WebResolver (though these are not yet completely fully async internally)
  • SnapshotSource (which also meant removing its Source property)
  • MultiResolver, CachedResolver

Based on this new functionality, we have changed the implementation of the SnapshotGenerator and ValueSetExpander to be asynchronous. For backwards compatibility, all of these components still support the existing sync functionality, although it is marked as [Obsolete]

The async functionality is based on an async version of the IResourceResolver interface, called IAsyncResourceResolver. Existing resolvers may chose to implement one or both of these. There is no expectation that a an async resolver will also implement sync behaviour, although the resolvers provided by the API will support both.

All components in the API that depend on resolvers will eventually accept both interfaces as a dependency. We introduced the (empty) base interface ISyncOrAsyncResolver for dependencies in, so on constructors and settable properties on setting objects (e.g. ValueSetExpanderSettings). In contrast, when these dependencies are exposed as properties (out), we include both an sync and async version of the property, where the sync version will return null if an (exclusively) async resolver was provided as an in dependency. This design ensures existing code will continue to compile.

Changes to the POCO classes

  • SimpleQuantity and MoneyQuantity (R4+) have been removed from the POCO model. These types were actually just profiles on types and were a constant source of confusion and bugs. E.g. since these types are profiles, they are not represented in the suffix of the name of an element with a choice type (e.g. Observation.value[x]), hence when round-tripping data, an in-memory SimpleQuantity instance would be serialized to Observation.valueQuantity and then parsed as a Quantity, not a SimpleQuantity.
  • The ResourceType member has been removed. This member relied on the ResourceType enumeration, which cannot be customized by the user with new resource types. You can use the new extension method bool TryDeriveResourceType(this Resource r, out ResourceType rt) instead.
  • The R4 Dosage, ElementDefinition and Timing types no longer derive from BackboneElement, but from BackboneType (as in R5).
  • We cleaned out numerous spurious interface declarations on subclasses when those were already declared on the base classes. This should not be breaking changes, but might be visible in some very specialized reflection scenarios.
  • All primitive datatype POCO's were made consistent again: all of them now have DebugDisplayAttributes and declare the Serializable and DataContract attribute.
  • The Meta.profile property (used in any Resource) has changed type from canonical to uri (in R4). This is done to be able to reuse the Meta type across the different versions of FHIR. This change is only visible in the POCO class and has no influence on the serialization or the type metadata, which will reflect the correct type for each FHIR version.
  • VerificationResult.status has been renamed to VerificationResult.VerificationResultStatus. status is the official name assigned by the committee, but it's not very unique (which is painful, since this is the name of a C# enum) and also not CLS-compliant (since it uses a lower-case letter).
  • To align the POCOs across all version with the normative R5 type model, the classes DataType, PrimitiveType and BackboneType have been introduced:
    • All datatypes (and primitives) are now subclasses of DataType rather than Element.
    • Choice elements (e.g. Extension.value[x]) are now of type DataType rather than Element (a non-breaking change, since DataType is a direct subclass of Element.
    • The extension method Value(this ElementDefinition ed, DataType fix=null, DataType pattern=null) has been updated to take DataType as parameters instead of Element.
    • The constructor Extension(string url, DataType value) now takes a DataType instead of an Element
    • The extension method AddExtension(this IExtendable extendable, string uri, DataType value, bool isModifier=false) now takes a DataType instead of an Element.
    • The extension method SetExtension(this IExtendable extendable, string uri, DataType value, bool isModifier=false) now takes a DataType instead of an Element.
    • Type Primitive has been renamed to PrimitiveType.
    • Since datatypes with modifier extensions and complex components can now be distinguished based on their inheritance hierarchy, it is no longer necessary to keep the IBackboneElement or the NamedBackboneElement parameter to the [FhirType] attribute around and they have been removed. The corresponding properties on ClassMapping and PropertyMapping have also been removed.
  • The abstract class PrimitiveType<T> had no members beyond those inherited from PrimitiveType and thus had no real use. It has been removed.
  • Those primitives that used a .NET string or int implemented an interface IStringValue resp. INullableIntegerValue instead of IValue<string> and INullableValue<int> - which would have been more consistent with the other primitives. This has been corrected by removing IStringValue and INullableIntegerValue. One can simply match on IValue<T> or INullableValue<T> instead.
  • The POCO classes representing resources no longer contain functionality for working with constraints. This means static members representing the ConstraintComponents like:
  public static ElementDefinition.ConstraintComponent Patient_PAT_1 = new ElementDefinition.ConstraintComponent()

and the associated functions such as AddDefaultConstraints() and ValidateInvariants() have been removed. These validation functions were incomplete and full validation cannot reliably be done based on the information available in the POCO model. Instead, use the Hl7.Fhir.Validation.Validator class instead. See the examples at https://github.com/FirelyTeam/Furore.Fhir.ValidationDemo on how to work with these classes.

Changes to the POCO metadata attributes

  • The type name for nested types (like the Patient.contact backbone component) has been made unique: the name of the resource is now used as a prefix, separated by '#'. The name is generated from the http://hl7.org/fhir/StructureDefinition/structuredefinition-explicit-type-name extension is available, otherwise the lasts part of the element's path is used. Note: the generated property TypeName has been changed accordingly.
  • The ModelInspector class has been rewritten: the constructor now requires a fhirVersion to use when inspecting the generated classes. Also, the interface has been simplified. Existing functions for getting mappings specifically for resource names have been removed and integrated in two generic functions for retrieving mappings (by name and by type).
  • Only the properties marked with FhirElementAttribute will be inspected for FHIR metadata, it is no longer necessary to put a NotMappedAttribute on all non-fhir properties in a class.
  • In Elements with a choice of types which included the now-removed SimpleQuantity or MoneyQuantity, the AllowedTypes attribute will contain Quantity.
  • If the type of an element is open (e.g. Extension.value[x]), PropertyMapping.FhirType will now only contain the type DataType (instead of all allowed types for an open element).
  • Since the FHIR spec has no formal notion of "open" elements (you cannot derive this faithfully from the StructureDefinition), the IsOpen attribute is no longer generated and removed from AllowedTypeAttribute. We introduced this in 1.8 - but only on Extension.value[x] to accommodate moving Extension to common, but this cannot faithfully be generated for other types. It has been removed from PropertyMapping as well.
  • Many metadata attribute may now be specified more than once. This makes sense if you also specify the new Since parameter, which is a string containing the semver version of the FHIR version for which you want to get the metadata.
  • ClassMapping.IsAbstract has been removed, use NativeType.IsAbstract instead.
  • ClassMapping.Profile is no longer supported - its predicted use never materialized and no support for profile-based generated POCOs is present other parts of the API
  • The FhirTypeAttribute now requires a name, default names (derived from the type) are no longer supported.
  • ClassMapping.IsBackbone, ClassMapping.IsNamedBackboneElement have been removed and replaced by a single IsNestedType boolean. The corresponding parameter to the [FhirType] attribute has also been renamed. This attribute is only true for types that represent complex nested (backbone)elements in a FHIR resource or datatype. Use the new R5 type hierarchy (specifically DataType and BackboneType) to identify extensible datatypes.
  • ClassMapping.FindMappedElementByName() did not only lookup elements by their actual name but also by the name of the XML element (which may be suffixed for choice types). This secondary function lead to confusion and frankly was a design mistake in the first place. It has been removed, just like the supporting methods PropertyMapping.MatchesSuffixedName() and PropertyMapping.GetChoiceSuffixFromName().
  • The argument TypeRedirect on the [FhirElement] attribute has been promoted to a separate [DeclaredType] attribute. This makes it easier to generate properties in common datatypes for which the FHIR-type of an element has changed from version to version.

Changes to the FhirPath engine

The FhirPath engine is upgraded to support FhirPath normative. The normative version of FP includes some incompatibilities, which include:

  • A new date type has been introduced, and the literals have been enhanced to make the distinction clear. In many cases this causes partial date literals (without a time) to be interpreted as date not dateTime. You may fix this by explicitly suffixing a partial dateTime with a 'T' (so 2015-04T is a dateTime, and 2015-04 is a date).
  • Using double quotes for identifiers has been obsoleted and replaced by single backticks. Though the engine will accept both you should replace your FP statements accordingly.
  • The today() functions now returns a date instead of a dateTime().
  • As a result, ITypedElement will now use Hl7.Fhir.ElementModel.Types.PartialDate for FHIR primitive 'date' instead of Hl7.Fhir.ElementModel.Types.PartialDateTime.
  • Equality, equivalence and ordering for (partial) datetimes has been tweaked to better deal with difference in precision (see http://hl7.org/fhirpath/#datetime-equality)
  • The ofType() function no longer takes a string, but expects a type-name literal, e.g. ofType('Quantity') becomes ofType(Quantity). Since namespaces for types were introduced, this would more explicitly now be written as ofType(FHIR.Quantity).
  • The new FP engine fixes a bug which allowed you to compare booleans to string constants, this is no longer allowed.
  • The properties Container and RootContainer on the EvaluationContext have been renamed to Resource and RootResource respectively, to align with the symbols (%resource and %rootResource) used within FhirPath.
  • The extension functions on ITypedElement for conversion (like ToDecimal, ToStringRepresentation) have been moved to the ICqlConvertible interface and are now explicitly implemented by the new system types deriving from Any. If you'd like to call them, use Any.Convert(ite.Value) to convert the value of an ITypedElement primitive to an Any, cast the Any to ICqlConvertible, then call the conversion functions via that interface.

This list is by no means exhaustive - though most changes are subtle enough that the new normative engine will still be able to execute the pre-normative FhirPath statements from older FHIR specifications.

Changes to the FHIR type/metadata system

The refresh of the FhirPath engine and work on mapping functionality has made it clear that parts of the API depend too much on hard-wired knowledge of the FHIR types. Although this seems natural for a FHIR API, the mapping engine and FhirPath are actually model-agnostic, so we have started to locate these dependencies. This will cause a few breaking changes:

  • ElementNode.ForPrimitive() is now more strict: it will only accept values that can be represented as a primitive value in the Value property of ITypedElement.
  • ElementModel.Add() allows adding a child to a node, without specifying the actual type of the child, since this can be derived from the type information present in the StructureDefinition. This was even true for choice types, where we would try to derive the desired FHIR type from the .NET primitive value passed in as the value for the child. Not only was this ambiguous (there's no way to derive that a string should have been turned into a FHIR.uri for example), but also tied the implementation of ElementNode to FHIR - this would work incorrectly for non-FHIR models. Therefore, this functionality has been removed and you will now get an exception IF the element has a choice of types and you have not indicated the actual type to the Add().
  • The Hl7.Fhir.FhirPath.TypeInfo class has been removed and replaced by the Hl7.Fhir.Language.TypeSpecifier class (to align with CQL/ELM terminology). It's functionality has been divided into working with the mapping between C# types and so called System types (which are in TypeSpecifier) and working with primitive instance data of the System types (which are subclasses of Hl7.Fhir.ElementModel.Types.Any and other classes in that namespace.
  • The Hl7.Fhir.Support.Model.Primitives.Primitives class has been removed and its methods moved to Hl7.Fhir.Language.TypeSpecifier. Since Primitives was the only class that was present in the Hl7.Fhir.Support.Model namespace, this namespace is now gone, and you will have to update your using statements accordingly.
  • The Hl7.Fhir.ElementModel.Types namespace now includes types representing the CQL/FhirPath primitives (like String, Boolean and Decimal) Importing both the standard System namespace from .NET and this namespace, will cause references to the System type to become ambiguous. Either explicitly qualify the references, or change the references to the built-in lowercase type keywords string, bool and decimal.

Changes to the FHIR Client

The previous FHIR client was still using Microsoft's HttpWebRequest and HttpWebResponse as a mechanism. It now has had been upgraded and uses Microsoft's newer HttpClient. Note: The older client based on HttpWebRequest is still available, but is now called LegacyFhirClient and marked as obsolete. However, it is strongly recommended to use the new FhirClient implementation.

  • The use of HttpClient instead of HttpWebRequest/Response might slightly change the client behaviour slightly.
  • Added new optional parameter FhirClientSettings. This replaces the public members PreferredFormat, VerifyFhirVersion, ReturnFullResource, TimeOut,and UseFormatParame, which are now marked as obsolete.
  • Removed public members OnBeforeRequest and OnAfterResponse, this are replaced by the optional parameter messageHandler. This option allows for more flexibility by letting the user add a custom "HttpMessageHandler" to the FHIR Client. We have added a new HttpClientEventHandler class which implements HttpMessageHandler, and has public members OnBeforeRequest and OnAfterResponse to make thing a bit easier implementing old behaviour.
  • Removed public members LastResponseand LastRequest. These members were obsolete in the previous version of the client since the values were already disposed by the time they arrived at the client. So no point having them around.
  • In SearchParams public members Include and RevInclude have been changed from List<string> to List<(string, IncludeModifier)> to be able to add modifiers, such as "iterate" to the include and reverse include search parameters while using search on the FhirClient. If you don't want to add any modifiers, use IncludeModifier.None for the default behavior.
  • BuildContentType has been changed from BuildContentType(ResourceFormat format, bool forBundle) to BuildContentType(ResourceFormat format, string fhirVersion). forBundle has been removed, since it wasn't used anymore, fhirVersion has been added to add the fhirversion to the content type and accept-header.

Other breaking changes

  • PartialDateTime (now: DateTime in the Hl7.Fhir.ElementModel.Types namespace) no longer has a ToUniversalTime() method, since there is no reasonable default behaviour for this function when the partial does not have a timezone offset specified. Instead, use ToDateTimeOffset(), which will enable the caller to pass in a default offset to use for the conversion. Then call ToUniversalTime().
  • To stay aligned with the Quantity type defined in CQL, The Hl7.Fhir.ElementModel.Types.Quantity class no longer allows you to have a system other than UCUM.
  • For FindStructureDefinition(this IResourceResolver resolver, string uri) and FindExtensionDefinition(this IResourceResolver resolver, string uri) the final requireSnapshot parameter has been removed. It promoted behaviour where an application would conclude a structuredefinition was not resolvable, even if it was just missing snapshot. The developer should explicitly check for the presence of a snapshot and report back to the user if a snapshot is missing while required for further processing.
  • The extension method IsValidTypeProfile(this IResourceResolver resolver, string type, StructureDefinition profile) has been removed. This method was mistakenly marked as public in a previous version.
  • Properties Settings and Retrieve have been removed the Cache<T> class: this suggested these properties could be modified, while in fact internally changes to these properties would not have any effect.
  • BaseFhirSerializer has been removed, it provided no public methods anymore - they all have been moved to extension methods.
  • We had inadvertently introduced a Hl7.Fhir.Support.Utility namespace, which should have been Hl7.Fhir.Utility. The classes in this namespace (which were luckily just a few minor ones amongst which AnnotationList) have been moved.
  • The functions ToPartialDateTime() on the POCO's Date.cs, Instant.cs and FhirDateTime.cs returned types from the Hl7.Fhir.ElementModel.Types namespace, which blended the POCO/non-POCO (ITypedElement) worlds. To avoid this confusion and keep the interfaces consistent, these functions have been removed from these classes.
  • The constructor of LocalTerminologyService expects now a IAsyncResourceResolver instead of IResourceResolver. When you still work with an IResourceResolver you can use the extension method AsAsync() to convert you resourceresolver to asynchronous resolver.
Clone this wiki locally