# Generating GraphQL from OPTs **Category:** [Platform](https://discourse.openehr.org/c/platform-implem/7) **Created:** 2021-12-05 22:38 UTC **Views:** 1421 **Replies:** 9 **URL:** https://discourse.openehr.org/t/generating-graphql-from-opts/2144 --- ## Post #1 by @borut.jures There was a discussion on how to simplify working with openEHR in the [Separating Models from Implementation](https://discourse.openehr.org/t/separating-models-from-implementation/2093). I created a generator for GraphQL to find out if this is a viable approach. I cannot upload ZIP files to Discourse so the [download link and source code](https://neoehr.com/openehr/graphql) are on my site. - There is a great Java example in the "java-apollo-android" folder. - Also "graphql.schema.json" for a Blood Pressure OPT => GraphQL schema in JSON. - And the "schema.graphql". Feedback is welcomed. p.s. EHRbase also has a generator for Java objects: [SDK](https://github.com/ehrbase/openEHR_SDK) --- ## Post #2 by @erik.sundvall Very interesting @borut.jures ! I am no GraphQL expert, but have asked experienced colleagues to have a closer look. I wonder about some initial things: 1. The intended/possible context of GraphQL query/mutation execution etc. A single field or subtreee of a versioned openEHR COMPOSITION is not modifiable without making and commiting a new version of the entire COMPOSITION (logically via a CONTRIBUTION). Are you considering using this in a "contribution builder" of some kind e.g. as described in: https://discourse.openehr.org/t/contribution-builder-supporting-collaborative-multi-user-multi-device-editing-graphql-operational-transformation/2071 ? (If so, we could load an existing version of a COMPOSITION (or several different compositions etc) into the builder, query and manipulate it (them) and then when we feel finished tell the builder to committ the changed objects via a new CONTRIBUTION. 2. The leaf data types, like different instances of DV_QUANTITY at different paths in listing 1 below seem to repeat the basic structure of DV_QUANTITY under new names `BloodPressureDataHistoryEventAnyEventDataBloodPressureItemDiastolicValueDvQuantity` has the same content as the corresponding `BloodPressureDataHistoryEventAnyEventDataBloodPressureItemDiastolicValueDvQuantity` This touches a bit upon what @Seref wrote about repeated data types in self contained worlds in https://discourse.openehr.org/t/separating-models-from-implementation/2093/79 * If the model/subtree is an *unconstrained* type (like DV_QUANTITY) I'd guess having and repeating a generic corresponding structure would be more readable * If the model/subtree is a *constrained* type like the systolic and diastolic subtrees actually are in [the archetype](https://ckm.openehr.org/ckm/archetypes/1013.1.3574) (constrained min/max values, chosen units, different terminology bindings etc) then specially named repetitions would be understandable. But maybe that is something you aim for later? ![image|643x150](upload://9brWXexnDFM73orsAjgQ8WxBno7.png) Since I am a GraphQL noob I don't know if the **[graphql-constraints-spec]([https://](https://github.com/APIs-guru/graphql-constraints-spec/blob/master/README.md))** is used in tools etc or if it is currently just a seemingly useful suggestion. Or maybe constraints are ususally taken care of by object definitions in client implementations (e.g. constraints built in to a generated TypeScript class for BloodPressure)? - The openEHR backend (CDR) will enfoce constraint checks later anyway... The structure in Listing 2 below is very readable by the way. # Listing 1 - graphql\schema.graphql ``` type BloodPressureDataHistoryEventAnyEventDataBloodPressureItem { # Peak systemic arterial blood pressure - measured in systolic or contraction phase of the heart cycle. systolic: BloodPressureDataHistoryEventAnyEventDataBloodPressureItemSystolic # Minimum systemic arterial blood pressure - measured in the diastolic or relaxation phase of the heart cycle. diastolic: BloodPressureDataHistoryEventAnyEventDataBloodPressureItemDiastolic # ...example snippet abbreviated here... } # The leaf variant of `ITEM`, to which a `DATA_VALUE` instance is attached. # RM type: ELEMENT type BloodPressureDataHistoryEventAnyEventDataBloodPressureItemSystolic { value: BloodPressureDataHistoryEventAnyEventDataBloodPressureItemSystolicValue } type BloodPressureDataHistoryEventAnyEventDataBloodPressureItemSystolicValue { dvQuantity: BloodPressureDataHistoryEventAnyEventDataBloodPressureItemSystolicValueDvQuantity } # ...example snippet abbreviated here... # RM type: DV_QUANTITY type BloodPressureDataHistoryEventAnyEventDataBloodPressureItemDiastolicValueDvQuantity { property: TerminologyCode magnitude: Float units: String precision: Int } # ...example snippet abbreviated here... # RM type: DV_QUANTITY type BloodPressureDataHistoryEventAnyEventDataBloodPressureItemSystolicValueDvQuantity { property: TerminologyCode magnitude: Float units: String precision: Int } ``` # Listing 2 - structure from generated\typescript-apollo-angular.ts and many of the other example files ``` bloodPressure { data { history { events { a24HourAverage { data { itemTree { items { systolic { value { dvQuantity { property { terminologyId codeString terminologyVersion } magnitude units precision } } # ...example snippet abbreviated here... --- ## Post #3 by @borut.jures [quote="erik.sundvall, post:2, topic:2144"] Are you considering using this in a “contribution builder” of some kind [/quote] The requirements in the linked thread are clear - make it as simple as possible for the developers to use an openEHR CDR. I'm still putting together my thoughts but I guess there will be a GraphQL middleware server that will sit on top of openEHR CDR. It will be a proxy between the CDR and applications that need to interact with it. Is there a better name for this (middleware/proxy)? Application developers would get a library (in their programming language) that they would use to store/retrieve documents from the CDR using GraphQL (or REST). They would not need to know anything about the openEHR (although I add all the possible documentation for the interested ones to the generated schema/code). [quote="erik.sundvall, post:2, topic:2144"] Are you considering using this in a “contribution builder” of some kind [/quote] Thank you for the notes about unconstrained types (like DV_QUANTITY). The generator already has few rules to decide whether to create a specific type or use a standard one that is the same for every property of that type. I have a TODO to check whether DV_QUANTITY is really the same for every property. It doesn't satisfy the current rules in the generator as it specifies which DV_QUANTITY properties are used. In the case of Blood pressure OPT it always uses the same 4 properties but there are more in the DV_QUANTITY. I'll do some more research about this. Any help in figuring this out is appreciated. --- ## Post #4 by @erik.sundvall [quote="borut.jures, post:3, topic:2144"] a GraphQL middleware server that will sit on top of openEHR CDR. It will be a proxy between the CDR and applications that need to interact with it. Is there a better name for this (middleware/proxy)? [/quote] My suggestion 2013 was a limited but important missing "middleware" for building/editing CONTRIBUTIONs consisting of one or several COMPOSITIONs so I called it "**Contribution builder**" as described in the thread https://discourse.openehr.org/t/contribution-builder-supporting-collaborative-multi-user-multi-device-editing-graphql-operational-transformation/2071 In 2013 I did not think of providing simplified formats/paths or GraphQL (not published by then), but accessing the contribution builder via GraphQL would be one of the obvious alternatives that should be provided today. *And now, future speculations:* Another piece of, partly related, middleware could be an AQL result* parser that could be accessed via GraphQL. If it is combined with the contribution builder in smart ways I think that would cover most "simplifying" middelware needs. *) Perhaps a GraphQL-accessible AQL result parser is not very useful for all kinds of AQL query results, some are really simple enough already, but it would likely be useful at least for certain (stored?) queries that return either: * entire templated compositions (naturally containing "template_id" - [see class "ARCHETYPED"](https://specifications.openehr.org/releases/RM/latest/common.html#_archetyped_class)) or * lists of subtrees of content extracted from parts inside templated compositions. Perhaps one would need to include the template IDs (+path?) in a response column always named "template_id" - or have some other way to make life easier for the template-based model-translating result parser. --- ## Post #5 by @borut.jures The GraphQL schema was generated by my OPT generator. It is easy to generate code in any programming language. I just don't want to spend time on other languages if there is no interest in them. Since my main project uses Dart, I reused the GraphQL generator and generated models for the same blood pressure OPT. Compare the results: GraphQL: ![graphql-generated|493x500](upload://bVN72d93kMvQAJ8QUqQdKLL81Oy.png) Dart: ![dart-generated|468x500](upload://t6MuGY5U3LSY7vtLEX3NTkTbe2m.png) The generator currently outputs Dart code (its syntax is almost the same as Java and JavaScript). Dart tools can compile it to JavaScript ([dart2js](https://dart.dev/tools/dart2js), [dartdevc](https://dart.dev/tools/dartdevc)). It remains almost readable even after compiling. But the readability is not important. The important part is that the code can be run on Node.js or be called from other JavaScript code. Here is the generator code for GraphQL and Dart. Almost the same. It takes only these few methods and the generator can output code in TypeScript, C#, Kotlin,... GraphQL: ![graphql-generator|325x500](upload://zAamSeuMhuZ0QDR6KtjqsMQ0KTA.png) Dart: ![dart-generator|332x499](upload://1OxChxobOXiIiG6kA1JSF9Md7HZ.png) --- ## Post #6 by @borut.jures [quote="erik.sundvall, post:2, topic:2144"] The leaf data types, like different instances of DV_QUANTITY at different paths in listing 1 below seem to repeat the basic structure of DV_QUANTITY under new names [/quote] I managed to remove the repeating classes for leaf data value attributes. Now they are all in a single class and using the basic DV_QUANTITY type. The constraints are all in the same class too: ![Screenshot 2022-01-08 at 08.33.52|348x500](upload://nc7yEtGi4ljEZndNXCoK2s57XzK.png) --- ## Post #7 by @erik.sundvall Cool! And quite readable! So does the superclass `Element`(or some other ancestor) contain a validator algorithm that makes use of the Constraints? P.s. To make examples more realistic and complete (and possible to submit to an openEHR CDR) you could wrap the Blood pressure OBSERVATION in a COMPOSITION an add time an other mandatory fields (and skip pulse pressure + mean arterial pressure). Showing example code instantiating a BP measurement would also help people understand usage P.p.s. I have had a look at Dart, Flutter etc and believe there is definitely room for openEHR-related innovation/implementation in that space, e.g. for: - simple UX/forms in cross platform developed native apps for old low end phones/tablets (Devices that often have poor/old web view capabilities - as we have noticed in app access logs from patients/inhabitants of Region Stockholm) - openEHR data handling in embedded computing - performance hungry UX (visualisations etc) on "normal" hardware --- ## Post #8 by @erik.sundvall One more thought: Since you have automated transformations from BMM, OPT etc., you may some time in the future also want to autogenerate utility enumerations for things in the openEHR TERM specs https://specifications.openehr.org/releases/TERM/latest Then code would be even more readable and less prone to error by programmers, perhaps part of your code above could then be something like: `..property= TERM.property.Pressure` --- ## Post #9 by @borut.jures [quote="erik.sundvall, post:7, topic:2144"] To make examples more realistic and complete (and possible to submit to an openEHR CDR) you could wrap the Blood pressure OBSERVATION in a COMPOSITION an add time an other mandatory fields (and skip pulse pressure + mean arterial pressure). Showing example code instantiating a BP measurement would also help people understand usage [/quote] I've posted COMPOSITION for the Vital signs in another thread. It looks like this: ![Screenshot 2022-01-08 at 08.22.40|493x500](upload://iMFwdaQKgap4P9BphfXux2dgED8.png) ![Screenshot 2022-01-08 at 08.24.35|651x500](upload://l4Al15YEQKrGfy5JovsuyoDKqBm.png) I've also posted an example code for instantiating BP: ![Screenshot 2021-12-10 at 21.18.17|690x457](upload://g6OQQncQT6BqV8cuVbnodeVR8ey.png) [quote="erik.sundvall, post:8, topic:2144"] Then code would be even more readable and less prone to error by programmers, perhaps part of your code above could then be something like: `..property= TERM.property.Pressure` [/quote] I have all the terminologies generated as models too: ![Screenshot 2022-01-10 at 08.52.08|592x500, 75%](upload://nah7d4mos9roushHCXbDxeblBIb.png) In the above example CODE_PHRASE is used since that is what is expected by the RM. To help programmers understand the codes I added the terminology text in comments: ``` ..property = CodePhrase(TerminologyId('openehr'), '125') /* Pressure */ ``` I'll see if the generated terminology classes can be used. They will probably be used in validation. [quote="erik.sundvall, post:7, topic:2144"] So does the superclass `Element`(or some other ancestor) contain a validator algorithm that makes use of the Constraints? [/quote] I'll answer this in another post since parts of the validation rules are not in computable form yet: https://discourse.openehr.org/t/rm-invariants-in-a-computable-form/2250 --- ## Post #10 by @borut.jures [quote="erik.sundvall, post:7, topic:2144"] Cool! And quite readable! [/quote] In my quest for simplifications I neglected [specifications](https://specifications.openehr.org/releases/RM/latest/data_structures.html). I've spent the past few weeks massaging attributes into condensed form likable to human eyes. I ran into all kinds of problems. I should have taken these as an indicator of going down the wrong path. Yesterday I finally succeeded in generating readable data classes :partying_face: And today I'll delete them and return to separate classes for the leaf types :boom: The question I asked myself: Is SDK meant to be used by humans or computers (e.g. generators)? The clear answer is the latter. Even a simple OPT has too many classes for a human to program into a web form or as an integration interface. --- **Canonical:** https://discourse.openehr.org/t/generating-graphql-from-opts/2144 **Original content:** https://discourse.openehr.org/t/generating-graphql-from-opts/2144