FHIR Chat · Server side triggers · implementers

Stream: implementers

Topic: Server side triggers


view this post on Zulip Grahame Grieve (Dec 21 2017 at 03:03):

One of the things I'd like to add to my server between now and the new orleans is the ability to set up server side trigger processing on my server. For now, the range of the things that I anticipate can be adequately captured by these 2 use cases:

  • I want someone to be able configure my server to reject an update to a resource if it would create a duplicate identifier on one of the identifiers in the resource
  • I want someone to be able to be able to add a Patient to a List if either an Encounter or an observation meets certain criteria

As far as options, I could approach this in one of a few ways:

  • I could use the CDS stuff in FHIR - EventDefinition and CQL mainly - so that you could register a trigger that either rejects an update or changes some other resource - but I don't think that CQL has bindings for that
  • I could extend graphQL / GraphDefinition / FHIRPath internally so that you'd write some predicate or perform some operation - but I think that the second case is really something beyond them
  • I could define an API that other servers could implement, some variant of cds-hooks maybe - and then allow users to configure the server (by FHIR or some other way) so that custom logic on the service makes these decisions

Any other options? Just to be clear: I'm prototyping here, looking for understanding what approach has legs for these kinds of problems within the FHIR eco-system.

@Bryn Rhodes @Josh Mandel @Ewout Kramer @Christiaan Knaap your opinions request (and everyone else, of course)

view this post on Zulip Josh Mandel (Dec 21 2017 at 03:52):

Is the idea that some up-front configuration step happens, and then subsequent requests will behave differently than they otherwise would have? For example 1, I guess it would change the behavior of future POST operations; for example 2, what's the expectedly API call to perform the "add patient"? I'm having trouble understanding this business about Lists.

view this post on Zulip Grahame Grieve (Dec 21 2017 at 04:00):

well, you might say 'any patient that is admitted to a ward goes onto the list of patients I manage' - this is how most systems work...\

view this post on Zulip Grahame Grieve (Dec 21 2017 at 04:00):

or 'any patient with a lab K over 6.5 goes onto the list of patients for the clinical chemist to review'

view this post on Zulip Bryn Rhodes (Dec 21 2017 at 04:01):

For example 1, a PlanDefinition could describe this, the trigger would attach to the add of a resource, the action condition would check for the existing identifier, and the action sentence would use a "remove" to remove the newly created resource. It's a bit of a stretch, and I can see wanting to add better action types to support that type of thing.

view this post on Zulip Grahame Grieve (Dec 21 2017 at 04:01):

and yes, subsequent requests happen differently

view this post on Zulip Bryn Rhodes (Dec 21 2017 at 04:02):

Same for example 2, a PlanDefinition with a trigger on the add, the action.condition specifying the criteria, and an action that, say, updates the List resource.

view this post on Zulip Josh Mandel (Dec 21 2017 at 04:10):

My default approaches here would be 1) register a JS snippet to be run when a POST occurs. The snippet can call some limited library of functions to modify the system as needed, or 2) Register an external subscription that triggers when a result arrives, and let the external system call back to make modifications as needed

view this post on Zulip Grahame Grieve (Dec 21 2017 at 04:12):

so 1/ is not interoperable unless we define a JS api - and still technically hard
2/ is hard because of network dependencies inside your transaction handling; particularly the idea that you can escalate the transaction scope

view this post on Zulip Grahame Grieve (Dec 21 2017 at 04:16):

I want this to be interoperable - so that you can define trigger behaviour consistently across servers. Do other people think that makes sense?

view this post on Zulip Bryn Rhodes (Dec 21 2017 at 04:16):

I do :)

view this post on Zulip Grahame Grieve (Dec 21 2017 at 11:38):

I suppose I should look around and find how this was solved elsewhere... but I don't know where to lok

view this post on Zulip John Moehrke (Dec 21 2017 at 13:12):

Seems similar to the kinds of rules that go into a Privacy Consent. I think you are trying to solve it in a broader way, which I applaud. The problem in the Privacy Consent space is not well done. Yes we have a FHIR Consent resource, but it fails when the rules become deeper than first order complexity. The other problem is that the Privacy Consents have more implementers using OAuth, which doesn't fit your use-case well (nor privacy consent beyond first order complexity)... So I don't have much for you, but do want these use-cases to be considered as possibly needing to be included in your scope.

view this post on Zulip James Agnew (Dec 21 2017 at 18:17):

This is all stuff that people generally do in HAPI using interceptors.. not really a reusable solution across platforms, but it's definitely stuff that is commonly done.

view this post on Zulip Josh Mandel (Dec 21 2017 at 18:33):

Kubernetes has a related feature called "custom controllers" -- overview at https://resources.coreos.com/youtube-coreos-fest-2017/writing-a-custom-controller-extending-the-functionality-of-your-cluster

view this post on Zulip Josh Mandel (Dec 21 2017 at 18:34):

I think we risk heading toward an end-state here of inventing a new turing-complete programming language... all within FHIR... to handle this -- which probably isn't where we want to go. It'd be nice to reuse existing languages or APIs

view this post on Zulip Grahame Grieve (Dec 21 2017 at 19:23):

so there's an existing language... js.... but we'd be doing API or language otherwise, no?

view this post on Zulip Josh Mandel (Dec 21 2017 at 23:37):

Well, developing an API and developing a language are rather different prospects.

view this post on Zulip Brian Postlethwaite (Dec 21 2017 at 23:54):

My server has this capability using the Microsoft Workflow Foundation, and not something that I would expect to be interoperable between servers.
But very interested in what's being proposed.
I've used it to date to implement rejecting updates under some conditions, and also a custom operation that generates a PDF for the resource it's called on.

view this post on Zulip John Moehrke (Dec 22 2017 at 13:49):

This kind of an API does open up more security risks. HL7 does not need to solve all security risks, but does need to communicate the ones we think of in a "Security Considerations" section. That section can be nothing other than an itemization of risks that the implementer and deployment need to address.

view this post on Zulip Grahame Grieve (Dec 22 2017 at 22:53):

ok, well, @Josh Mandel what kind of a JS API would you propose?

view this post on Zulip Josh Mandel (Dec 22 2017 at 23:31):

Something that pretty closely followed (a subset of) the REST API.

So developers register functions like

POST Encounter/$trigger-create
{
  "onMethod": ["PUT", "POST"],
  "runFunction": "<see below>"
}

--> returns a trigger id that can be used to suspend/resume/delete trigger activity through additional operations.

The function body would look something like the following...

"Duplicate Identifier" Use Case
What we want here is, "reject incoming Encounters when we already have an Encounter with the same identifier."

async function trigger(resource, method, headers, fhir) {
  const identifiersToCheck = resource.identifier
    .map(i => `${i.system}|${i.value}`)
    .join(',');

  let matchesBundle = await fhir.get(`Encouter?identifier=${encodeURIComponent(identifiersToCheck)}`);
  if (matchesBundle.total > 0) {
    throw "Duplicate identifier";
  }
}

"Special Patient List" Use Case
What we want here is, "look at incoming Encounters; if you see any happening in a direct sub-location of my-special-location, then add the patient to a list called my-special-list."

async function trigger(resource, method, headers, fhir) {
  const myListUrl = 'List/my-special-list';
  const myLocationUrl = 'Location/my-special-location';
  const encouterLocation = await fhir.get(`Location/${resource.location.reference}`);
  if (encouterLocation.partOf.reference !== myLocationUrl) {
    return;
  }
  let list = await fhir.get(myListUrl);
  if ((list.entry || [])
    .filter(entry => entry.item.reference === resource.subject.reference)
    .length == 0) {
      list.entry.push({'item': resource.subject});
  }
  await fhir.put(myListUrl, list);
}

view this post on Zulip Josh Mandel (Dec 22 2017 at 23:37):

This assumes, more or less, that the fhir object supports get, put, post, and delete methods that take JSON content and return Promise<JSON>.

view this post on Zulip Josh Mandel (Dec 22 2017 at 23:39):

The server code would wait for the trigger function's return value (a Promise) and:

  • If resolved: complete the transaction
  • If rejected: abort the transaction
  • If timed out: abort the transaction

view this post on Zulip Josh Mandel (Dec 22 2017 at 23:45):

(For another analogy, AWS Lambda functions are used in various places in the platform where there's an existing workflow that needs way to adjust behavior with arbitrary logic -- see here for examples o functions called during authentication workflows.

view this post on Zulip Josh Mandel (Dec 22 2017 at 23:52):

And if the fancy ecmascript syntax scares you, it's convertable automatically to oldschool JS (ES3). But we could just as easily say that you've got to write ES3... I just wanted to show a clean example.

view this post on Zulip Grahame Grieve (Dec 23 2017 at 04:30):

@James Agnew @Christiaan Knaap any comments about this? @Dan Gottlieb does this overlap with your JS sandbox? is it consistent if it does?

view this post on Zulip Christiaan Knaap (Dec 23 2017 at 07:35):

In Vonk I chose the approach of Vonk FHIR Components: setup your own (ASP.NET Core) processing pipeline with components of Vonk, interleaved with any of your own processing - where you have both the raw Http request and a representation of it in terms of FHIR at your disposal. I think the HAPI Interceptors allow you to do similar things.
Among others one important reason for this approach instead of more loosely coupled 'plugins' is that I haven't worked out a complete means to express at what position(s) in the pipeline process plugins should act. Before a search? Or before any read operation? After the results are retrieved, after they are bundled? Should it happen synchronously (as in your first use case) or async after finishing the request (probably a fit for your second use case)? And on write operations things may even be more complex.
And yes, this ties the customer to the Vonk programming model. But imho FHIR is more about interoperability of health data, than about interoperability of health-data-processing-source-code.

view this post on Zulip Christiaan Knaap (Dec 23 2017 at 08:00):

Now reading outside the @Mentions narrow...
A JS trigger approach would force me to make the Vonk API usable from JS. I don't think that will happen very soon.
Besides that I find that people tend to choose a server not only for it's core capabilities but also for it's development environment. So if they choose HAPI it's partly because they are comfortable with Java, and likewise for Vonk / .Net. I even know a hospital that chose your server @Grahame Grieve because it's in Delphi! Why would you then go writing plugins in JS?

Reading through Josh's example about the duplicate id I immediately think 'and what if you prefer to solve this with a unique constraint in the database?' (Which would be a lot easier to manage in transactions). More generally put: a generic framework for this kind of plugins / triggers is already hard to come up with and even harder to implement as efficiently as you would be able to in the native language or storage layer of the server.

But maybe I'm just old fashioned in my reluctance to accept JS as the one-language-that-solves-all-problems...

view this post on Zulip Grahame Grieve (Dec 23 2017 at 10:47):

I'm not much in the camp that JS solves all problems. But I think that you are saying that 'changing business rules requires one to recompile the server'... and I don't think that's a good answer. I'm exploring whether there's interest in standardising this as an adjunct to the spec

view this post on Zulip Grahame Grieve (Dec 23 2017 at 10:49):

and I also don't think that database constraints being to handle the problem

view this post on Zulip Josh Mandel (Dec 23 2017 at 12:11):

And to be clear, I'm not saying that I think the thing I wrote up is a good idea to peruse -- just that if the goal is to augment a FHIR server to enable expressive runtime modifications to its behavior, then using a programming language is a powerful place to start (rather than trying to encode logic in, like, a new FHIR AST resource). If you go this route, interpreted languages are a natural fit. And if you want to pick one with broad support, js is hard to ignore. (But we could have a lisp, or lua, or...)

view this post on Zulip Josh Mandel (Dec 23 2017 at 12:15):

Also @Christiaan Knaap raises the important point that we'd probably want more than just synchronous, in-transaction triggers (which was where my examples focused).

view this post on Zulip John Moehrke (Dec 23 2017 at 13:32):

for async... can we use AuditEvent? It should already be used to record that requests to/from the server have happened. If it needs improvements, lets make them. It likely needs profiling to assure all servers use it the same way. Then you have an audit log, and a mechanism for triggering (subscription).

view this post on Zulip Josh Mandel (Dec 23 2017 at 14:34):

For the async use case, you can also just subscribe to new encounters and then call back to update your list via regular rest API.

view this post on Zulip Josh Mandel (Dec 23 2017 at 14:35):

But either these requires keeping a second server online.

view this post on Zulip James Agnew (Dec 23 2017 at 15:29):

A couple of thoughts-

I agree with Christian, something like this would be difficult and probably wouldn't happen in the short term. Most people pick a server based on its platform today, and interceptors have been well received by HAPI's users for this kind of thing. That said, we actually did have someone ask us about the possibility of implementing side-effects in JavaScript the other day as a long term roadmap item. I hadn't given it much thought in terms of how it would look.

Josh's syntax looks neat but damn if that wouldn't be a challenge to specify, standardize, and make consistent. I definitely agree with the notion that a programming language is a better place to start than an AST or something like that. (It occurs to me as I type this that it would be neat to be able to submit patches as javascript too, but that's another story :))

I kind of think this should be bundled into Subscription somehow. I.e. add EventType definitions for simple POST/PUT/DELETE if we need to, and add a JavaScript delivery channel, along with maybe a property specifying that deliver should be synchronous?

view this post on Zulip Josh Mandel (Dec 23 2017 at 16:16):

Leaving aside the issue of trying to standardize, and leaving aside js... Do you think there is an opportunity to do a simpler platform-specific thing that supports dynamic behavior changes without recompiling and restarting -- perhaps in a hapi- or vonk-specific way? @James Agnew @Christiaan Knapp

view this post on Zulip Grahame Grieve (Dec 23 2017 at 20:16):

I can't imagine a serious record keeping system where the answer for changing business rules is to recompile.

view this post on Zulip Grahame Grieve (Dec 23 2017 at 20:32):

as for subscription.... I process subscriptions post-transaction. I figured everyone does that?

view this post on Zulip Brian Postlethwaite (Dec 23 2017 at 23:18):

@Josh Mandel , sqlonfhir has this capabiltiy and is already in use.

view this post on Zulip Josh Mandel (Dec 23 2017 at 23:27):

Sweet -- can you comment on how it works? How do developers register the #a new trigger? What capabilities does a trigger have? Any docs you can point us to?

view this post on Zulip Brian Postlethwaite (Dec 23 2017 at 23:46):

It uses the Microsoft Workflow Foundation component as the script execution engine, which really compiles on the fly to actual .net objects, and has full access to the server's internal models.
You can also just put c# code inside it too.
The routines are called with each invocation, to permit changing the results of each call.
http://sqlonfhir-ci2.azurewebsites.net/FhirServerHome/Workflows
Shows the parameters to the engine.
(There is a seperate REST API to load/unload these from the server)

view this post on Zulip Brian Postlethwaite (Dec 23 2017 at 23:48):

It can also wire in custom operations into the server.

view this post on Zulip Brian Postlethwaite (Dec 23 2017 at 23:49):

i.e. $random-stuff = some new code

view this post on Zulip Brian Postlethwaite (Dec 23 2017 at 23:53):

  <Sequence >
    <If >
      <If.Condition>
        <InArgument x:TypeArguments="x:Boolean">
          <mca:CSharpValue x:TypeArguments="x:Boolean">"$generatePDF" == Operation</mca:CSharpValue>
        </InArgument>
      </If.Condition>
      <If.Then>
        <Assign >
          <Assign.To>
            <OutArgument x:TypeArguments="hfm:Resource">
              <mca:CSharpReference x:TypeArguments="hfm:Resource">Output</mca:CSharpReference>
            </OutArgument>
          </Assign.To>
          <Assign.Value>
            <InArgument x:TypeArguments="hfm:Resource">
              <mca:CSharpValue x:TypeArguments="hfm:Resource">Fhir.Utilities.CustomOperations.GeneratePDF(OperationParameters, Output as Hl7.Fhir.Model.QuestionnaireResponse, @"C:\Templates\PDFs\output-certificate.pdf")</mca:CSharpValue>
            </InArgument>
          </Assign.Value>
        </Assign>
      </If.Then>
    </If>
  </Sequence>

view this post on Zulip Brian Postlethwaite (Dec 23 2017 at 23:54):

Its very verbose, but you can just have a simple bit of .net code in there too
(this example is using some code in another assembly that is on the server)

view this post on Zulip Josh Mandel (Dec 24 2017 at 00:28):

Oh, fascinating!

view this post on Zulip Josh Mandel (Dec 24 2017 at 00:29):

And this code can query/modify existing resources in the server, synchronously?

view this post on Zulip Brian Postlethwaite (Dec 24 2017 at 00:47):

Yup, full access to the servers object model and storage.
For async stuff, you could use this to queue processsing.

view this post on Zulip Christiaan Knaap (Dec 24 2017 at 11:53):

For Vonk we will probably add a means for this without recompilation. But it is not on top of the roadmap yet.
WWF is a nice model but afaik not available for .NET Core, so that rules it out for us.
Also I still have to see the first actual request for Vonk to allow dynamic extensibility. @James Agnew : Anyone demanded that of HAPI?

view this post on Zulip Christiaan Knaap (Dec 24 2017 at 11:55):

Gives me something to philosophize on during the holidays though.

view this post on Zulip Abbie Watson (Dec 24 2017 at 19:59):

The Meteor.js has a very splendid matb33:collection-hooks package, which creates isomorphic triggers on both server and client to handle all these things. (We're definitely in the 'javascript solves all problems' camp.)

https://github.com/matb33/meteor-collection-hooks

Once an incoming REST call comes in along a FHIR endpoint, we normalize to JSON and put the incoming data into a resource type cursor. If it's on the server, the cursor is connected to a Mongo DB by default for caching/persistence; and if it's on the client, it's attached to a minimongo instance to persist through browser refreshes. The collection hooks are attached to the cursors, and provide a common API across all resource types:

Mongo.collections[resourceType].before.insert(userId, doc)
Mongo.collections[resourceType].before.update(userId, doc, fieldNames, modifier, options)
Mongo.collections[resourceType].before.remove(userId, doc)
Mongo.collections[resourceType].before.upsert(userId, selector, modifier, options)
Mongo.collections[resourceType].before.find(userId, selector, options)
Mongo.collections[resourceType].before.findOne(userId, selector, options)
Mongo.collections[resourceType].after.insert(userId, doc)
Mongo.collections[resourceType].after.update(userId, doc, fieldNames, modifier, options)
Mongo.collections[resourceType].after.remove(userId, doc)
Mongo.collections[resourceType].after.find(userId, selector, options, cursor)
Mongo.collections[resourceType].after.findOne(userId, selector, options, doc)

Here's how the associated code looks like in practice (which can be run on both the server and web browser):

import { has, get } from 'lodash';

// duplicate error
Encounters.before.insert(function (userId, doc) {
  doc.createdAt = Date.now();
  if(Encounters.findOne('identifier.$.value': get(doc, 'identifier.0.value'))){
    throw "Duplicate error";
  }
});

// add to patient list if observation is above a threshold
var observationThreshold = 0.5;
var list = getMyList();
Observations.before.insert(function (userId, doc) {
  if(get(doc, 'category.text') == "Foo"){
    if(get(doc, 'valueQuantity') > observationThreshold){
      Lists.update({_id: getMyList()}, {$addToSet: {
        'item': doc.subject
      }});
    }
  }
});

// if you want dynamic runtime triggers (with versioning)
// so we don't have to recompile code
Observations.before.insert(function (userId, doc) {
  var trigger = DynamicTriggers.findOne({
    'type': 'observation',
    'method': 'before.insert',
    'version': getLatest()
  });
  trigger.run(userId, doc);
});

view this post on Zulip Grahame Grieve (Dec 25 2017 at 09:18):

@Christiaan Knaap "dynamic extensibility" isn't really quite the right words, I think. "Configuring business rules and workflow" is more how I'd describe it

view this post on Zulip James Agnew (Dec 25 2017 at 18:13):

Oh, I definitely agree on the notion that this would be useful. Truthfully I kind of like Josh's proposal, though I think it should be bundled into Subscription.

I just don't know how hard it would be to implement (no harm in trying).

view this post on Zulip Dan Gottlieb (Dec 26 2017 at 15:51):

@Grahame Grieve I don't think this concept overlaps with either our client side js sandboxing experiments or our js FHIR proxy server implementation. That said, I like the idea of a standard method to register triggers that run in a runtime language implementation on the FHIR server. Do we need to settle on a single language though?

Why not just have a standard approach to registering the triggers and a way to for servers to indicate which language(s) they support (maybe in the capability statement)? It would reduce portability of the trigger functions, but there are bound to be portability issues regardless given different js implementations, degrees of sandboxing, runtime restrictions, etc. Then, servers will have the flexibility to support common embedded languages like lua and js, as well as java, .net, a custom XML language, etc and can even host more than one runtime. Users could specify a language as part of the operation Josh sketched out and servers could error if they don't support it:

POST Encounter/$trigger-create {
    "onMethod": ["PUT", "POST"],
    "language": "javascript",
    "runFunction": "<see below>"
}

view this post on Zulip Abbie Watson (Dec 26 2017 at 15:56):

Truthfully I kind of like Josh's proposal, though I think it should be bundled into Subscription.

I just don't know how hard it would be to implement (no harm in trying).

Agreed on bundling with Subscription. That's exactly how our system is implemented; with the client side cursor subscribing to the server side cursor, and the triggers firing when either of the cursors are updated. The pub/sub comes through the Distributed Data Protocol (DDP), which is a somewhat esoteric Meteor.js based websocket layer that was loosely modeled after PubSubHubBub. If you follow this server-side-triggers with subscriptions line of reasoning to it's logical conclusion, one eventually wants to harmonize the server/client APIs, which is where the isomorphism comes from; then one wants to name the cursors after the Resource; and then hoist onto websockets to optimize network traffic; and so forth. And we did this all before the Subscription resource was even formalized. But the config object we pass around is trivially described by the Subscription resource.

view this post on Zulip Abbie Watson (Dec 26 2017 at 15:58):

Here's an example of what we're moving towards on the server:

Meteor.publish("FhirSubscriptions", function (payload){
  // make sure the user is logged in
  // for an oauth implementation, we might want to replace with an auth token
  if (this.userId) {
    Mongo.collections[payload.resourceType].after.update(function (userId, doc) {
      payload.trigger.after.update(userId, doc);
    });
    return Mongo.collections[payload.resourceType].find(payload.query, payload.options);
  } else {
    return [];
  }
});

Which lets us then write things like this:

Meteor.subscribe("FhirSubscriptions", subscription.channel.payload);

And more robustly:

var exampleSubscription = {
  "resourceType": "Subscription",
  "status": "active",
  "channel": {
    "type": "websocket",
    "endpoint": Meteor.absoluteUrl(),
    "payload": {
      "resourceType": "Patient",
      "query": {
        "name.$.family": getSearchInput()
      },
      "options": {
        "sort": { "name.$.text": getSortPreference() }
      },
      "trigger": {
        "after": {
          "update": function(userId, doc){
            HipaaLogger.logEvent({
              resourceType: "AuditEvent",
              action: "Updated doc via server-side trigger passed in via subscription from the client.",
              outcome: "Success",
              agent: [{
                role: {
                  text: 'Practitioner'
                },
                userId: Meteor.userId(),
                name: Meteor.user({_id: userId}).fullName(),
              }],
              source: {},
              entity: [{
                identifier: {
                  value: doc.id()
                },
                name: "Patient",
              }]
            });
          }
        }
      }
    }
  }
};
Subscriptions.insert(exampleSubscription);

Subscriptions.find().forEach(function(subscription){
  if(subscription.active){
    Meteor.subscribe("FhirSubscriptions", subscription.channel.payload);
  }
});

view this post on Zulip Abbie Watson (Dec 26 2017 at 15:59):

So, from the client side, if we wanted to add triggers to a subscription that included a trigger, we'd probably add it into the payload, like so: (Simplified to illustrate the schema).

{
  "resourceType": "Subscription",
  "status": "active",
  "channel": {
    "type": "websocket",
    "endpoint": "http://localhost:3000",
    "payload": {
      "resourceType": "",
      "query": {},
      "options": {
        "sort": {},
        "limit": {}
      },
      "trigger": {
        "after": {
          "find": {},
          "findOne": {},
          "insert": {},
          "update": {},
          "delete": {}
        },
        "before": {
          "find": {},
          "findOne": {},
          "insert": {},
          "update": {},
          "delete": {}
        }
      }
    }
  }
}

Send that over the wire to our publication method, and it has everything needed to set up the subscription with server side triggers.

view this post on Zulip Grahame Grieve (Dec 26 2017 at 22:20):

Dan what other languages would be relevant?

view this post on Zulip Grahame Grieve (Dec 26 2017 at 22:21):

For me, subscription is a problem; I do all the subscription processing post-commit. Business rules are in commit. I think this is a big distinction

view this post on Zulip John Moehrke (Dec 26 2017 at 22:31):

So you are looking for a server side hook that can make changes that are not Versioned... (shutters the security geek inside)

view this post on Zulip Brian Postlethwaite (Dec 27 2017 at 03:13):

You could do stuff like regenerate the narrative of a generated narrative to be more consistent, resolve identifiers to references, include the display portions of references . Or check for violations of rules, and throw exceptions (preventing commit).

view this post on Zulip Christiaan Knaap (Dec 27 2017 at 07:38):

For me, subscription is a problem; I do all the subscription processing post-commit. Business rules are in commit. I think this is a big distinction

+1

view this post on Zulip Dan Gottlieb (Dec 27 2017 at 14:13):

I think there are a bunch of embedded language runtimes that could make sense depending on whether the server implementer is optimizing for performance, memory usage, sandboxing functionality, ease of integration with the core server language, developer comfort, etc. For example, redis embeds lua instead of js since it has a smaller memory footprint, nginx created a custom subset of js for performance, and R Studio built out the whole Shiny ecosystem so developers don't have to learn js in addition to R...

view this post on Zulip Dan Gottlieb (Dec 27 2017 at 14:13):

Also, I could see some server implementors building a DSL for trigger functions to have more control over third party code running on their system, or wanting to use the same language as the core server implementation so they can more easily debug triggers (eg http://www.remobjects.com/ps.aspx :)

view this post on Zulip Abbie Watson (Dec 27 2017 at 14:57):

You could do stuff like regenerate the narrative of a generated narrative to be more consistent, resolve identifiers to references, include the display portions of references . Or check for violations of rules, and throw exceptions (preventing commit).

Exactly. These are mostly before hooks, as one generally wants to optimize the generated narrative before putting it into cache. after hooks tend to be logging, auditing, and data pipelining.

view this post on Zulip Grahame Grieve (Jan 02 2018 at 10:01):

so, @Josh Mandel, it tuns out that in my server, there is 3 different points in the pipeline for server side decision making logic of this type:

  • when the request is fully parsed, and before any decisions (security) are made (and before transaction is entered). can modify absolutely anything
  • deep down inside the transaction, when before and after are being compared. Can blow up the transaction, but not modify anything
  • post transaction, when subscriptions are posted

view this post on Zulip Grahame Grieve (Jan 02 2018 at 10:01):

I suspect that most server architectures will lead to the same general set of 3 options

view this post on Zulip Brian Postlethwaite (Jan 02 2018 at 20:36):

Those first 2 are the same in my server, base security is checked first.
But i don't disagree with the list.

view this post on Zulip Grahame Grieve (Jan 07 2018 at 18:56):

ok, @Josh Mandel , getting back to this. I have an implementation of this now. I did things a little differently to what you originally proposed. the first difference is that I didn't see the need to do a whole lot of persistence work when we already have EventDefinition - so I just re-used EventDefinition: you post EventDefinition resources to the server, and set them active or not with a tag.

view this post on Zulip Grahame Grieve (Jan 07 2018 at 18:56):

so here's an example resource:

<EventDefinition xmlns="http://hl7.org/fhir">
  <id value="1"/>
  <meta>
    <tag>
      <system value="http://www.healthintersections.com.au"/>
      <code value="active"/>
    </tag>
  </meta>
  <url value="http://www.healthintersections.com.au/fhir/EventDefinition/test"/>
  <name value="test"/>
  <status value="draft"/>
  <trigger>
    <type value="data-changed"/>
    <data>
      <type value="Patient"/>
    </data>
    <condition>
      <language value="application/javascript"/>
      <expression value="..."/>
    </condition>
  </trigger>
</EventDefinition>

view this post on Zulip Grahame Grieve (Jan 07 2018 at 18:58):

this shows the example tag, the trigger the resource type that you want to run this trigger on, and the javascript you want to run. The trigger code must be one of the following:

view this post on Zulip Grahame Grieve (Jan 07 2018 at 18:59):

  • data-changed - called for create, update, patch, delete
  • data-added - called for create
  • data-modified - called for update/patch
  • data-removed - called for delete

view this post on Zulip Grahame Grieve (Jan 07 2018 at 19:00):

the javascript function has the signature

function dataChanged(session, previous, current}

view this post on Zulip Grahame Grieve (Jan 07 2018 at 19:01):

Here's an example function that does what I originally proposed:

function isMasterUrn(id) {
    return id.system === 'http://acme.org/fhir/NamingSystem/urn';
}

function dataChanged(session, previous, current) {
  if (current != null) {
    id = current.identifier.find(isMasterUrn);
    if (id == null)
      throw('No URN provided');
    bnd = fhir.search('Patient', 'identifier=http://acme.org/fhir/NamingSystem/urn|'+id.value);
    if ((bnd.entry.length > 0) && (bnd.entry[0].resource.id != current.id))
      throw('Duplicate URN '+id.value);
  }
}

view this post on Zulip Grahame Grieve (Jan 07 2018 at 19:03):

You'll not that I chose to implement a higher level wrapper for the FHIR operations - fhir.search instead of fhir.get - makes it easier to invoke the operations

view this post on Zulip Grahame Grieve (Jan 07 2018 at 19:05):

@Bryn Rhodes I'm really not using EventDefinition quite correctly here - I'm (mis)using condition as a rule against the other filter information. But I couldn't see another convenient way?

view this post on Zulip Grahame Grieve (Jan 07 2018 at 19:06):

... now I have to figure out how to set this up on test.fhir.org....

view this post on Zulip Brian Postlethwaite (Jan 07 2018 at 21:32):

What about before-update, after-update etc. Just like we have in SQL triggers, covering the ones that execute before commit, and after(async)

view this post on Zulip Grahame Grieve (Jan 07 2018 at 21:35):

still working on that. it feels like it will be a lot harder to get consistency there

view this post on Zulip Grahame Grieve (Jan 07 2018 at 21:51):

here. btw, is my Session Object:

  • String provider {'', 'Custom', 'Facebook', 'Google', 'HL7'} - who authenticated the user (null for anonymous)
  • String id - internal id for the session (database primary key)
  • String userKey - internal id for the user (anonymous has it's own key)
  • String userName - displayable description of the user
  • String userEvidence {'Anonymous', 'Internal' ,'Login', 'External AOuth', 'JWT Bearer Token'} - on what grounds the user is identified
  • String systemName - name that describes the client (depends on how authenticated; might just be IP address
  • String systemEvidence {'Unknown', 'This Server', 'by OAuth', 'By OWin', 'By Certificate', 'By JWT'} - how the client name was established ('unknown' = the client was not identifier, IP only. "This server' = an internally sourced request)
  • String sessionName - net description of the session (user + system)
  • String email - user email, if known
  • String expires - UTC date that the session expires, in FHIR format
  • String created - UTC data session was created, in FHIR format
  • bool canRead({String} resourceName) - true if session provides read access to resourceName (includes OAuth scopes)
  • bool canWrite({String} resourceName) - true if session provides write access to resourceName (includes OAuth scopes)
  • String[] compartments - list of compartments this session has access to (empty= no compartment based restriction)
  • String[] scopes - list of scopes user agreed to (or empty, if not using OAuth)
  • bool isAnonymous - true if user is anonymous (derived from userEvidence)

view this post on Zulip Josh Mandel (Jan 08 2018 at 15:31):

(deleted)

view this post on Zulip Josh Mandel (Jan 08 2018 at 15:34):

It looks like you've set this up as a synchronous js app? It might be worth keeping it async so it can be used more readily with existing js-based environments.

view this post on Zulip Josh Mandel (Jan 08 2018 at 15:38):

Also, in your session model @Grahame Grieve I'm not sure I understand the "evidence" concept, or how user evidence VS system evidence are used separately.

view this post on Zulip Grahame Grieve (Jan 08 2018 at 19:49):

I don't understand the use of asynchronous in this context.

view this post on Zulip Grahame Grieve (Jan 08 2018 at 19:51):

Session model: it's about authenticating the client and the user. You may have an authenticated client, but not know the user (backend services) or you may have authenticated the client without knowing the use (unusual use of OAuth) or you may have firmly authenticated both

view this post on Zulip Grahame Grieve (Jan 08 2018 at 19:51):

it's not clear how useful this is to a script, that's for sure. But I have that information and it's easy to expose.

view this post on Zulip Josh Mandel (Jan 08 2018 at 20:17):

Asynchronous is just the normal mode for interacting with the JavaScript interpreter in most contexts. This includes the browser and node js. If we defined a synchronous API instead, this would be very hard to implement in those environments.

view this post on Zulip Josh Mandel (Jan 08 2018 at 20:17):

If you're going to pick one, asynchronous is the more flexible pattern is all.

view this post on Zulip Grahame Grieve (Jan 08 2018 at 22:14):

I'm inside a transaction. I have to wait for the script to execute. how can it be meaningfullly asynchronous?

view this post on Zulip Brian Postlethwaite (Jan 08 2018 at 23:05):

Inside the transaction I agree it has to be syncronous as it needs to be able to effect the outcome of the transaction. For the post transaction, being async would be fine.

view this post on Zulip Grahame Grieve (Jan 08 2018 at 23:06):

I'd still have to wait post-transaction at the moment... I'll think about that

view this post on Zulip Brian Postlethwaite (Jan 08 2018 at 23:07):

I'm coding up the infrastructure to be able to do the post transaction processing at the moment.

view this post on Zulip Grahame Grieve (Jan 08 2018 at 23:09):

I'll have previous and current versions available, but I haven't thought deeply about about the balance between driving actions from the script vs using the script as a filter on the subscription

view this post on Zulip Josh Mandel (Jan 08 2018 at 23:59):

For what it's worth I'm not arguing for Meaningful asynchronicity. I'm just saying that it makes sense for the API itself to be asynchronous. Mostly from the perspective of being generalizable at fitting with existing Frameworks. @Abigail Watson @nicola (RIO/SS) @Dan Gottlieb would like to get your perspective on this.

view this post on Zulip Josh Mandel (Jan 09 2018 at 00:00):

When it comes to acting as a filter vs driving behavior... both of these are important and rather different use cases. They can also work nicely together :simple_smile:

view this post on Zulip Grahame Grieve (Jan 09 2018 at 01:02):

with regard to async.... I'm still not sure why it makes sense to be asynchronous. Under what circumstances would asynchronicity be meaningful for this operation?

view this post on Zulip nicola (RIO/SS) (Jan 09 2018 at 10:30):

the problem with this approach - that you customization moves to code realm - many complex systems went this way embedding js, lua or python and everybody, who work with this approach - hate it :wink: Most well known system is browser. So industry moved to fast deploy and micro-services instead.

view this post on Zulip Grahame Grieve (Jan 09 2018 at 21:43):

So josh suggested this first, but that's a serious challenge for me - and I expect for others. The key thing about this is that it runs inside my transaction context, and it would be a massive rewrite to be able to call out to another server within the transaction core, and then have it call back into the context of the transaction core.

view this post on Zulip nicola (RIO/SS) (Jan 09 2018 at 21:56):

@Grahame Grieve - two phase commit :wink:


Last updated: Apr 12 2022 at 19:14 UTC