Architecture

Pipeline and middleware components

Firely Server is built upon ASP.NET Core and its modular setup of a pipeline of middleware. It also leverages the dependency injection pattern that is central to ASP.NET Core. If you are not familiar with these concepts, please review them first.

Firely Server’s core plugins provide middleware components that handles the interactions of the FHIR RESTful API. These components are placed in the pipeline. The pipeline starts with a few general components that interpret the incoming http request and determine which interaction is being called. This information is fed to the rest of the pipeline as the Request property of an IVonkContext object, analogous to the HttpContext and its Request property of ASP.NET Core itself. The IVonkContext also provides the Response property that is used to communicate the status, resource and/or OperationOutcome resulting from the interaction. On the way back through the pipeline the counterparts of the interpreting middleware translate the IVonkContext Response back to an http response, conforming to the FHIR specification. The rest of the pipeline consists of middleware components that each fulfill one of the interactions. So there is one for read, one for create, for search et cetera. These components are just regular ASP.Net Core Middleware components, except that they have access to the IVonkContext, and act on that.

Adding custom middleware

Using Firely Server Components you can add your own middleware anywhere in the pipeline. It can be standard ASP.NET Core middleware - having nothing to do with FHIR - or middleware acting on the IVonkContext, e.g. to implement a custom operation. Firely Server also provides convenience methods to register your middleware as one that handles a FHIR interaction, including attributes to declare for which interaction and which resource types your middleware should be invoked. This is explained in Firely Server Plugins.

Repository interfaces

Many of the FHIR interactions require access to a repository of resources. E.g. create must be able to store a resource, whereas search must be able to query resources and retrieve the results. In Firely Server, the middleware components that implement these interactions access the repository through interfaces. There are two different interfaces for different parts of the FHIR RESTful API.

IChangeRepository           //for create, update and delete
ISearchRepository           //for all types of search, read and history

In many scenarios, read-only access is sufficient, and you only need to implement the ISearchRepository. In that implementation you can choose which of the search parameters you want to support, and whether to expose versions and deleted resources.

These interfaces enable you to implement a Firely Server Facade. And they enable us to support database engines as diverse as MongoDB, SQL Server and in-memory.

Capabilities

A FHIR server has to express its capabilities in a CapabilityStatement, available on the /metadata endpoint. Firely Server’s capabilities are defined by the middleware components that make up its pipeline. Every component knows what interaction it adds to the capabilities. Therefore, we keep that information close to the component itself. Typically, every component has an implementation of ICapabilityStatementContributor, in which it gets access to the ICapabilityStatementBuilder. The latter provides methods to add information to the CapabilityStatement without having to worry about what is already added by other components or the order of execution.

These methods are especially handy for implementers of a facade. Plugins implemented in the facade do not automatically end up in the /metadata endpoint of Firely Server, but ICapabilityStatementContributor and ICapabilityStatementBuilder can be used to make sure the plugins are visible in the CapabilityStatement. For example, you have implemented a Bulk Data Export plugin in your facade and you would like to make sure this is visible in the CapabilityStatement.instantiates of Firely Server. You can add a CapabilityStatementContributor class to your plugin code that implements the ICapabilityStatementContributor. Within this class you can implement the ICapabilityStatementBuilder to add your plugin to the CapabilityStatement.instantiates. See the folowing code snippet:

internal class CapabilityStatementContributor: ICapabilityStatementContributor
{
    public void ContributeToCapabilityStatement(ICapabilityStatementBuilder builder)
    {
        builder.UseCapabilityStatementEditor(cse =>
        {
            cse.AddInstantiates("http://hl7.org/fhir/uv/bulkdata/CapabilityStatement/bulk-data");
        });
        }
    }

Make sure to register this class in your PluginConfiguration.cs:

public static IServiceCollection ConfigureServices(IServiceCollection services)
    {
        services.TryAddContextAware<ICapabilityStatementContributor, CapabilityStatementContributor>(ServiceLifetime.Transient);
        return services;
    }