legacy systems blog post
Firely Updates
9 Min Read

Open up Legacy Systems to FHIR with FHIR Facade

Christiaan Knaap
Christiaan Knaap

Subscribe to our newsletter

Subscribe

TLDR;

Firely Server can run as either a full FHIR server with its own native FHIR storage, or as a FHIR Facade to FHIR-enable existing legacy systems. Read on below or dive right into our FHIR Facade documentation.

What can I do with the FHIR Facade?

What can I do with Vonk FHIR Facade?

Have you ever tried a different kind of bike? A recumbent, a canal bike, or even a flying bicycle? From the cyclist’s perspective, they are all the same. That’s because they all have the same interface: a pair of pedals, a steering wheel and something to sit on. However, no single type of bike can cycle through air, land or water. These bicycles will need a little adjustment to their ‘interface’ depending on whether you want to cycle on water, in the air, or on the road. Similarly, Firely’s FHIR Facade allows you to provide the same FHIR interface and bridge the gap to different backend systems.

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

What is it?

The turn-key Firely 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 four implementations of that abstraction ourselves in order to support different types of storage, such as SQLite, MS SQL Server, MongoDB, and Memory.

Firely’s 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, though that might hurt performance.

Do I have to program?

Yes. Building a facade with Firely Server means building a .NET Standard or .NET Core class library 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.

Side wheels for relational databases

Side wheels for relational databases

We expect access to relational databases to be the most common schema. To do so, we provided a base implementation based on .NET Entity Framework Core: Vonk.Facade.Relational.

How does it work?

We have to be a bit technical here – 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 resource type 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.ToIResource();
        }

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. Directly 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 search parameters like token, quantity, or reference, and modifiers on top of that.

Firely Server can help you analyze the search URL (or body, POST is also allowed for search) and break it down into pieces. It will validate all parts of it:

  • The search parameter: is it 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 show you a parameter name and a value that contains the details according to the type of search parameter. 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.

Firely Server disassembles that search URL and makes sure you only worry about one thing at a time. If you were to also implement the Observation.code parameter, this whole search would work.

In this example, the BPQueryFactory is 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).

As a bonus, if you implement the parameter as done above, the 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 Firely Server takes over again. You just return a list of resources based on the data returned from the composed query and the mapping. Firely Server will take care of bundling, paging links, and serialization to the requested format.

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

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 Firely Server documentation site. This is accompanied by code (of which the examples above are an excerpt) on Github.

We are always very eager to provide support and get feedback from your experiences. Feel free to contact us at server@fire.ly and share any feedback or comments you might have.

Want to stay on top of Interoperability and Health IT? 

Subscribe to our newsletter for the latest news on FHIR. 

Post a comment

Your email address will not be published. Required fields are marked *