Stream: fhir/infrastructure-wg
Topic: Interfaces and ancestors and patterns
Josh Mandel (Feb 28 2022 at 22:09):
Right now DomainResource is a "Resource" and MetadataResource is an "Interface".
-
Is it accurate to say that both are "ancestors" of a resource like http://build.fhir.org/branches/subscriptions2022/subscriptiontopic.html#resource? (I think this is a "yes").
-
Per http://build.fhir.org/uml.html#interfaces, "Any classes the implement the interface redeclare any attributes and associations." Is it expected that tooling will do this automatically? For the SuscriptionTopic example above, the tooling says the following are "defined in Ancestors", even though the generated snapshots do not include elements like
MetadataResource.editor
. Is this accurate, and if so: would it be valid or invalid to include aneditor
on a SubscriptionTopic? (I think @Grahame Grieve's answers are: "yes accurate" and "invalid to include," but it's confusing.)
-
Per http://build.fhir.org/uml.html#interfaces, "In addition, the resource might not implement the attribute at all - this is equivalent to constraining the attribute to a cardinality of 0..0 - not used with this resource". For the SubscriptionTopic example above, should the absence of a property from the interface be understood as a choice to "not implement the attribute," and if so should the tooling suppress the claim that the attribute exists?
-
Are there any guidelines for when to "just inherit all the properties" vs selectively excluding some when inheriting from an interface?
Grahame Grieve (Feb 28 2022 at 22:39):
- yes
- no, it's not automatic, since the resource can choose not to. And it's technically valid to say that the element is defined by the ancestor, such as editor, even though it's not redeclared in the implementation, because it hasn't gone away, it's just implicitly constrained to 0..0. But that's certainly misleading. As for 'invalid to include'... not sure what you're asking there. Of course the definition could include it if it wanted - that wouldn't be invalid
- yes that's how to understand it. "the claim that the attribute exists"... which particular claim. Because it kind of does.
- no there's no guidelines, but per the discussion we just had on the call, I'd rather things inherit all the properties and not exclude something. Really, there should be a good reason to exclude things. But I'm not convinced that the three properties that SubscriptionTopic picks up from MetadataResource are actually quite the same properties, and maybe it should just be a canonical resource
Gino Canessa (Feb 28 2022 at 23:07):
But that is quite confusing, isn't it? The specification page makes no distinction between the inherited definition for Resource.id
and the non-inherited 'definition' of CanonicalResource.version
, they are just listed together by element name.
And I think that's my biggest issue with the current model - a reader has no idea if an element is inherited or not because there is no differentiation between a base definition and an interface.
Josh Mandel (Feb 28 2022 at 23:44):
Agreed, saying that id and editor are both "defined in Ancestor" is problematic if the former really exists and can be included in instance data and the latter really doesn't exist and cannot be included in instance data.
Gino Canessa (Mar 01 2022 at 16:24):
I was writing one of my patented novels in Zulip ;-), but in an attempt to "do better", I think I have a short version!
Assuming:
- Interface elements are not inherited by children
- Children are allowed to redefine elements that are defined in an interface parent
- Interfaces inherit from actual definitions and those elements are inherited normally
Then:
- What is the purpose of 'interface' definitions?
I guess I am wondering what I am missing - as far as I can tell, this is adding something to R5 that is incorrect/invalid for data-structures (in a CS context), adds complexity for readers, and the sum total of use is: if you want something defined in it, you have to redefine it anyway.
Josh Mandel (Mar 01 2022 at 20:39):
Looking at http://build.fhir.org/uml.html#abstract and the hierarchy here, the page is listed as "Normative" but first appeared in late 2019. The commits don't reference Jira tickets and Jira doesn't seem to mention uml.html
anywhere (searching by string or referenced page).
@Grahame Grieve mentioned on our call yesterday that he wasn't thrilled with the design but couldn't get consensus on a better design. Can you help me understand where this conversation happened?
Josh Mandel (Mar 01 2022 at 20:51):
FHIR-32336 is the closest I see to discussion, but given the number of questions about how "interface" inheritance works, this is hard to swallow. The actual details here not been published, and outstanding technical corrections like FHIR-25568 mean that implementers can't well have tried/used it to provide feedback.
Josh Mandel (Mar 01 2022 at 20:51):
(Thanks to @Gino Canessa for about 90 minutes of deep dive on this just now -- we understand a lot more than yesterday but are still quite confused.)
Gino Canessa (Mar 07 2022 at 20:57):
Here's my list of thoughts, as it was before discussion today (March 7) on FHIR-I. Some of the items we have discussed partially, but figured it was easier to post as it was than revise.
Technical issues:
-
Inheriting part of a definition is confusing - search parameters (for example) are inherited from interfaces while element definitions are not. This is very error-prone and makes the FHIR definitions less generically useful.
-
Example: the
Resource Content
sections do not distinguish between 'real' and 'undefined'Elements defined in Ancestors
(e.g.,
CodeSystem). -
Related: what does it mean to have a search parameter or invariant defined for an element that is not defined on a resource?
- Not knowing about this extension to StructureDefinition results in incorrect processing - if this continues to exist it should at least be a modifier extension. That said, if this mechanism is being added and is normative it should be an element on the resource and not an extension.
-
Process issues:
- While FHIR-32336 explains the theory behind interfaces being normative, the creation of a new class of artifacts that jump straight to normative without feedback does not feel correct.
- We have not been able to find discussion/voting/etc. that led to these changes.
Theory-based issues:
- Mixing classes of ancestors in an inheritance tree is unfamiliar
- The word
interface
has meaning in computer science generally and UML specifically (which is used to represent these models). The current usage does not meet its requirements.
Gino Canessa (Mar 07 2022 at 20:58):
Other notes that I just remembered and did not make it in:
- The build has
<<I>>
and<<A>>
tags wrong (e.g.,DomainResource
shows an<<I>>
Lloyd McKenzie (Mar 07 2022 at 23:51):
The base use-case is that we have a set of resources that will have 'mostly' the same data elements, but there will be (and needs to be) variability. Some of the concepts simply don't make sense in certain contexts and there will also be variations in terms of what's core vs. extension. We want systems to be able to write software that handles the "typically shared" portions of these resources consistently, primarily for 'read'. ('Write' is less strong a use-case. That's possible, but would have to handle situations where certain resources won't allow writing certain data elements.)
My personal opinion is that class inheritance is not a great way to expose that behavior. Even where the elements do appear, there can be variations in comments, definitions, etc. 'interface' is a slightly better way, but it needs to be though of as "we can/will expose getters/setters for these elements, but behind the scenes they'll work differently for different resources".
If we can't buy off on that, then we're left with a different 'flavor' of pattern - one where we'll generate (and use) interfaces in the code, but won't have inheritance, nor will we add data elements to every resource just for the sake of complying with the pattern.
Josh Mandel (Mar 08 2022 at 18:25):
Such re-declarations will be consistent with the interface but may be a subset of the value domain e.g. the cardinality of an attribute may be 0..* on the interface, and 0..1 on a particular resource
Has this ever happened in real life? If it did, would the resource represent, say, "author" with an array of 0..1 ContactDetails, or just a single element?
Lloyd McKenzie (Mar 08 2022 at 19:18):
In patterns, yes. In this 'tighter' situation, I think the only constraint is from 0..x to 0..0. I'm not aware of a x..* going to x..1
Josh Mandel (Mar 08 2022 at 19:20):
Okay, thanks -- if that can be formalized it prevents a whole class of instance incompatibility with interfaces.
Gino Canessa (Mar 16 2022 at 20:40):
Apologies for the novel, but I wanted to spend some time exploring with concrete outputs... so I did =). I created a gist that has a C# file (.Net 6) and the matching output. I used C# because I like the tooling - I tried to make examples that port well into TypeScript, Java, and C++. I think they also port well to Swift and Objective-C, but am a bit rusty. I also tried to make each style as 'good' as possible over several iterations. I kept the 'best' I could with 'interface in inheritance' but kept two versions of external definitions because it highlights a definitional problem.
Disclaimers/Assumptions/Context:
- I attempted to give each model 'best effort', but know that I am biased on the topic. I posted all the source so that someone else can jump in with a better approach.
- Coding each 'style' (in or out of inheritance tree) I used what would be the natural language constructs for doing so. I made sure to keep to single inheritance in the class hierarchy, and used language interfaces for external interface definitions.
- I am using C#, with a focus on strongly-typed languages.
- I am using classes that differentiate scalar value types from array value types.
- I defined very simplistic models to illustrate points. I used three 'elements' from a root 'metadata':
Url
is 0..1 in metadata and questionnaireAuthor
is 0..* in metadata, prohibited in questionnaireProblematic
is 0..* in metadata, 0..1 in questionnaire (this is defined valid, so I wanted to include it)
- I went through some basic operations of creating objects and getting or setting elements. In order to show the differences in where they throw exceptions, I only put try blocks around things that throw in the example.
- Thoughts are based the experience of tinkering with this, etc.
So, my attempt at collecting thoughts...
-- 'Profiling' in resource definitions:
- What is the JSON serialization for the
Questionnaire.problematic
element? It needs to either be an array or a value, and I have no idea which is 'more correct'- If it should be an array, we cannot discover that from the snapshot today - we need to either add more information or we need to parse the interface and apply the logic ourselves.
- If it should be a scalar, developers will need to discern between scalar types and array types in an odd way.
- Either way, if we keep this feature we lose some of the 'magic' of FHIR in how easy it is to 'just use'.
- edit: note this can be seen in the
...Strict
vs. the...Strict2
models - I did not do it twice in the 'in tree' model since it ends up so similar and this one was less code.
-- Interfaces in the class hierarchy:
- I could not figure out a way to have the language enforce not having the Author property on Questionnaire when casted as a parent Metadata object. The language does not have the ability to prevent setting a value on a parent casted as that type.
- This is consistent with OO in general - a descendent class cannot influence ancestors.
- This results in the ability to generate Questionnaire objects with Authors.
- It can be caught by the parent class, but requires some serious and painful inversion.
- It can be caught with a custom serializer, but adds a lot of overhead (edit: and relies on developers only using the provided serializers/parsers).
-- General thoughts
- Both models cause: runtime errors, invalid data, or implicitly 'swallowed' data - this must be handled at the application layer regardless of which structure is used (though it is more or less obvious depending on the route taken).
- This makes sense, since we are defining contracts that are not really contracts - the cost of modeling elements that may or may not exist is handling that complexity at runtime.
- Having spent a fair bit of time on this, I am confident I can generate either form of code based on either modeling structure.
- Including interfaces in the modeling inheritance means I have to deal with the strangeness of implied elements, etc. in order to use any model that has one (with the implication that more of these will appear in the future). Leaving it out means that I only add that complexity if I want to use interfaces.
- complexity here means both code complexity and runtime cycles
Gino Canessa (Mar 16 2022 at 21:22):
Been asked so one more note:
- Common
PartialResource
: base class to root inheritance for all models
- Interfaces in inheritance
PartialMetadataResource
: class with the above noted properties for experimentation, child ofPartialResource
PartialQuestionnaireInherit
: child ofPartialMetadataResource
to show a 'final' class that handles interface properties
- Interfaces NOT in inheritance (using language interface constructs)
IPartialMetadata
: Interface exposing same properties for experimentation, for brevity it does not include what would beResource
elements, etc.PartialQuestionnaireStrict
: child ofPartialResource
, implements interfaceIPartialMetadata
and exposesProblematic
property as a single string and serializes it as one.PartialQuestionnaireStrict2
: child ofPartialResource
, implements interfaceIPartialMetadata
and exposesProblematic
property as an array of strings.
Josh Mandel (Mar 28 2022 at 16:53):
@Lloyd McKenzie has written https://docs.google.com/document/d/1YrdzZoChWCBCClynZ_5jLTXXl0_l0MdL2BdUnSWumh4 to help drive discussion on today's call
Last updated: Apr 12 2022 at 19:14 UTC