FHIR up your legacy data

TLDR;

At the FHIR DevDays we released Vonk FHIR Facade. A set of NuGet packages to FHIR-enable existing (legacy) systems. Read on or dive in.

What can I do with it?

12-08-21-bici-01

Ever tried a different kind of bike? A recumbent, a canal bike or even a flying bicycle? From the cyclist’s perspective they’re all the same. That’s because they all have the same interface (pedals, steering wheel, something to sit on). But some of them need a little adjustment to their ‘surface’. That is where riding water or air is different from riding a road, and no single universal bike could cover all of them. Likewise, Vonk FHIR Facade empowers you to provide the same – FHIR – interface and just bridge the gap to different backend systems.

So whether you have that homegrown Access® system with valuable research data or that tailored Diabetes registration or this cool app platform that defines it’s own web services – Vonk FHIR Facade is meant to put a uniform API in front of each of them. And yes, that uniform API is obviously FHIR.

What is it?

The turn-key Vonk FHIR Server is built from several libraries. The FHIR RESTful API logic is built into Vonk.Core. It is agnostic to the actual storage. Instead it only communicates with an abstraction of the storage. We have already built three implementations of that abstraction ourselves. That way we are able to support very different types of storage, currently SQL Server, MongoDB and Memory.

Vonk FHIR Facade offers you the Vonk.Core library for all the FHIR functionality, and allows you to provide an implementation of the storage abstraction that fits your existing repository. That can be a database (relational or otherwise), other web services or even flat files, but that might hurt performance 🙂

Do I have to program?

Yes. Building a Facade with Vonk means building an ASP.NET (Core) web application that uses the provided NuGet packages.

The .NET programming languages give you a lot of freedom to express whatever you need to access the backend system. This power is hard to express in a declarative approach. On top of that, it allows for optimal performance.

That being said, we strive to develop methods to define a Facade for users that are less familiar with (.NET) programming. In order to get that right, we will first collect best practices from the early adopters and look for emerging patterns.

Side wheels for relational databases

Side wheels for relational databases

We expect access to relational databases to be the most common schema. And we already implemented it three times ourselves. So we provided a base implementation for doing exactly that, based on .NET Entity Framework Core: Vonk.Facade.Relational.

How does it work?

Here we have to be a bit technical – it’s about programming right? Implementing the storage abstraction involves two main components:

  1. Mapping data from the entities in your backend / database to FHIR Resources
    (And vice-versa if you accept creates or updates.)
  2. Translating pieces of the FHIR search mechanism to queries / web calls in your backend system.

Mapping data

This is pretty straightforward. Given that you have one or more related entities in your source system, you create a new instance of the target resourcetype and fill in the blanks:

public IResource MapPatient(ViSiPatient source)
{
  var patient = new Patient();
  patient.Id = source.Id.ToString();
  patient.Identifier.Add(new Identifier("http://mycompany.org/patientnumber", source.PatientNumber));
  patient.Name.Add(new HumanName().WithGiven(source.FirstName).AndFamily(source.FamilyName));
  patient.BirthDate = new FhirDateTime(source.DateOfBirth).ToString();
  patient.Telecom.Add(new ContactPoint(ContactPoint.ContactPointSystem.Email, ContactPoint.ContactPointUse.Home, source.EmailAddress));
  return patient.AsIResource();
}

You can also put data in extensions if a custom profile requires you to do so.

Before you program the mapping, you will have to design it first. Unless data is required on one side that is absent on the other, this will not take too much time.

Building queries

Searching in FHIR can become pretty complex. Straightforwardly searching for a string may already involve inspecting multiple columns in your database (e.g. the names of a patient), and then there are several other types of searchparameters like token, quantity or reference and modifiers on top of that.

What Vonk will do for you is analyzing the search url (or body, POST is also allowed for search) and break it up to pieces. It will validate all the parts of it:

  • Is the searchparameter defined on the requested resourcetype (also within a chain)?
  • Is the modifier valid for this type of parameter?
  • Is the argument in a valid format?
  • Can the quantity be canonicalized?

After that, it will present you a parametername and a value that contains the details according to the type of searchparameter. This gives you all the information to essentially build a clause of the where statement.

public override PatientQuery AddValueFilter(string parameterName, ReferenceFromValue value)
{
  if (parameterName == "subject" && value.Source == "Observation")
  {
    var obsQuery = value.CreateQuery(new BPQueryFactory(OnContext));
    var obsIds = obsQuery.Execute(OnContext).Select(bp => bp.PatientId);

    return PredicateQuery(p => obsIds.Contains(p.Id));
  }
  return base.AddValueFilter(parameterName, value);
}

The example above contains a non-trivial type of search: it is the result of a reverse chain on a reference search parameter: e.g. Patient?_has:Observation:subject.code=abc. Vonk disassembles that search url and makes sure you only need to worry about one thing at a time. If you would also implement the Observation.code parameter, this whole search would work.

The BPQueryFactory is in this example the factory that creates where clauses on the imaginary BloodPressure table, yielding Observation resources. This is the class where you would implement the Observation.code parameter in (based on a TokenValue).

A bonus is that if you implement the parameter as done above, include functionality comes for free: Patient?_revinclude=Observation:subject.

This implementation utilizes an EFCore DbContext and LINQ to express the filter. But the repository interfaces also allow you to express in terms of other query mechanisms, e.g. an http call.

Providing a FHIR response

This is where Vonk takes over again. You just return a list of resources based on the data returned from the composed query and the mapping. Vonk will take care of bundling, paging links and serialization to the requested format.

If you have not implemented one of the used searchparameters, Vonk will report that in an OperationOutcome. It will also automatically handle other headers for you such as the Prefer header on create and update.

STU3

This release of Vonk FHIR Facade supports FHIR STU3. Expect support for R4 shortly after that has been released.

Get on your bike!

We prepared an elaborate example for reading and searching Patient and Observation resources on a fictitious legacy system. You can find it on the Vonk documentation site. This is accompanied by code (of which the examples above are an excerpt) on Github.

The Facade code is meant to be ready for use in production scenario’s. Nevertheless this is the first release, so expect to see some changes to the interfaces in the near future. To smoothen your implementation and any changes afterwards we would like to keep in touch about your development.

Of course we’re very eager to provide support and get feedback from your experiences. Contact us on vonk@furore.com.

Pricing

Currently the pricing of all editions of Vonk – Server, Facade, Components – is the same. Please read the product page for details on the Launching Customer Program.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s