Welcome to the Firely Server’s documentation!
Firely Server is Firely’s FHIR server. It was formerly named ‘Vonk’ and is the successor to our Spark server.
Firely Server is the answer to the growing need for a stable server that can be used in a variety of production environments. A public sandbox is available at https://server.fire.ly, which is is free to use and intended for testing and educational purposes only.

On these pages we provide you with the documentation you need to get up and running with your own standard Firely Server installation, as well as information on how to contact us when you have additional needs, such as a custom implementation or support contract.
Overview of Firely Server, Plugins and Facades
Framework
Firely Server is not just a FHIR Server, it is a processing pipeline for handling standard and custom FHIR requests. Firely Server consists of this pipeline filled with processors to handle the interactions defined in the FHIR RESTful API. With Plugins you can add your own processors to the framework to perform custom operations, or fill in cross-cutting concerns for your business. A Facade is a type of plugin that provides a data access layer for an existing data repository (e.g. a proprietary system). This image sums it all up:

Firely Server comes in several editions:
Sandbox: try it right now on https://server.fire.ly
Evaluation: get an evaluation license from Simplifier.net, allowing you to explore all functionality for free during a week (renewable)
Community: use Firely Server for free, but only on SQLite
Commercial use with professional support in different scales: Startup, Growth, Scale and Enterprise
For more information and pricing visit the product site.
Complete Server
Firely Server is a FHIR Server out of the box. It is built with Microsoft .NET Core and runs on any of the platforms for which a .NET Core Runtime is available. Linux, Windows, MacOS, Docker etcetera. Installation can be done in minutes. After that you can configure main features and further details:
Choose your database: SQLite is configured by default, but for serious use you’d want to configure MongoDB or SQL Server.
Configure the level of validation: Firely Server can be very loose or very strict on the validity of the resources that you send to it.
Configure endpoints for FHIR versions that you want to support (since Firely Server (Vonk) 3.0.0: FHIR STU3 and FHIR R4)
Fill in your licensefile.
Adjust the processing pipeline by trimming it down (excluding certain plugins) or extending it with extra plugins.
Besides configuration of the settings, Firely Server features an Administration API that allows you to configure the so-called Conformance Resources that drive parsing, serialization, validation and terminology. The Administration API is pre-filled with Conformance Resources such as the StructureDefinitions, Searchparameters, CodeSystems and ValueSets that come with the FHIR Specification. Beyond that you can use the Administration API to make Firely Server aware of:
Custom profiles, e.g. national or institutional restrictions on the standard FHIR resources.
Custom resources: you can even define resources beyond those in FHIR and they are treated as if they were standard FHIR resources.
CodeSystem and ValueSet resources for terminology.
Custom Searchparameters: have Firely Server index and search resources on properties that are not searchable with the searchparameters from the FHIR Specification itself.
Read more on Firely Server:
Plugins
A plugin is a library of code that you can buy, clone or create yourself that implements additional or replacement functionality in Firely Server. Examples are:
Implementation of a custom operation. E.g. $document (generate a document Bundle based on a Composition resource), which is available on GitHub. Or $transform (execute a FHIR Mapping on a source structure to produce a target structure), which is developed by Healex and can be bought separately.
Implementation of a cross-cutting concern. Imagine that in your organization every resource that is created or updated must be logged to a very specific location. You may create a plugin that does exactly that.
Special handling of specific requests. E.g. requests for a Binary resource where you need to merge in binary data from one of your systems.
Provide custom authentication and authorization methods for compliancy with business or governmental rules.
In all cases, a Plugin is technically a .NET Core assembly (or a set of them) containing well-defined configuration methods that allow Firely Server to:
add services
add a processor to the request processing pipeline
Most plugins do both, registering (testable) services that do the actual work and a thin layer around it that adds it as a processor to the pipeline.
Read more on Firely Server Plugins.
View the session on Plugins from DevDays 2018.
Facades
A Facade is a Firely Server processing pipeline working on an existing data repository. That repository could be the database of proprietary system, some API of an existing system or a whole Clinical Data Repository specifically created to open up data through a FHIR API.
The implementation of a Facade is a special type of plugin that registers services to access the existing data repository. By building the data access layer you leverage all of the FHIR processing in Firely Server, connected to your repository - thus creating a FHIR RESTful API for that repository with the least amount of work.
So a Facade is still a Plugin, and therefore technically a .NET Core assembly (or a set of them) having the same well-defined configuration methods. In the case of a Facade it usually only registers services (and no processor), specifically implementing the interfaces that define the data access layer in Firely Server:
ISearchRepository, for reading and searching
IResourceChangeRepository: for creating, updating, and deleting
Read more on Firely Server Facade.
View the session on Facade from DevDays 2018.
Getting Started
If you want to start using the standard Firely Server in your own Windows environment, follow the steps on this page to install
and run the server.
For non Windows systems, or if you want to use Docker for Windows, please look at the Using Firely Server on Docker section.
Download the Firely Server binaries and the license file from Simplifier.net.
Extract the downloaded files to a location on your system, for example:
C:FirelyServer
. We will call this the working directory.Put the license file in the working directory.
In the working directory create a new JSON file and name it
appsettings.json
. You will use this file for settings that you want to differ from the defaults inappsettings.default.json
. For more background on how the settings are processed, see Firely Server settings
5. Open appsettings.json
, copy the LicenseFile
setting from appsettings.default.json
to it and change this property to the name of your license file. For example
"License": {
"LicenseFile": "firelyserver-trial-license.json"
}
Important
The next step assumes you have a .Net Core environment installed. If not, please download and install ASP.NET Core Runtime 3.1.x before you continue. Choose the latest security patch to mitigate security issues in previous versions.
Open a command prompt or Powershell, navigate to the working directory and run:
> dotnet .Firely.Server.dll
Firely Server will then run on port 4080 of the system.
If you want to check if Firely Server is running correctly, open a browser and go to
localhost:4080
. You should see a homepage similar to this:

Please note that the third example query /Patient/example
will only work if you first PUT a Patient with the id ‘example’.
You can get this example from the specification.
Configuration
The section Configuring Firely Server explains how you can configure the Firely Server.
Running the server
When you have completed your configuration changes, you can run the server. Open a command prompt or Powershell, navigate to your working directory and run:
> dotnet .\Firely.Server.dll
Release notes Firely Server
Older Vonk Release notes
Release 2.2.0
Database
SQL Server: the index tables have their clustered index on their link to the resources. SQL script ‘20190919000000_Cluster_Indexes_On_EntryId.sql’ (found in the /data folder of the Vonk distribution) must be applied to existing Vonk SQL databases (both to the admin and to the data repositories) to make this change.
Attention
Vonk 2.2.0 (using SQL server) will not start unless this script has been applied to the databases. Please note that running the script can take considerable time, especially for large databases.
Features
When running Vonk in IIS, it now profits from the in-process hosting model that ASP.NET Core offers.
Further improved concurrent throughput for SQL Server.
Fix
On errors in a transaction, Vonk would not point out the entry if it had no fullUrl. Improved this message by using the resourcetype and id (if present) instead.
_include gave a 500 responsecode if a resource contains absolute references.
Release 2.1.0
Database
SQL Server: Improved concurrent throughput.
Features
Upgrade to HL7.Fhir.Net API 1.3, see the Older SDK release notes.
Vonk calls
UseIISIntegration
for better integration with IIS (if present).
Fix
Transactions: references to other resources in the transaction were not updated if the resource being referenced was in the transaction as an update (PUT). (this error was introduced in 2.0.0).
Release 2.0.1 hotfix
Fix
Supported Interactions were not checked for custom operations. In the appsettings.json the custom operations, like $meta, were ignored. This has been fixed now.
Release 2.0.0 final
This is the final release of version 2.0.0, so the -beta is off. If you directly upgrade from version 1.1, please also review all the 2.0.0-beta and -beta2 release notes below.
Attention
We upgraded the version of .NET Core used to 2.2. Please get the latest 2.2.x runtime from the .NET download site. The update was needed for several security patches and speed improvements.
Attention
The structure of the Validation section in the settings has changed. See Validating incoming resources for details.
Attention
This version of Vonk is upgraded to the Hl7.Fhir.API version 1.2.0. Plugin- and Facade builders will transitively get this dependency through the Vonk.Core package.
Database
No changes have been made to any of the database implementations.
Fix
When you created a StructureDefinition for a new resourcetype on /administration, the corresponding endpoint was not enabled.
Vonk does not update references in a transaction when a conditional create is used for an existing resource.
Paths in PipelineOptions would interfere if one was the prefix of the other.
Indexing a HumanName with no values but just extensions failed.
The selflink in a bundle did not contain the sort parameters. In this version the selflink always contains a sort and a count parameter, even if they were not in the request and the default values have been applied.
The import of conformance resources from specification.zip yielded warnings on .sch files.
Errors introduced in the 2.0.0-beta versions:
Syntax errors in the XML or JSON payload yielded an exception, now they are reported with an OperationOutcome upon parsing.
$expand and other terminology operations caused a NullReference exception.
_element did not include the mandatory elements.
Feature
Vonk supports Custom Resources. See Custom Resources.
Operation $meta is now supported, to quickly get the tags, security labels and profiles of a resource.
/metadata, retrieving the CapabilityStatement performs a lot better (just the initial call for a specific Accept-Type takes a bit longer).
Validation can be controlled more detailed. Choose the strictness of parsing independent of the level of validation. With this, the settings section ‘Validation’ has also changed. See Validating incoming resources.
Plugin and Facade API
We upgraded the embedded Fhir.Net API to version 1.2, see the Older SDK release notes.
Together with the upgrade to .NET Core 2.2, several libraries were updated as well. Most notably Microsoft.EntityFrameworkCore.*, to 2.2.3.
Release 2.0.0-beta2
Fix
Fixed RelationalQuery in Vonk.Facade.Relational, so Vonk.Facade.Starter can be used again.
Release 2.0.0-beta
We have refactored Vonk internally to accomodate future changes. There are only minor functional changes to the FHIR Server. Facade and Plugin builders must be aware of a few interface changes, most notably to the IResource interface.
This release is a beta release because of the many internal changes, and because we expect to include a few more in the final release. Have a go with it in your test environment to see whether you encounter any trouble. We also encourage you to build your plugin and/or facade against it to prepare for code changes upon the final release.
You can still access the latest final release (1.1.0):
Binaries: through the Simplifier downloads page, choose ‘List previous versions’.
Docker:
docker pull simplifier/vonk:1.1.0
NuGet:
<PackageReference Include="Vonk.Core" Version="1.1.0" />
Database
No changes have been made to any of the database implementations.
Fix
The $validate operation processes the profile parameter.
If an update brings a resource ‘back to life’, Vonk returns statuscode 201 (previously it returned 200).
On an initial Administration Import of specification.zip, Vonk found an error in valueset.xml. This file was fixed in the specification.zip that comes with Fhir.NET API 1.1.2.
Transaction: references within the transaction are automatically changed to the id’s the referenced resources get from Vonk when processing the transaction. This did not happen for references inside extensions. It does now.
Administration Import: an Internal Server Error could be triggered with a zip file with nested directories in it.
NB: Directories in your zip are still not supported because of Fhir.NET API issue #883, but Vonk will not error on it anymore.
Search: The entry.fullUrl for an OperationOutcome in a Search bundle had a relative url.
Search: Processed _elements and _summary arguments were not reported in the selflink of the bundle (or any of the paging links).
Search: The selflink will include a _count parameter, even if it was not part of the request and hence the default value for _count from the BundleOptions was applied.
Search on :exact with an escaped comma (e.g.
/Patient?name:exact=value1\,value2
) was executed as a choice. Now the escape is recognized, and the argument processed as one term.
Feature
Upgraded Fhir.NET API to version 1.1.2, see the Older SDK release notes.
The Vonk Administration API now allows for StructureMap and GraphDefinition resources to be loaded.
The opening page of Vonk (and the only UI part of it) is updated. It no longer contains links that you can only execute with Postman, and it has a button that shows you the CapabilityStatement.
We published our custom operations on Simplifier! And integrated those links into the CapabilityStatement.
You can now access older versions of the Vonk binaries through the Simplifier downloads. (This was already possible for the Docker images and NuGet packages through their respective hubs).
Vonk.IdentityServer.Test and Vonk.Facade.Starter have been integrated into the Continuous Integration system.
In JSON, the order of the output has changed:
If id and/or meta elements were added by Vonk (on a create or update), they will appear at the end of the resource.
Plugin and Facade API
IResource interface and related classes have had several changes. If you encounter problems with adapting your code, please contact us.
It derives from the ISourceNode interface from the Fhir.NET API.
Change and Currency are properties that were only relevant in the repository domain, and not in the rest of the pipeline. They have been deprecated. You can access the values still with resource.GetChangeIndicator() and resource.GetCurrencyIndicator(). This is implemented with Annotations on the ISourceNode. All of Vonk’s own implementations retain those annotations, but if the relevant annotation is somehow missing, default values are returned (ResourceChange.NotSet resp. ResourceCurrency.Current).
The Navigator property is obsolete. The type of it (IElementNavigator) is obsolete in the Fhir.NET API. To run FhirPath you provide type information and run the FhirPath over an ITypedElement:
//Have IStructureDefinitionSummaryProvider _schemaProvider injected in the constructor. var typed = resource.ToTypedElement(_schemaProvider); var matchingElements = typed.Select('your-fhirpath-expression');
Id, Version and LastUpdated can no longer be set directly on the IResource instance. IResource has become immutable (just like ISourceNode). The alternatives are:
var resourceWithNewId = resource.SetId("newId"); var resourceWithNewVersion = resource.SetVersion("newVersion"); var resourceWithNewLastUpdated = resource.SetLastUpdated(DateTimeOffset.UtcNow);
Because the IChangeRepository is responsible for creating new id’s and versions, we also included extensions methods on it to update all three fields at once:
var updatedeResource = changeRepository.EnsureMeta(resource, KeepExisting.Id / Version / LastUpdated); var updatedResource = changeRepository.FreshMeta(resource); //replaces all three
The PocoResource class is obsolete. To go from a POCO (like an instance of the Patient class) to an IResource, use the ToIResource() extension method found in Vonk.Fhir.R3.
The PocoResourceVisitor class is obsolete. Visiting can more effectively be done on an ITypedElement:
//Have IStructureDefinitionSummaryProvider _schemaProvider injected in the constructor. var typed = resource.ToTypedElement(_schemaProvider); typed.Visit((depth, element) => {//do what you want with element});
SearchOptions has changed:
Properties Count and Offset have been removed.
Instead, use _count and _skip arguments in the IArgumentCollection provided to the SearchRepository.Search method if you need to.
We have created a template for a plugin on GitHub. Fetch it for a quick start of your plugin.
Release 1.1.0
Attention
New security issues have been identified by Microsoft. See the Security notifications for Firely Server for details.
Attention
The setting for the location of the license file has moved. It was in the top level setting LicenseFile
. It still has the same name, but it has moved into the section License
. See License for details.
Attention
This version of Vonk is upgraded to the Hl7.Fhir.API version 1.1.1. Plugin- and Facade builders will transitively get this dependency through the Vonk.Core package.
Database
No changes have been made to any of the database implementations.
Feature
Vonk will count the number of requests that it processes. See License for settings on that. Because of this change, the
LicenseFile
setting has moved from the top level to underLicense
.The plugin folder (Configuring the Firely Server Pipeline) may now contain subfolders. Plugins will be read from all underlying folders.
Vonk supports If-Match on update. See Managing Resource Contention in the specification for details.
Plugins may return non-FHIR content. See Returning non-FHIR content from a plugin.
This feature may also be used for Custom Authentication.
A Template for a plugin is added to the documentation.
A documentation page on performance is added: vonk_performance.
Upgrade of the Hl7.Fhir.API library to 1.1. See the Older SDK release notes.
Fix
Transaction: forward references from one resource to another in a Transaction were not correctly resolved.
When you set ValidateIncomingResources to true, Vonk no longer accepts resources with extensions that are unknown to it. This is now also reflected in the CapabilityStatement.acceptUnknown.
The links in a bundle response (
Bundle.link
) were relative links. Now they are absolute links.HTTP 500 instead of an OO was returned when trying to update a subscription with an invalid request status.
If an error is found in a SearchParameter in the Administration database, Vonk logs the (canonical) url of that SearchParameter for easier reference.
Transaction: Response bundle contained versioned fullUrls. We changed that to unversioned urls.
Bundles: Response bundles with an OperationOutcome contained a versioned fullUrl for the entry containing the OperationOutcome. We changed that to an unversioned url.
Deleting a resource from the Administration API that does not exist would lead to an internal server error.
Supported Plugins
Several fixes have been done on the Document plugin.
Release 1.0.0
Yes! Vonk version 1.0 is out. It is also the first version that is released withouth the -beta postfix. It has been very stable from the very first version, and now we think it is time to make that formal.
Release 1.0.0 is functionally identical to 0.7.4.0. But we optimized the deployment process for Yellow Button - Firely Server for your Simplifier project and Docker in general. The contents of the core specification are now preloaded in the SQLite administration database, so your first startup experience is a lot faster.
Release 0.7.4.0
Database
The index definitions for SQL Server have been updated for improved performance. This should be handled automatically when you start Vonk 0.7.4 and have AutoUpdateDatabase enabled.
Fix
Posting a resource with an invalid content-type to the regular FHIR endpoint should result in HTTP 415 and not HTTP 400.
Warning ‘End method “PocoResourceVisitor.VisitByType”, could not cast entity to PocoResource.’ in the log was incorrect.
When running Administration API on SQLite and Vonk on SQL Server, update or delete would fail.
Handle quantity with very low precision (e.g. ‘3 times per year’ - 3|http://unitsofmeasure.org|/a).
POST to <vonk_base>/Administration/* with another Content-Type than application/json or application/xml results in HTTP 500.
Feature
Support forward references in a Transaction bundle. Previously Vonk would only process references back to resources higher up in the bundle.
Performance of Validation and Snapshot Generation has improved by approximately 10 times…
… and correctness has improved as well.
Administration API also support the NamingSystem resource.
Release 0.7.3.0
Fix
Search on /administration/Subscription was broken
Neater termination of the Subscription evaluation process upon Vonk shutdown
A Bundle of type batch is now rejected if it contains internal references.
Urls in the narrative (href and src) are also updated to the actual location on the server.
A system wide search on compartment returns 403, explaining that that is too costly.
Release 0.7.2.1
Fix
Delete on /administration was broken.
Release 0.7.2.0
Database
Fixes 2 and 3 require a reindex for specific searchparameters, if these parameters are relevant to you.
Features and fixes
Fix: Reject a search containing a modifier that is incorrect or not supported.
Fix: The definition for searchparameter Encounter.length was unclear. We added the correct definition from FHIR R4 to the errata.zip, so it works for STU3 as well. If this is relevant for you, you may want to reindex for this searchparameter. See Rebuild the search index for specific searchparameters, just for ‘Encounter.length’.
Fix: Error “Unable to index for element of type ‘base64Binary’”. This type of element is now correctly indexed. One known searchparameter that encounters this type is Device.udi-carrier. If this is relevant to you, you may want to reindex for this searchparameter. See Rebuild the search index for specific searchparameters, just for ‘Device.udi-carrier’.
Fix: Validation would fail on references between contained resources. See also fix #423 in the Older SDK release notes.
Fix: E-tag was missing from the response on a delete interaction.
Fix: An invalid mimetype in the _format parameter (like _format=application/foobar) returned response code 400 instead of 415.
Fix: If a subscription errors upon execution, not only set the status to error, but also state the reason in Subscription.error for the user to inspect.
Fix: Search on /Observation?value-string:missing=false did not work. As did the missing modifier on other searchparameters on value[x] elements.
Feature: After /administration/importResources (see Load Conformance Resources on demand), return an OperationOutcome detailing the results of the operation.
Feature: Upon usage of a wrong value for _summary, state the possible, correct values in the OperationOutcome.
Feature: Allow for multiple deletes with a Conditional Delete, see Create, read, update, patch, delete.
Feature: The version of Vonk is included in the log file, at startup.
Configuration: Add Vonk.Smart to the PipelineOptions by default, so the user only needs to set the SmartAuthorizationOptions.Enabled to true.
Upgrade: We upgraded to the latest C# driver for MongoDb (from 2.4.4 to 2.7.0).
Release 0.7.1.1
Fix
Spinning up a Docker container would crash the container because there was no data directory for SQlite (the default repository). This has been solved now: Vonk will create the data directory when it does not exist.
Release 0.7.1.0
Attention
Fix nr. 8 requires a reindex/searchparameters with include=Resource._id,Resource._lastUpdated,Resource._tag
.
Please review Re-indexing for new or changed SearchParameters on how to perform a reindex and the cautions that go with it.
Also note the changes to reindexing in fix nr. 1.
Database
We added support for SQLite! See Using SQLite for details.
We also made SQLite the default setting for both the main Vonk database and the Firely Server Administration API.
With the introduction of SQLite we advise running the Administration API on SQLite. In the future we will probably deprecate running the Administration API on any of the other databases.
Support for CosmosDB is expanded, though there are a few limitations.
Facade
If you rejected the value for the _id searchparameter in your repository, Vonk would report an InternalServerError. Now it reports the actual message of your ArgumentException.
Features and fixes
We sped up Re-indexing for new or changed SearchParameters. The request will be responded to immediately, while Vonk starts the actual reindex asynchronously and with many threads in parallel. Users are guarded against unreliable results by blocking other requests for the duration of the reindex. Reindexing is still not to be taken lightly. It is a very heavy operation that may take very long to complete. See Re-indexing for new or changed SearchParameters for details.
A really large bundle could lead Vonk (or more specifically: the validator in Vonk) to a StackOverflow. You can now set limits to the size of incoming data to avoid this.
Reindexing is supported on CosmosDB, but it is less optimized than on MongoDB.
Using _include or _revinclude would yield an OperationOutcome if there are no search results to include anything on. Fixed that to return 404 as it should.
Using the :not modifier could return false positives.
A batch or transaction with an entry having a value for IfModifiedSince would fail.
History could not be retrieved for a deleted resource. Now it can.
Reindex would ignore the generic searchparameters defined on Resource (_id, _lastUpdated, _tag). Because id and lastUpdated are also stored apart from the search index, this was really only a problem for _tag. If you rely on the _tag searchparameter you need to reindex just for the searchparameter ``Resource._tag``.
Vonk logs its configuration at startup. See Log of your configuration for details.
Release 0.7.0.0
Database
Indexes on the SQL Server repository were updated to improve performance. They will automatically be applied with AutoUpdateDatabase.
Facade
Release 0.7.0.0 is compatible again with Facade solutions built on the packages with versions 0.6.2, with a few minor changes. Please review the Vonk.Facade.Starter project for an example of the necessary adjustments. All the differences can be seen in this file comparison.
Fix: The SMART authorization failed when you don’t support all the resourcetypes. It will now take into account the limited set of supported resourcetypes.
Fix: Vonk.Facade.Relational.RelationalQueryFactory would lose a _count argument.
Documentation: We added documentation on how to implement Create, Update and Delete in a facade on a relational database. See Enable changes to the repository. This is also added to the example Facade solution on GitHub.
Features and fixes
Feature: Vonk FHIR Plugins has been released. You can now add libraries with your own plugins through configuration.
Feature: Through Vonk FHIR Pluginss you can replace the landing page with one in your own style. We provided an example on how to do that.
Feature: You can now start Vonk from within another directory than the Vonk binaries directory, e.g.
c:\programs>dotnet .\vonk\vonk.server.dll
.Feature: You can configure the maximum number of entries allowed in a Batch or Transaction, to avoid overloading Vonk. See Batch and transaction.
Upgrade: We upgraded the FHIR .NET API to version 0.96.0, see the Older SDK release notes for details. Mainly #599 affects Vonk, since it provides the next…
Fix: Under very high load the FhirPath engine would have concurrency errors. The FhirPath engine is used to extract the search parameters from the resources. This has been fixed.
Fix: Search on a frequently used tag took far too long on a SQL Server repository.
Fix: The Patient.deceased search parameter from the specification had an error in its FhirPath expression. We put a corrected version in the errata.zip.
Fix: Several composite search parameters on Observation are defined incorrectly in the specification, as is reported in GForge issue #16001. Until the specification itself is corrected, we provide corrections in the errata.zip.
Fix: Relative references in a resource that start with a forward slash (like
/Patient/123
) could not be searched on.Fix: System wide search within a compartment looked for the pattern
<base>/Patient/123/?_tag=bla
. Corrected this to<base>/Patient/123/*?_tag=bla
Fix: When loading Simplifier resources, Vonk can now limit this to the changes since the previous import, because the Simplifier FHIR endpoint supports _lastUpdated.
Fix: Conformance resources are always loaded into the Administration API when running on a Memory repository. Or actually, always if there are no StructureDefinitions in the Administration database. To enable this change, imported files are no longer moved to the AdministrationOptions.ImportedDirectory.
Fix: Re-indexing for new or changed SearchParameters would stop if a resource was encountered that could not properly be indexed. It will now continue working and report any errors afterwards in an OperationOutcome.
Fix: The terms and privacy statement on the default landing page have been updated.
Fix: When searching on a search parameter of type date, with an argument precision to the minute (but not seconds), Vonk would reject the argument. It is now accepted.
Fix: DateTime fields are always normalized to UTC before they are stored. This was already the case on MongoDb, and we harmonized SQL and Memory to do the same. There is no need to reindex for this change.
Fix: When you use accents or Chinese characters in the url for a search, Vonk gives an error.
Fix: A reverse chained search on MongoDb sometimes failed with an Internal Server Error.
Release 0.6.5.0
Attention
This version changes the way conformance resources are loaded from zip files and/or directories at startup. They are no longer loaded only in memory, but are added to the Administration API’s database. You will notice a delay at first startup, when Vonk is loading these resources into the database. See Feature #1 below.
Attention
2018-06-07: We updated the Database actions for 0.6.5.0, you should always perform a reindex, see right below.
Database
Feature 2, 4 and 14 below require a reindex/all, both for MongoDB and SQL Server.
Facade
Release 0.6.5.0 is not released on NuGet, so the latest NuGet packages have version 0.6.2-beta. Keep an eye on it for the next release…
Features and fixes
Feature: Run Vonk from you Simplifier project! See Use Firely Server with your Simplifier artifacts for details.
Feature: Vonk supports Microsoft Azure CosmosDB, see Using Microsoft Azure CosmosDB. This required a few small changes to the MongoDB implementation (the share the drivers), so please reindex your MongoDB database: reindex/all.
Feature: Configuration to restrict support for ResourceTypes, SearchParameters and CompartmentDefinitions, see Restrict supported resources and SearchParameters.
Feature: Errata.zip: collection of corrected search parameters (e.g. that had a faulty expression in the FHIR Core specification), see Errata to the specification
Upgrade: FHIR .NET API 0.95.0 (see the Older SDK release notes)
Fix: a search on _id:missing=true was not processed correctly.
Fix: better distinction of reasons to reject updates (error codes 400 vs. 422, see RESTful API specification
Fix: recognize _format=text/xml and return xml (instead of the default json)
Fix: handling of the :not modifier in token searches (include resource that don’t have a value at all).
Fix: handling of the :not modifier in searches with choice arguments
Fix: fullUrl in return bundles cannot be version specific.
Fix: evaluate _count=0 correctly (it was ignored).
Fix: correct error message on an invalid _include (now Vonk tells you which resourcetypes are considered for evaluating the used searchparameter).
Fix: indexing of Observation.combo-value-quantity failed for UCUM code for Celcius. This fix requires a reindex/all on this searchparameter.
Fix: total count in history bundle.
Fix: on vonk.fire.ly we disabled validating all input, so you can now create or update resources also if the relevant profiles are not loaded (this was neccessary for Crucible, since it references US Core profiles, that are not present by default).
Fix: timeout of Azure Web App on first startup of Vonk - Vonk’s first startup takes some time due to import of the specification (see Default Conformance Resources). Since Azure Web Apps are allowed a startup time of about 3 minutes, it failed if the web app was on a low level service plan. Vonk will now no longer await this import. It will finish startup quickly, but until the import is finished it will return a 423 ‘Locked’ upon every request.
Fix: improved logging on the import of conformance resources at startup (see Import of Conformance Resources).
Release 0.6.4.0
Attention
This version changes the way conformance resources are loaded from zip files and/or directories at startup. They are no longer loaded only in memory, but are added to the Administration API’s database. You will notice a delay at first startup, when Vonk is loading these resources into the database. See Feature #1 below.
Database
Fix #9 below requires a reindex/all.
Facade
Release 0.6.4.0 is not released on NuGet, so the latest NuGet packages have version 0.6.2-beta. This release is targeted towards the Administration API and Terminology services, both of which are not (yet) available in Facade implementations. We are working on making the features of the Administration API available to Facade implementers in an easy way.
Features and fixes
Feature: Make all loaded conformance resources available through the Administration API.
Previously:
Only SearchParameter and CompartmentDefinition resources could be loaded from ZIP files and directories;
And those could not be read from the Administration API.
Now:
The same set of (conformance) resourcetypes can be read from all sources (ZIP, directory, Simplifier);
They are all loaded into the Administration database and can be read and updated through the Administration API.
Refer to Conformance Resources for details.
Feature: Experimental support for Terminology services operations $validate-code, $expand, $lookup, $compose.
Feature: Support for Compartment Search.
Feature: Track timing of major dependencies in Azure Application Insights.
Feature: Log settings can be overridden in 4 levels, just as the appsettings. The logsettings.json file will not be overwritten anymore by a Vonk distribution.
Fix: The check for allowed profiles is no longer applied to the Administration API. Previously setting AllowedProfiles to e.g. [http://mycompany.org/fhir/StructureDefinition/mycompany-patient] would prohibit you to actually create or update the related StructureDefinition in the Administration API.
Fix: When posting any other resourcetype than the supported conformance resources to the Administration API, Vonk now returns a 501 (Not Implemented).
Fix: Support search on Token with only a system (e.g.
<base>/Observation?code=http://loinc.org|
)Fix: Support search on Token with a fixed system, e.g.
<base>/Patient?gender=http://hl7.org/fhir/codesystem-administrative-gender.html|female
. This fix requires a reindex/all.Fix: Reindex could fail when a Reference Searchparameter has no targets.
Fix: Vonk works as Data Server on ClinFHIR, with help of David Hay.
Fix: Clearer error messages in the log on configuration errors.
Fix: Loading conformance resources from disk in Docker.
Documentation
We added documentation on using IIS or NGINX as reverse proxies for Vonk.
We added documentation on running Vonk on Azure Web App Services.
Release 0.6.2.0
Attention
The loading of appsettings is more flexible. After installing a new version you can simply paste your previous appsettings.json in the Vonk directory. Vonk’s default settings are now in appsettings.default.json. see Firely Server settings for details.
Database
No changes
Features and fixes
Feature: Conditional References in Transactions are resolved.
Feature: More flexible support for different serializers (preparing for ndjson in Bulkdata)
Feature: Improved handling on missing settings or errors in the Firely Server settings.
Feature: Improved logging, including Dependency Tracking on Azure Application Insights, see Application Insights
Feature: SearchParameter and CompartmentDefinition are now also imported from Simplifier, so both Simplifier import and the Administration API support the same set of conformance resources: StructureDefinition, SearchParameter, CompartmentDefinition, ValueSet and CodeSystem. See Conformance resources.
Feature: Loading of appsettings is more flexible, see Firely Server settings.
Feature: Added documentation on running Vonk behind IIS or NGINX: Deploy Firely Server on a reverse proxy.
Performance: Improvement in speed of validation, especially relevant if you are Validating incoming resources.
Fix: If you try to load a SearchParameter (see Load Conformance Resources from disk) that cannot be parsed correctly, Vonk puts an error about that in the log.
Fix: Results from _include and _revinclude are now marked with searchmode: Include (was incorrectly set to ‘Match’ before)
Fix: _format as one of the parameters in a POST Search is correctly evaluated.
Fix: No more errors in the log about a Session being closed before the request has finished (“Error closing the session. System.OperationCanceledException: The operation was canceled.”)
Fix: Subscription.status is evaluated correctly upon create or update on the Administration API
Fix: Token search with only a system is supported (
Observation.code=somesystem|
)Fix: On validation errors like ‘Cannot resolve reference Organization/Organization-example26”’ are now suppressed since the validator is set not to follow these references.
Fix: New Firely logo in SVG format - looks better
Fix: Creating resources with duplicate canonical url’s on the Administration API is prohibited, see Conformance Resources.
Fix: If a Compartment filter is used on a parameter that is not implemented, Vonk will return an error, see Compartments.
Release 0.6.1.0
Name change from Furore to Firely
Release 0.6.0.0
Attention
SearchParametersImportOptions is renamed to MetadataImportOptions.
Subscription can now be disabled from the settings.
Database
The MongoDB implementation got a new index. It will be created automatically upon startup.
Features and fixes
Feature: Access control based on SMART on FHIR.
Feature: Vonk can also load CompartmentDefinition resources. See Conformance Resources for instructions.
Feature: ValueSet and CodeSystem resources can be loaded into the administration endpoint, and loaded from Simplifier. See Conformance Resources for instructions.
Feature: Be lenient on trailing slashes in the url.
Feature: OperationOutcome is now at the top of a Bundle result. For human readers this is easier to spot any errors or warnings.
Fix: In the settings for SQL Server it was possible to specify the name of the Schema to use for the Vonk tables. That was actually not evaluated, so we removed the option for it. It is fixed to ‘vonk’.
Fix: The OperationOutcome of the Reset operation could state both an error and overall success.
Fix: If you did not set the CertificatePassword in the appsettings, Vonk would report a warning even if the password was not needed.
Fix: Loading conformance resources in the SQL Server implementation could lead to an error.
Fix: Clearer error messages if the body of the request is mandatory but empty.
Fix: Clearer error message if the Content-Type is missing.
Fix: GET on [base]/ would return the UI regardless of the Accept header. Now if you specify a FHIR mimetype in the Accept header, it will return the result of a system wide search.
Fix: In rare circumstances a duplicate logical id could be created.
Fix: GET [base]/metadat would return status code 200 (OK). But it should return a 400 and an OperationOutcome stating that ‘metadat’ is not a supported resourcetype.
Documentation
We consolidated documentation on loading conformance resources into Conformance Resources.
Release 0.5.2.0
Attention
Configuration setting SearchOptions is renamed to BundleOptions.
Features and fixes
Fix: When you specify LoadAtStartup in the ResourceLoaderOptions, an warning was displayed: “WRN No server base configured, skipping resource loading.”
Fix: Conditional create that matches an existing resource returned that resource instead of an OperationOutcome.
Fix: _has, _type and _count were in the CapabilityStatement twice.
Fix: _elements would affect the stored resource in the Memory implementation.
Fix: Getting a resource with an invalid id (with special characters or over 64 characters) now returns a 404 instead of 501.
Feature: Re-indexing for new or changed SearchParameters now also re-indexes the Administration API database.
Fix: modifier :above for parameter type Url now works on the MongoDB implementation.
Fix: Vonk would search through inaccessible directories for the specification.zip.
Fix: Subscription could not be posted if ‘Database’ was not one of the SearchParametersImportOptions.
Fix: _(rev)include=* is not supported but was not reported as such.
Fix: In a searchresult bundle, the references to other resources are now made absolute, refering to the Vonk server itself.
Fix: BundleOptions (previously: SearchOptions) settings were not evaluated.
Fix: Different responses for invalid resources when you change ValidateIncomingResources setting (400 vs. 501)
Fix: Better reporting of errors when there are invalid modifiers in the search.
Fix: Creating a resource that would not fit MongoDB’s document size resulted in an inappropriate error.
Fix: There was no default sort order in the search, resulting in warnings from the SQL implementation. Added default sort on _lastUpdated (desc).
Fix: Preliminary disposal of LocalTerminology server by the Validator.
Facade
Fix: _include/_revinclude on searchresults having contained resources triggered a NotImplementedException.
Release 0.5.1.1
Facade
We released the Facade libraries on NuGet along with getting started documentation.
No features have been added to the Vonk FHIR Server.
Release 0.5.0.0
Database
Long URI’s for token and uri types are now supported, but that required a change of the SQL Server database structure. If you have AutoUpdateDatabase enabled (see Using SQL server), Vonk will automatically apply the changes. As always, perform a backup first if you have production data in the database.
To prevent duplicate resources in the database we have provided a unique index on the Entry table. This update does include a migration. It can happen that that during updating of your database it cannot apply the unique index, because there are duplicate keys in your database (which is not good). Our advise is to empty your database first (with
<vonk-endpoint>/administration/reset
, then update Vonk with this new version and then run Vonk withAutoUpdateDatabase=true
(for the normal and the administration databases).If you run on production and encounter this problem, please contact our support.
Features and fixes
Feature: POST on _search is now supported
Fix: Statuscode of
<vonk-endpoint>/administration/preload
has changed when zero resources are added. The statuscode is now 200 instead of 201.Fix: OPTIONS operation returns now the capability statement with statuscode 200.
Fix: A search operation with a wrong syntax will now respond with statuscode 400 and an OperationOutcome. For example
GET <vonk-endpoint>/Patient?birthdate<1974
will respond with statuscode 400.Fix: A statuscode 501 could occur together with an OperationOutcome stating that the operation was successful. Not anymore.
Fix: An OperationOutcome stating success did not contain any issue element, which is nog valid. Solved.
Improvement: In the configuration on Load Conformance Resources from simplifier.net the section
ArtifactResolutionOptions
has changed toResourceLoaderOptions
and a new option has been introduced under that section namedLoadAtStartup
which, if set to true, will attempt to load the specified resource sets when you start VonkImprovement: the Memory implementation now also supports
SimulateTransactions
Improvement: the option
SimulateTransactions
in the configuration defaults to false nowFeature: You can now add SearchParameters at runtime by POSTing them to the Administration API. You need to apply Re-indexing for new or changed SearchParameters to evaluate them on existing resources.
Fix: The batch operation with search entries now detects the correct interaction.
Fix: ETag header is not sent anymore if it is not relevant.
Fix: Searching on a String SearchParameter in a MongoDB implementation could unexpectedly broaden to other string parameters.
Fix: If Reference.reference is empty in a Resource, it is no longer filled with Vonks base address.
Feature: Search operation now supports
_summary
.Fix: Paging is enabled for the history interaction.
Fix: Conditional updates won’t create duplicate resources anymore when performing this action in parallel.
Fix: Indexing of CodeableConcept has been enhanced.
Fix: Search on reference works now also for an absolute reference.
Fix: Long uri’s (larger than are 128 characters) are now supported for Token and Uri SearchParameters.
Improvement: The configuration of IP addresses in Limited access has changed. The format is no longer a comma-separated string but a proper JSON array of strings.
Release 0.4.0.1
Database
Long URL’s for absolute references are now supported, but that required a change of the SQL Server database structure. If you have AutoUpdateDatabase enabled, Vonk will automatically apply the changes. As always, perform a backup first if you have production data in the database.
Datetime elements have a new serialization format in MongoDB. After installing this version, you will see warnings about indexes on these fields. Please perform Re-indexing for new or changed SearchParameters, for all parameters with
<vonk-endpoint>/administration/reindex/all
. After the operation is complete, restart Vonk and the indexes will be created without errors.
Features and fixes
Fix: SearchParameters with a hyphen (‘-’, e.g. general-practitioner) were not recognized in (reverse) chains.
Fix: CapabilityStatement is more complete, including (rev)includes and support for generic parameters besides the SearchParameters (like
_count
). Also the SearchParameters now have their canonical url and a description.Improvement: Preloading resources gives more informative warning messages.
Fix: Re-indexing for new or changed SearchParameters did not handle contained resources correctly. If you have used this feature on the 0.3.3 version, please apply it again with
<vonk-endpoint>/administration/reindex/all
to correct any errors.Improvement: Loading resources from Simplifier now also works for the Memory implementation.
Improvements on Validation:
profile parameter can also be supplied on the url
if validation is successful, an OperationOutcome is still returned
it always returns 200, and not 422 if the resource could not be parsed
Feature: support for Conditional Read, honouring if-modified-since and if-none-match headers.
Fix: Allow for url’s longer than 128 characters in Reference components.
Fix: Allow for an id in a resource on a Create interaction (and ignore that id).
Fix: Allow for an id in a resource on a Conditional Update interaction (and ignore that id).
Fix: Include Last-Modified header on Capability interaction.
Fix: Format Last-Modified header in httpdate format.
Fix: Include version in bundle.entry.fullUrl on the History interaction.
Fix: Update
_sort
syntax from DSTU2 to STU3. Note:_sort
is still only implemented for_lastUpdated
, mainly for the History interaction.Improvement: If the request comes from a browser, the response is sent with a Content-Type of application/xml, to allow the browser to render it natively. Note that most browsers only render the narrative if they receive xml.
Release 0.3.3.0
Attention
We upgraded to .NET Core 2.0. For this release you have to install .NET Core Runtime 2.0, that you can download from dot.net.
Hosting
- The options for enabling and configuring HTTPS have moved. They are now in appsettings.json, under ‘Hosting’:
"Hosting": { "HttpPort": 4080, "HttpsPort": 4081, // Enable this to use https "CertificateFile": "<your-certificate-file>.pfx", //Relevant when HttpsPort is present "CertificatePassword" : "<cert-pass>" // Relevant when HttpsPort is present },
This means you have to adjust your environment variables for CertificateFile and CertificatePassword (if you had set them) to:
VONK_Hosting:CertificateFile VONK_Hosting:CertificatePassword
The setting ‘UseHttps’ is gone, in favour of Hosting:HttpsPort.
Database
There are no changes to the database structure.
Features and fixes
Feature: Subscription is more heavily checked on create and update. If all checks pass, status is set to active. If not, the Subscription is not stored, and Vonk returns an OperationOutcome with the errors.
Criteria must all be supported
Endpoint must be absolute and a correct url
Enddate is in the future
Payload mimetype is supported
Feature: use _elements on Search
Feature: load profiles from your Simplifier project at startup.
Feature: Content-Length header is populated.
Fix: PUT or POST on /metadata returned 200 OK, but now returns 405 Method not allowed.
Fix: Sometimes an error message would appear twice in an OperationOutcome.
Fix: _summary is not yet implemented, but was not reported as ‘not supported’ in the OperationOutcome. Now it is. (Soon we will actually implement _summary.)
Fix: If-None-Exist header was also processed on an update, where it is only defined for a create.
Fix: Set Bundle.entry.search.mode to ‘outcome’ for an OperationOutcome in the search results.
UI: Display software version on homepage.
Release 0.3.2.0
Fix: _include and _revinclude could include too many resources.
Release 0.3.1.0
IP address restricted access to Administration API functions.
Fix on Subscriptions:
Accept only Subscriptions with a channel of type rest-hook and the payload (if present) has a valid mimetype.
Set them from requested to active if they are accepted.
Release 0.3.0.0
Database changes
If you have professional support, please consult us on the best way to upgrade your database.
The schema for the SQL Database has changed. It also requires re-indexing all resources.
The (implicit) schema for the documents in the MongoDb database has changed.
The Administration API requires a separate database (SQL) or collection (MongoDb).
New features:
Support for Subscriptions with rest-hook channel
Preload resources from a zip.
Reset database
Conditional create / update / delete
Support for the prefer header
Validation on update / create (can be turned on/off)
Restrict creates/updated to specific profiles.
Configure supported interactions (turn certain interactions on/off)
New search features:
_has
_type
(search on system level)
_list
_revinclude
Enhancements
:exact
: Correctly search case (in)sensitive when the :exact modifier is (not) used on string parameters.Enhanced reporting of errors and warnings in the OperationOutcome.
Custom profiles / StructureDefinitions separated in the Administration API (instead of in the regular database).
Full FHIRPath support for Search Parameters.
Fixed date searches on dates without seconds and timezone
Fixed evaluation of modifier :missing
Correct total number of results in search result bundle.
Fix paging links in search result bundle
Better support for mimetypes.
DevOps:
Enabled logging of the SQL statements issued by Vonk (see Log settings)
Migrations for SQL Server (auto create database schema, also for the Administration API)
Performance
Added indexes to MongoDb and SQL Server implementations.
Security notifications for Firely Server
January 2021
Microsoft has a new Security Advisory regarding ASP.NET Core:
Microsoft Security Advisory CVE-2020-1161 | ASP.NET Core Denial of Service Vulnerability in all ASP.NET Core applications on runtime 3.1.3 or lower (#416). If you are not already up-to-date, you should install the latest runtime version from https://dotnet.microsoft.com/download/dotnet-core/3.1
July 2020
Microsoft has published several newer Security Advisories regarding ASP.NET Core:
Microsoft.ApplicationInsights.AspNetcore 2.12 was vulnerable to CVE-2005-2224. We upgraded it to 2.14.
Microsoft Security Advisory CVE-2020-0602 : ASP.NET Core Denial of Service Vulnerability, #402 and
Microsoft Security Advisory CVE-2020-0603 : ASP.NET Core Remote Code Execution Vulnerability, #403. These affect applications running SignalR. Vonk does not use SignalR. Nevertheless we recommend to follow Microsoft’s advice: For machines running .NET Core 3.1, you should download and install Runtime 3.1.1 or SDK 3.1.101 from https://dotnet.microsoft.com/download/dotnet-core/3.1
Microsoft Security Advisory | MessagePack Denial of Service, #405. This only affect applications using MessagePack, which Vonk does not use.
September 2019
Updates regarding previous Security Advisories:
Please upgrade the ASP.NET Core runtime to at least version 2.2.7, from the runtimes download page. This solves:
#295: Vonk has been upgraded to ASP.NET Core 2.2, and is therefore no longer vulnerable to this issue. It is nevertheless advised to run a publicly exposed Vonk behind a proxy or on an Azure Web App.
#335: no longer relevant to Vonk since it runs on ASP.NET Core 2.2
Microsoft has published several newer Security Advisories regaring ASP.NET Core:
January 2019
Microsoft has published two Security Advisories regarding ASP.NET Core:
If you run Vonk behind Internet Information Server (IIS), you may be vulnerable to “Microsoft Security Advisory CVE-2019-0548: ASP.NET Core Denial Of Service Vulnerability”. Refer to the related Github issue #335 for details and the fix.
When using older versions of some of the ASP.NET Core packages you may be vulnerable to “Microsoft Security Advisory CVE-2019-0564: ASP.NET Core Denial of Service Vulnerability”. Refer to the related Github issue #334 for details. Vonk FHIR Server up until version 1.1.0 uses versions of the packages involved that are not affected (older than the vulnerable versions). In a future version we will upgrade beyond the vulnerable version upto secure versions. No action is required by the administrator of Vonk.
April 2018
Microsoft has published two Security Advisories regarding ASP.NET Core:
If you run Vonk behind Internet Information Server (IIS), you may be affected by “Microsoft Security Advisory CVE-2018-0808: ASP.NET Core Denial Of Service Vulnerability”. Refer to the related GitHub issue #294 for details and the fix.
If you expose Vonk directly to the internet, or host it behind a proxy which does not validate or restict host headers to known good values, you may be affected by “Microsoft Security Advisory CVE-2018-0787: ASP.NET Core Elevation Of Privilege Vulnerability”. Refer to the related GitHub issue #295 for details and the correct way of hosting Vonk. This ‘host validating middleware’ mentioned by this issue is not a part of Vonk. We advise you to run a publicly exposed Vonk behind a proxy or on an Azure Web App.
Upgrading Firely Server
See How to upgrade Firely Server? for information on how to upgrade to a new version of Firely Server.
Public Endpoint Announcement 8 July 2021
The default FHIR version of the public Firely Server endpoint is now R4.
Release 4.5.1
Database
MongoDB
The migration script ‘FS_SchemaUpgrade_Data_v17_v18.js’ has been fixed. All data present in the database before the migration is now again accessible after the migration.
SQL Server
Improved the query performance when using _include by using “WITH FORCESEEK”.
Improved performance by avoiding scanning indexes when searching on the UriHash column
Fix
Firely Server will now by default include a user-agent header when retrieving the SMART Discovery document
Release 4.5.0
Database
Attention
The release version of the MongoDB migration contains an error causing compartment searches to return no search results for all migrated resources. Only newly added resources after the migration will be returned successfully. We will provide a hotfix soon. Please refrain from updating until the hotfix.
MongoDB
To improve the performance of compartment searches, Firely Server now precalculates the compartment links to which a resource belongs on insert in the database. An external migration script ‘FS_SchemaUpgrade_Data_v17_v18.js’ is provided in the distribution. It needs to be applied manually using MongoDB Shell.
Security
A VonkConfigurationException, which was thrown if a SQL database migration could not be performed, included the SQL connection string in plain text in the log. Please check you log files if they include any sensitive information such as the database password, which might have been part of the connection string.
Fix
It is now possible to configure pre- and posthandlers for a custom operations using VonkInteraction.all_custom regardless of the interaction level of the operation handler and the interaction level on which the operation is configured in the appsettings.
$lastN could not handle chained arguments on the subject/patient reference
$lastN reported an invalid error message if the reference to a subject/patient was provided as an urn:uuid reference
$lastN searchresult bundles were missing self-links when no results were found
Disabling Vonk.Fhir.R4 in the pipeline resulted in an internal exception thrown by the ConformanceCache
Feature
$lastN can be combined with _elements and _include parameters
$lastN can group the results by the
component-code
orcombo-code
search parameter
Documentation
Added an explanation to the documentation why the use of
_total=none
influences the performance of a search query.
Plugins
The FHIR Mapper is no longer distributed together with Firely Server. Please contact fhir@healex.systems for any questions regarding the FHIR Mapper.
The packages Vonk.Fhir.R(3|4) depended on an unpublished NuGET package Vonk.Administration.Api.
All classes in the namespace ‘Vonk.Facade.Releational’ are now published on GitHub.
Release 4.5.0-beta
Fix
Security: Added a warning to the documentation that using compartments other than ‘Patient’ to restrict access based on patient-level SMART on FHIR scopes may result in undesired behaviour. See Compartments for more information.
The RequestCountService caused an exception on startup if the RequestInfoFile could not be accessed, e.g. due to limited filesystem permissions. The RequestCountService has been removed completely. Any remaining .vonk-request-info.json files can be deleted manually.
The logsettings for SQL server included an outdated configuration.
The logsettings for MongoDB included an outdated configuration.
Feature ^^^
Improved error messages if an internal exception occured due to failing filesystem access.
The $lastN operation is now available when using SQL Server as the backend for Firely Server. See $lastN Observations for more information.
Plugin and Facade
Added async support for the ISnapshotGenerator interface and its implementations.
Release 4.4.0
Database
MongoDB
To improve the performance of deletes, the definition of the index
ix_container_id
is redefined. Firely Server 4.4.0 will automatically change the definition.
SQL Server
Improved query behind
_include
to leverage an index. No changes to the database schema involved. This only affects the new implementation (available since 4.3.0).
Fix
Improved automatic upgrading of terminology settings from pre-4.1.0 instances.
Added
CapabilityStatement.status
for R4The default
SmartAuthorizationOptions
inappsettings.default.json
only have the Filter for ‘Patient’ enabled. The rest is now commented out as those are generally not used.
Plugin and Facade
The interfaces PrioritizedResourceResolver(R3|R4|R5) and their implementations are no longer available. It is advised to construct your own StructureDefinitionSummaryProvider incl. a MultiResolver combining your own resource resolver and the IConformanceCache provided by Firely Server.
The interface IConformanceCacheInvalidation has been moved from Vonk.Core.Import to Vonk.Core.Conformance
The classes SpecificationZipResolver(R3|R4|R5) are no longer available. Please use the IPrioritizedResourceResolvers instead.
Release 4.3.0
Database
SQL Server
To improve the performance of searching we have rewritten a large part of our SQL Server implementation. To be able to use the new implementation go to section PipelineOptions in
appsettings.default.json
(orappsettings.instance.json
if you have overriden the default pipeline options) and add"Vonk.Repository.Sql.Raw.KSearchConfiguration"
. See Using SQL server for more details.We have identified two indexes that needed a fix to increase query performance for certain searches. The upgrade procedure will try to fix these indexes automatically. If your database is large, this may take too long and the upgrade process will time out. If that happens you need to run the upgrade script manually, The script can be found in
sqlserver/FS_SchemaUpgrade_Data_v19_v20.sql
. If you use SQL Server as your Administration database, Firely Server will try to update it automatically as well. If you prefer a manual update, you can run the following script:sqlserver/FS_SchemaUpgrade_Admin_v18_v19.sql
.
Feature
Firely Server now allows you to execute a ValueSet expansion of large ValueSets (> 500 included concepts). Previously, Firely Server would log an error outlining that the expansion was not possible. The appsettings now contain a setting in the Terminology section allowing to select the MaxExpansionSize. See Options for more details.
Fix
Fixed a NullPointerException which occured when indexing UCUM quantities that contained more than one annotation (e.g. “{reads}/{base}”).
Fixed a bug where it was possible to accidentally delete a resource with a different information model then the request. Firely Server will now check the information model of the request against the information model of the resource for conditional delete and delete requests.
$subsumes returned HTTP 501 - Not implemented for a POST request (instance-level) even if the operation was enabled in the appsettings.
The _type filter on $everything and Bulk data export didn’t allow for resources that are not within the Patient compartment. The operations would return an empty result set.
Added a clarification to the documentation that $everything and Bulk data export do not export Device resources by default. Even though the resource contains a reference to Patient, the corresponding compartment definition for Patient does not include Device as a linked resource. It is possible to export Device resources by adding the resource type to “AdditionalResources” settings of the operations.
Release 4.2.1 hotfix
Database
Note
We found an issue in version 4.2.0, which affects the query performance for Firely Server running on a SQL Server database. If your are running FS v4.2.0 on SQL Server you should upgrade to v4.2.1 or if that is not possible, Contact us.
Attention
The upgrade procedure will execute a SQL script try to validate the foreign key constraints. If your database is large, this may take too long and the upgrade process will time out. If that happens you need to run the upgrade script manually, The script can be found in
data/20210720085032_EnableCheckConstraintForForeignKey.sql
.Here are some guidelines:
We tested it on a database with about 15k Patient records, and 14 million resources in total. The upgrade script took about 20 seconds to complete on a fairly powerful laptop.
As always, make sure you have a backup of your database that has been tried and tested before you begin the upgrade.
If you expect the upgrade to time out, you can choose to run the SQL script manually beforehand. Please make sure that Firely Server is shutdown before you execute the script.
Fix
Fixed a bug where some of the Foreign Keys in SQL Server had become untrusted. This bug has an impact on the query performance since the the SQL Server query optimizer will not consider FKs when they are not trusted. This has been fixed, all Foreign Keys have been validated and are trusted again.
Release 4.2.0
Database
Attention
For SQL Server users: this version of Firely Server running on SQL Server has a bug where some of the Foreign Keys became untrusted. This has an impact on the query performance. Please upgrade to version 4.2.1 or if that is not possible, Contact us. Please note that users running Firely Server running either MongoDb, CosmoDb, or SQLite are not affected by this issue.
Attention
For SQL Server we changed the datatype of the primary keys. The related upgradescript (data/20210519072216_ChangePrimaryKeyTypeFromIntToBigint.sql
) can take a lot of time if you have many resources loaded in your database. Therefore some guidelines:
We tested it on a database with about 15k Patient records, and 14 mln resources in total. Migrating that took about 50 minutes on a fairly powerful laptop.
Absolutely make sure you create a backup of your database first.
If you haven’t done so already, first upgrade to version 4.1.x.
If you already expect the migration might time out, you can run it manually upfront. Shut down Firely Server, so no other users are using the database, and then run the script from SQL Server Management Studio (or a similar tool).
Running the second script (
20210520102224_ChangePrimaryKeyTypeFromIntToBigintBDE.sql
) is optional - that should also succeed when applied by the automigration.
Feature
Terminology operation
$lookup
is now also connected to remote terminology services, if enabled. See Terminology services.We provided a script to ‘purge’ data from a SQL Server database. See
data/20210512_Purge.sql
. You can filter on the resourcetype only. Use with care and after a backup. If you need more elaborate support for hard deletes, please Contact us.
Fix
Firely Server could run out of primary keys on the index tables in SQL Server. Fixed by upgrading to bigint, see warning above.
Nicer handling of SQL Server migration scripts that time out on startup. It will now kindly ask you to run the related script manually if needed (usually depends on the size of your database).
The Patient-everything (
$everything
) operation was not mentioned in the CapabilityStatement.License expired one day too early.
Dependencies have been upgraded to the latest versions compatible with .NET Core 3.1.
PATCH did not allow adding to a repeating element.
If your license does not allow usage of SMART on FHIR, authorization was disabled, emitting a warning in the log. Possibly causing unauthorized access without the administrator noticing it. This specific case will now block the startup of Firely Server.
Release 4.1.3 hotfix
Fix
Fixed a bug where a number of concurrent $transform requests on a freshly started Firely Server could lead to Internal Server Error responses.
Upgraded the Mapping plugin to fhir_mapper_docs:mapping_releasenotes_071.
Release 4.1.2 hotfix
Fix
Fixed a bug when trying to delete multiple resources at once (bulk delete, see Configuration for configuration options). The operation would take a while and eventually return a
204 No Content
without actually deleting any resources. This is fixed, the bulk delete operation now deletes the resources.
Release 4.1.1 hotfix
Feature
SMART configuration: Some identity providers use multiple endpoints with different base addresses for its authorization operations. Added an extra configuration option
AdditionalEndpointBaseAddresses
to define additional base endpoints addresses next to the main authority endpoint to accommodate this. See Configuration for further details.
Fix
Fixed an error in SQL script
data/20210226200007_UpdateIndexesTokenAndDatetime_Up.sql
that is used when manually updating the database to v4.1.0. We alse made the script more robust by checking if the current version the database is suitable for the manual upgrade.
Release 4.1.0
Attention
We have found an issue with SMART on FHIR and searching with _(rev)include. And fixed it right away, see Fix nr 1 below. Your Firely Server might be affected if:
you enabled SMART on FHIR
and used patient/read.* scopes together with a patient compartment
What happens? Patient A searches Firely Server with a patient launch scope that limits him to his own compartment. If any of the resources in his compartment links to another patient (let’s say for Observation X, the performer is Patient B), Patient A could get to Patient B with <base>/Observation?_include=Observation.performer
. If you host Group or List resources on your server, a _revinclude on those might give access to other Patient resources within the same Group or List.
If you think you might be affected you can:
upgrade to version 4.1.0
or if that is not possible, Contact us.
Database
SQL Server
A new index table was added. The upgrade procedure will try to fill this table based on existing data. If your database is large, this may take too long and time out. Then you need to run the upgrade script found in
data/20210303100326_AddCompartmentComponentTable.sql
manually.A new SQL Server index was added to improve query times when searching with date parameters. The upgrade procedure will try to build this index. If your database is large, this may take too long and time out. Then you need to run the upgrade script found in
data/20210226200007_UpdateIndexesTokenAndDatetime_Up.sql
manually.In both cases you may also run the script manually beforehand.
As always: make sure you have a backup of your database that is tested for restore as well.
DevOps
Attention
Because of a change in the devops pipeline there is no longer a Firely.Server.exe
(formerly Vonk.Server.exe
) in the distribution zip file. You can run the server as always with dotnet ./Firely.Server.dll
Features
Inferno, The ONC test tool: Firely Server now passes all the tests in this suite! With version 4.1.0 we specifically added features to pass the ‘Multi-patient API’ tests. Do you want a demo of this? Contact us!.
Terminology support has been revamped. Previously you needed to choose between using the terminology services internal to Firely Server or external terminology services like from OntoServer or Loinc. With this version you can use both, and based on the codesystem or valueset involved the preferred terminology service is selected and queried.
This works for terminology operations like
$validate-code
and$lookup
It also works for validation, both explicitly with
$validate
and implicitly, when validating resources sent to Firely Server.The CodeSystem, ValueSet and ConceptMap resources involved are conformance resources and therefore always retrieved from the Administration database.
Responses may differ on details from previous versions of Firely Server, but still conform to the specification.
See Terminology services for further details.
$everything
: We now support the Patient $everything operation for single Patients. (For multiple patients, there is the Bulk Data Export feature.)Performance of $everything, Bulk Data Export and authorization on compartments improved. We added a special index to the database that keeps track which resource belongs to which compartment. First in SQL Server, MongoDB has less need for it.
SMART on FHIR: Support for token revocation. Reference tokens can be revoked, and Firely Server can check for the revocation.
Fixes
SMART on FHIR: We have found ourselves that the authorization restrictions were bypassed when using _include or _revinclude in a FHIR Search. We solved this security issue immediately.
Firely Server transparently translates absolute urls to relative urls (for internal storage) and back. There was a performance gain to be made in this part, which we did. This is mostly notable on large transaction or batch bundles.
Batch bundles are not allowed to have links between the resources in the entries. Firely Server will now reject batch bundles that have these links. If you need links, use a transaction bundle instead.
Plugin and Facade
We upgraded the Firely .NET SDK to version 3.0.0. This SDK version is almost fully compatible with 2.9, but it brings significant simplifications to its use because the Parameters and OperationOutcome resource POCOs are no longer FHIR-version specific.
Note
Every new version of the SDK brings new versions of the
specification.zip
files. So upon upgrade these new files will be read into the Administration database. See Conformance Resources for more background.
Release 4.0.0
This major version introduces a new name: Firely Server instead of Vonk. Other than that, this release contains some significant code changes, which could impact you if you run Firely Server with your own plugins.
Features
Name change Vonk -> Firely Server:
The main entry point dll (formerly:
Vonk.Server.dll
) and executable (formerly:Vonk.Server.exe
) names have been changed toFirely.Server.dll
andFirely.Server.exe
respectively.The name was changed in the CapabilityStatement.name.
The name of the download zip (from Simplifier) has changed from vonk_distribution.zip to firely-server-latest.zip. Likewise the versioned zip files have changed as well.
We have implemented FHIR Bulk Data Access (
$export
) to allow for fast, asynchronous ndjson data exports. The Bulk Data Export documentation can help you to get started.Firely Server now uses Firely .NET SDK 2.0.2 (formerly: FHIR .NET API)
Attention
If you are running Firely Server with your own self-made plugins, you will likely encounter package versioning problems and need to upgrade your NuGet Firely Server package references (package names starting with
Vonk.
) to version 4.0.0. You also need to upgrade any Firely .NET SDK package references (package names starting withHl7.Fhir.
) to version 2.0.2. The Firely .NET SDK release notes and Breaking changes in Firely SDK 2.0 can give you an idea of the changes you may encounter in the SDK.SMART on FHIR can now recognize prefixes to the claims, see its Configuration.
The smart-configuration endpoint (<url>/.well-known/smart-configuration) relays the signature algorithms configured in the authorization server.
Fixes
Application Insights has now been disabled by default. If you need Application Insights, you can enable it in your log settings file by including the entire section mentioned in Application Insights log settings.
When validating a resource, a non-existing code would lead to an OperationOutcome.issue with the code
code-invalid
. That issue code has been changed tonot-supported
.On a batch or transaction bundle errors were not reported clearly if the entry in error had no fullUrl element. We fixed this by referring to the index of the entry in the entry array, and the resourcetype of the resource in the entry (if any).
The
import[.R4]
folder allows for importing custom StructureDefinition resources. If any of them had no id, the error on that caused an exception. Fixed that.If a Facade returned a resource without an id from the Create method, an error was caused by a log statement. Fixed that.
Indexing
Subscription.channel[0].endpoint[0]
failed for R4. Fixed that. This means you can’t search for existing Subscriptions bySubscription.url
on the /administration endpoint for FHIR R4.Postman was updated w.r.t. acquiring tokens. We adjusted the documentation on that accordingly.
If a patient claim was included in a SMART on FHIR access token, the request would be scoped to the Patient compartment regardless of the scope claims. We fixed this by allowing “user” scopes to access FHIR resources outside of the Patient compartment regardless of the patient claim. See Launch context arrives with your access_token for more background information.
Plugin and Facade
The mapping plugin is upgraded to the Mapping Engine 0.6.0, see its releasenotes.
As announced in Release 3.0.0 we removed support for creating a Facade as a standalone ASP.Net Core project. You can now only build a Facade as a plugin to Firely Server. See Firely Server Facade on how to do that.
The order of some plugins has changed. This way it is possible to add a plugin between PreValidation and UrlMapping:
UrlMapping: from 1230 to 1235
Prevalidation: from 4320 to 1228
A Facade based on
Vonk.Facade.Relational
no longer defaults to STU3Attention
If you developed a facade plugin based on
Vonk.Facade.Relational
, you need to overrideRelationalQueryFactory.EntryInformationModel(string informationModel)
in your implementation to allow the FHIR version you wish to target (see Deciding on a FHIR version)We took the opportunity of a major version upgrade to clean up a list of items that had been declared
Obsolete
already. Others have become obsolete now. This is the full list:#
Obsolete
, now deleted:# Vonk.Core.Common.DeletedResource # Vonk.Core.Common.IResource.Currency, Change and Clone(), also in VonkResource. # Vonk.Core.Common.IResourceExtensions.ToIResource(this ISourceNode original, ResourceChange change, ResourceCurrency currency = ResourceCurrency.Current) (the overload defaulting to STU3) # Vonk.Core.Context.Guards.SupportedInteractionOptions.SupportsCustomOperationOnLevel() # Vonk.Core.Context.Internal.BatchOptions # Vonk.Core.Operations.Validation.ValidationOptions # Vonk.Core.Pluggability.InteractionHandlerAttribute.Tag # Vonk.Core.Pluggability.ModelOptions # Vonk.Core.Repository.SearchOptions.LatestOne # Vonk.Core.Support.LogHelpers.TryGetTelemetryClient, both overloads. # Vonk.Core.Support.SpecificationZipLocator.ctor(IHostingEnvironment…) # Vonk.Fhir.R3.IResourceVisitor + extensions # Vonk.Fhir.R3.Configuration.ModelContributorsFacadeConfiguration # Vonk.Fhir.R3.FhirExtensions.AsIResource() # Vonk.Fhir.R3.FhirPropertyIndex + FhirPropertyInfo + FhirPropertyIndexBuilder # Vonk.Fhir.R3.IConformanceBuilder + BaseConformanceBuilder + HarvestingConformanceBuilder + extensions + IConformanceContributor # Vonk.Fhir.R3.CompartmentDefinitionLoader + (I)SearchParameterLoader # Vonk.Fhir.R3.MetadataImportOptions + MetadataImportSet + ImportSource # Vonk.Fhir.R3.PocoResource + PocoResourceVisitor # Vonk.Core.InformationModelAttribute (actually made internal)
#
Obsolete
since this version:# Vonk.Core.Configuration.CoreConfiguration: allows for integrating Vonk components in your own ASP.NET Web server, discouraged per 3.0 (see these releasenotes). # Vonk.Fhir.R3.FhirR3FacadeConfiguration: see above.
Release 3.9.3 hotfix
Attention
We changed the behaviour of resthook notifications on Subscriptions. See Fix nr 1 below.
Database
SQL Server: The migration that adds the indexes described in Release 3.9.2 hotfix might run longer than the timeout period of 30 seconds. Therefore we added scripts to apply and revert this migration manually. If you encounter the timeout during upgrade: shut down vonk, run the script using SQL Server Management Studio or any similar tool, then start Vonk 3.9.3 again. In both scripts you only need to provide the databasename for the database that you want to upgrade. If you run your administration database on SQL Server you can but probably do not need to run this script on it. The administration database is typically small enough to complete the script within 30 seconds.
apply: <vonk-dir>/data/2021211113200_AddIndex_ForCountAndUpdateCurrent_Up.sql
revert: <vonk-dir>/data/2021211113200_AddIndex_ForCountAndUpdateCurrent_Down.sql
Fix
Subscriptions: A resthook notification was sent as a FHIR create operation, using POST. This was not compliant with the specification that states it must be an update, using PUT. We changed the default behaviour to align with the specification. In order to avoid breaking changes in an existing deployments, you may set the new setting
SubscriptionEvaluatorOptions:SendRestHookAsCreate
totrue
- that way Vonk will retain the (incorrect) behaviour from the previous versions.
Release 3.9.2 hotfix
Fix
All fixes are relevant to SQL Server only.
The 3.9.0 fix that “Improved the handling of concurrent updates on the same resource.” decreased the performance of concurrent transaction handling. We implemented another solution that does not affect performance.
Improved read performance by adding an index.
Release 3.9.1 hotfix
Fix
Fixed a bug introduced with 3.9.0 were Vonk would throw the following exception on start-up
System.InvalidOperationException: Unable to resolve service for type 'Vonk.Core.Conformance.IDefinitionProvider' while attempting to activate 'Vonk.Fhir.R3.SnapshotGeneration.SnapshotGeneratorR3
Fixed a breaking change to public search API with the implementation of
_total
parameter. We had introduced a new parameter to the Next method in ResultPage, effectively breaking backwards compatibility. This has been fixed.
Release 3.9.0
Features
We have made Subscriptions more robust. See Subscriptions for details. In summary, if an evaluation of a Subscription fails, Vonk will retry the evaluation periodically for a number amount of tries. You can control the retry period and the maximum number of retries in the subscription settings:
RetryPeriod
is expressed in milliseconds. Default30000
(30 sec).MaximumRetries
is the maximum amount of times Vonk will retry to send the resources. Default3
retries.
We have implemented the
_total
parameter for optionsnone
andaccurate
. Omitting the_total
parameter is equivalent to_total=accurate
. If the total number of resources is not relevant, using_total=none
in the request results in better performance when searching.It is no longer necessary for the
:type
parameter to always be provided to distinguish between multiple reference targets. The parameter does not need to be provided anymore when the search only applies to a single target. For example:GET <base>/AllergyIntolerance?patient=xyz
Thepatient:Patient
type parameter does not have to be supplied. The ‘patient’ search parameter on AllergyIntolerance has two possible targets. It may reference either a Patient or a Group resource. However, the fhirpath statement that goes with it, selects ‘AllergyIntolerance.patient’, and that reference element may only target a Patient resource.
Fixes
Indexing values for a string search parameter threw an exception when there was no value but only an extension. This has been corrected.
We made the Provenance.target available as a revInclude Parameter in the CapabilityStatement. Previously, Vonk did not account for the case that a reference is allowed to ANY resource type, which incorrectly resulted in Provenance.target to not be shown in the CapabilityStatement.
All the following fixes are only relevant for SQL Server:
Improved the handling of concurrent updates on the same resource.
Upgraded the version of the SqlClient library to fix issues when running on Linux.
Fixed missing language libraries for SQL Server when running on Docker.
Release 3.8.0
Database
We added an important note to the 3.6.0 release notes for MongoDb users.
Because of the changes in searching for Quantities (feature 2 below), you will need to do a reindex in order to make use of this. You may limit the reindex to only the searchparameters of type ‘quantity’ that you actually use (e.g.
Observation.value-quantity
).
Features
We upgraded the FHIR .NET API to 1.9, see the 1.9 releasenotes. This will trigger an automatic import of the Conformance Resources at startup.
We upgraded the Fhir.Metrics library to 1.2. This allows for a more uniform search on Quantities (mainly under the hood)
We upgraded the FHIR Mapping plugin to support the FHIR Mapper version 0.5. See its FHIR Mapper releasenotes.
The built-in terminology services now support the
includeDesignations
parameter.The IVonkContext now lets you access the original HttpContext.
The CapabilityStatement now lists the profiles that are known to Vonk (in its Administration database) under
CapabilityStatement.rest.resource.supportedProfile
(>= R4 only) and the base profile for a resource underCapabilityStatement.rest.resource.profile
.We extended the security extension on the CapabilityStatement to include the endpoints for
register
,manage
,introspect
andrevoke
.IAdministrationSearchRepository
andIAdministrationChangeRepository
interfaces are no publicly available. Use with care.
Fixes
If the server configured as authorization endpoint in the Smart setting is not reachable, Vonk will log a proper error about that.
An error message for when a query argument has no value is improved.
When SMART-on-FHIR is enabled, and the received token contains a launch context, the _history operation is no longer available. Because Vonk does not retain the search parameter index for historical resources, it cannot guarantee that these resources fall within the launch context (at least not in a performant way). To avoid information leakage we decided to disable this case altogether.
A Create interaction without an id in the resource, with SMART-on-FHIR enabled, resulted in an exception.
You can now escape the questionmark ‘?’ in a query argument by prepending it with a backslash ‘'.
A Quantity search using ‘lt’ on MongoDb resulted in too many results.
Release 3.7.0
Database
Attention
To accomodate for feature #2 below there is an automatic migration carried out for SQL Server and SQLite. This migration might take some time, so please test it first. For MongoDb, you will have to Rebuild the whole search index. If this is not feasible for your database, please Contact us for assistance.
Features
Patch: We implemented FHIR Patch. You can now update a resource having only partial data for it. See Create, read, update, patch, delete.
Search on accents and combined characters: we improved searching with and on accents and combined characters. Note the database change above.
API 1.7: We upgraded Vonk to use the FHIR .NET API 1.7, having its own releasenotes.
Security: The Docker image is now based on the Alpine image for .NET Core. This has far less security issues than the Ubuntu image that we used before. The base image is aspnet:3.1-alpine:3.11 (newest version 3.12 has an open bug related to SQLite).
Security: We revisited the list of security vulnerabilities, see Security notifications for Firely Server.
Administration: ConceptMaps are now imported at startup.
Fixes
Searching on _lastUpdated could be inaccurate when time zone differences are in play. We fixed that.
Search arguments for a quantity search weren’t allowed to be greater than 999.
Release 3.6.1
Features
ConceptMap resources can be stored at the Administration endpoint, both through import and through the RESTful API.
Attention
Previous versions of Vonk did not include the ConceptMap resources in the import so they will currently not be in your Administration database. If you run your Administration database on SQL Server or MongoDb and want to use the ConceptMap resources from the spec, be sure to rerun the import of the specification resources. You can force Vonk to do so by deleting the .vonk-import-history.json
file from the ImportedDirectory (see the settings under AdministrationImportOptions
). If you use SQLite, you can simply use the pre-built ./data/admin.db
from the binaries.
Plugins
The FHIR Mapper plugin is upgraded to version 0.3.6.
The FHIR Mapper plugin now fully works on the Administration endpoint.
Release 3.6.0
Database
Attention
For MongoDb users: We implemented feature #1 below using the Aggregation Pipeline. This makes an existing issue in MongoDb - SERVER-7568 <https://jira.mongodb.org/browse/SERVER-7568> - a more urgent problem. MongoDb has solved this problem in version 4.4. Therefore we advise you to upgrade to MongoDb 4.4.
Feature
Sort: The sorting that was implemented for the SQL/SQLite repositories in the previous version is now also implemented for MongoDb.
Terminology: The local terminology service, built in to the Vonk Administration API, is upgraded to support R4 and R5 (and still R3 of course).
Vonk can now index and search on searchparameters that reference a nested resource, like Bundle.message.
Attention
Note that any nested resources have to be indexed by Vonk. For new data that is done automatically. But if you want to use this on existing data, you have to reindex for the searchparameters you want to use it on. Those will most notably be Bundle.message and Bundle.composition.
If you accidentally provide a body in a GET or DELETE request, Vonk will now ignore that body instead of returning an error.
Fix
CapabilityStatement (rev)includes now use ‘:’ as a separator instead of ‘.’.
Plugins
The BinaryWrapper plugin is upgraded to 0.3.1, where the included BinaryEncodeService is made more reusable for other plugins (most notably the FHIR Mapper).
Release 3.5.0
Feature
Search reference by identifier: FHIR R4 allows you to search a reference by its identifier. We added support for this in Vonk. Note that any identifiers in reference elements have to be indexed by Vonk. For new data that is done automatically. But if you want to use this on existing data, you have to reindex for the searchparameters you want to use it on. E.g. Observation.patient.
AuditEvent logging: In release 3.3.0 we already added support for logging audit information to a file. With this release we add to that logging that same information in AuditEvent resources. These resources are written to the Vonk Data database (not the Administration database). Users are not allowed to update or delete these resources. See Auditing for more background.
Audit logging: We added
[Request]
or[Response]
to the log lines so you can distinguish them better.Sort: We started implementing sorting. This release provides sorting for searchparameters of the types string, number, uri, reference, datetime and token, on the repositories SQL, SQLite and Memory. On the roadmap is extending this support to MongoDb and to quantity searchparameters.
Terminology Integration: You can configure Vonk to route the terminology operations to external terminology servers. You can even configure a preferred server for certain code systems like LOINC or Snomed-CT. On the roadmap is to also allow you to use these servers for validation of codes and for token searches.
We implemented $meta-delete, see Meta plugins.
Loading plugins can lead to unexpected errors. We made the process and the log friendlier, so you can spot configuration errors more easily:
The log outputs the version of each of the plugins
If a duplicate .dll file is found, Vonk tells you which two dlls are causing this and then exits.
If you configured a plugin that you are not licensed to use, this is logged with a friendly hint to acquire a license that does allow you to use it.
The log is now by default configured to use asynchronous logging so Vonk is not limited by the speed of the logging sinks (like the Console and the log file). Please update your logsettings.instance.json if you created your own log settings in that. See Log settings for more background.
Fix
You could load invalid XML in the Resource.text through a JSON payload. When that resource was then retrieved in XML, it would fail with an InternalServerError. Vonk will now return an OperationOutcome telling you what the problem is. You can then correct it by using JSON.
Composite searchparameters were not parsed correctly. Now they are. So you don’t see warnings like
Composite SearchParameter 'CodeSystem.context-type-quantity' doesn't have components.
anymore.Indexing for the _profile searchparameter was broken for R4 since Vonk 3.2.1. We fixed it. If you added new resources with Vonk 3.2.1 - 3.4.0, you need to reindex for the Resource._profile parameter.
Audit log:
%temp%
in the path setting was evaluated as<current directory>\%temp%
. Fixed that to evaluate to the systems temporary directory.The logsettings.json configured the Serilog RollingFile sink by default. That is deprecated, so we replaced it with the File sink.
Rebuild the search index for specific searchparameters now returns an error if you forget to actually specify a searchparameter.
An InternalServerError was returned when you validate a resource that is syntactically incorrect. Like a Patient with multiple id’s. Vonk now returns an OperationOutcome with the actual problem.
The configuration for the FHIR Mapper was simplified. You only need to include
Vonk.Plugin.Mapping
. Check appsettings.default.json for the new pipeline.Maybe you got accustomed to ignoring a list of warnings at startup of Vonk. We cleaned up the list so that if there is a warning, it is worthwile investigating the cause of it.
The appsettings and logsettings can contain relative file paths for several settings, like the
License:LicenseFile
. These were evaluated against the current working directory, but that could lead to problems if that was not the Vonk directory. We solved that: all relative paths are evaluated against the Vonk directory.The docker image for version 3.4.0 was tagged
3.4.0-
. With 3.5.0 we removed the superfluous hypen at the end.We updated the documentation on Using Firely Server on Docker on SQL Server to be clearer about the order of the steps to take.
We updated the documentation on Firely Server Plugin example - Create a new landing page to match .NET Core 3.1.
Plugins & Facade
-
Has been upgraded to version 0.3.4.
Release 3.4.0
Feature
Upgraded to FHIR .NET API 1.6.0, that features a couple of changes for working with CDA logical models. See the release notes of the API.
Included the FHIR Mapper in the distribution. It is only enabled however when you include the mapping plugin in your license. See mappingengine_index for more information about the FHIR Mapper.
Fix
When prevalidation is set to the level ‘Core’, Vonk no longer complains about extensions that are not known if they are not core extensions (i.e. having a url starting with ‘http://hl7.org/fhir/StructureDefinition/’).
Release 3.3.0
Attention
To use the new features for auditing and R5, you need a new license file including the tokens for those plugins. For evaluation and community editions you can retrieve them from Simplifier.net. If you need these updates in your production license, please contact us.
Feature
Vonk was upgraded to FHIR .NET API 1.5.0. See the release notes of the API.
Vonk can now log audit lines in a separate file. This can help you achieve HIPAA/GDPR compliancy. See Auditing for more info.
Failed authorization attempts are now logged from the SMART on FHIR plugin.
Support for
_include:iterate
and_revinclude:iterate
, see Search.The BinaryWrapper plugin is now two-way. So you can POST binary content and have it stored as a Binary resource, and GET a Binary resource and have it returned in its original binary format.
Experimental support for R5 is now included in the Vonk distribution. For enabling it, see Support for R5 (experimental!).
Fix
Indexing of a quantity in resource could fail with a Statuscode 500 if it had no
.value
but only extensions.The use of a SearchParameter of type
reference
having notarget
failed. These searchparameters are now signalled upon import.Since R4 it is valid to search for a quantity without specifying the unit. Vonk now accepts that.
A transaction response bundle could contain an empty
response.etag
element, which is invalid.Preloading resources was not working since the upgrade to .NET Core 3.0. That has been fixed. It is still only available for STU3 though.
Administration import would state that it moves a file to history when it had imported it. That is no longer true, so we removed this incorrect statement from the log.
$validate-code could cause a NullReference exception in some case.
The generated CapabilityStatement for R4 failed constraint cpb-14.
Content negotiation favoured a mediatype with quality < 1 over a mediatype without quality. But the default value is 1, so the latter is now favoured.
Validate an instance from the database did not account for the informationmodel (aka FHIR version) of the resource.
Plugins & Facade
-
Has been upgraded to Vonk 3.2.0.
Was assigned a license token
Assigns an id and lastUpdated to the result bundle
-
Has been upgraded to Vonk 3.2.0.
Was assigned a license token.
Vonk.Facade.Starter has been upgraded to Vonk 3.2.1 and as a consequence also to EntityFrameworkCore 3.1.0.
Release 3.2.1
Fix
SMART plugin now understands multiple scopes per access token.
SMART plugin now understands
Resource.*
claims, in addition to already understandingResource.read
andResource.write
.
Release 3.2.0
Attention
Vonk 3.2.0 is upgraded to .NET Core 3.1.0, ASP.NET Core 3.1.0 and EntityFramework Core 3.1.0.
For running the server: install the ASPNET.Core runtime 3.1.0.
For developing or upgrading Facades that use Vonk.Facade.Relational: upgrade to EF Core 3.1.0.
Plugins that target NetStandard 2.0 need not be upgraded.
Database
There are no changes to the databases. The upgrade of EntityFramework Core does not affect the structure of the SQL Server or SQLite databases, just the access to it.
Fix
Supported interactions were not enforced for custom operations like e.g. $convert.
If a resource failed Validating incoming resources, the OperationOutcome also contained issues on not supported arguments.
A search with
?summary=count
failed.Added support for FhirPath
hasValue()
method.Resolution of canonical
http://hl7.org/fhir/v/0360|2.7
failed.CapabilityStatement.rest.resource.searchInclude used ‘.’ as separator, fixed to use ‘:’ in <resource>:<search parameter code>
Changed default value of
License:LicenseFile
tovonk-license.json
, aligned with the default naming if you download a license from Simplifier.Reindexing always interpreted a resource as STU3. Now it correctly honours the actual FHIR version of the resource.
Feature
BinaryWrapper plugin can now be restricted to a list of mediatypes on which to act.
Vonk used to sort on
_lastUpdated
by default, and add this as extra sort argument if it was not in the request yet. Now you can configure the element to sort on by default inBundleOptions:DefaultSort
. Although Vonk FHIR Server does not yet support sorting on other elements, this is useful for Facade implementations that may support that (and possibly not support sort on_lastUpdated
). See also Search and History.Implemented $versions operation
Extended the documentation on:
several smaller additions
The SMART authorization plugin can now be configured to not check the audience. Although not recommended, it can be useful in testing scenarios or a highly trusted environment.
Attention
We changed the default value for the setting
SmartAuthorizationOptions.Audience
fromvonk
to empty, or ‘not set’. This is to avoid awkward syntax to override it with ‘not-set’. But if you rely on the valuevonk
, please override this setting in yourappsettings[.instance].json
or environment variables as described in Changing the settings.
Plugin and Facade API
Vonk.Facade.Relational now supports the use of the .Include() function of EntityFramework Core. To do so, override
RelationalQuery.GetEntitySet(DbContext dbContext)
.Vonk.Facade.Relational now supports sorting. Override
RelationalQueryFactory.AddResultShape(SortShape sortShape) and return a RelationalShorShape using the extension method ``SortQuery()
.
Release 3.1.3 hotfix
Fix
Fixed behaviour on conditional updates in transactions. In odd circumstances Vonk could crash on this.
Release 3.1.0
Please also note the changes in 3.0.0 (especially the one regarding the SQL server database)
Fix
Validation on multi-level profiled resources no longer fails with the message “Cannot walk into unknown StructureDefinition with canonical”
Improved documentation on upgrading Vonk, the Vonk pipeline, CORS support, plugins and IIS deployment
Using multiple parameters in _sort led to an error for all repositories
Vonk UI capability statement view now works for self-mapped endpoints like
/R3
or/R4
A saved resource reference (e.g.
Patient.generalPractitioner
) on a self-mapped endpoint (e.g./R4/...
) would have its relative path duplicated (/R4/R4/...
)Attention
If you have used self-mapped endpoints (appsettings:
InformationModel.Mapping.Map
in the ‘Path’ mapping mode) and you have saved resources containing references, it is possible that your database now contains some resources with broken references. Please contact us if this is the case
Feature
The new experimental FHIR mapping engine, which is currently exclusively available on our public FHIR server http://vonk.fire.ly
New licensing system, supporting the community edition
Simplifier projects are now imported for FHIR R4 as well
The following plugins have been bundled with the Vonk release (compare your appsettings with the new appsettings.default.json to activate them)
The $document operation (see Documents)
The $convert operation (see Conversion)
The binary wrapper (see Binary)
Plugin and Facade API
Vonk.Facade.Starter has been upgraded to work with Vonk 3.1.0
IConformanceContributor and IConformanceBuilder have moved from Vonk.Core.Pluggability to Vonk.Fhir.R3.Metadata. It is also deprecated, as Vonk.Core.Metadata.ICapabilityStatementContributor is now preferred instead. See Capabilities for more information
Implementations of ISearchRepository can now sort on multiple parameters (in BaseQuery.Shapes). Previously this would result in an error.
Improved documentation on the Important classes and interfaces
See Release 3.0.0 for some additional issues you may encounter upgrading your plugins
Release 3.0.0
Database
Please also note the changes in 3.0.0-beta1
SQL Server: SQL script ‘20190919000000_Cluster_Indexes_On_EntryId.sql’ (found in the /data folder of the Vonk distribution) must be applied to existing Vonk SQL databases (both to the admin and to the data repositories)
Attention
Vonk 3.0.0 (using SQL server) will not start unless this script has been applied to the databases. Please note that running the script can take considerable time, especially for large databases.
Feature
Information model (= FHIR version) settings
Although Vonk now supports multiple information models (STU3 and R4) simultaneously, an unused model can be disabled (see Configuring the Firely Server Pipeline)
You can set the default (or fallback) information model (previously: STU3), which is used when Vonk can not determine the information model from context (see Information model)
You can map a path or a subdomain to a specific information model (see Information model), mitigating the need to specify it explicitly in a request
Vonk now uses FHIR .NET API 1.4.0
Several performance enhancements have been made for SQL server and IIS setups
Added R4-style Conditional Update to both STU3 and R4
Fix
Circular references within resources are now detected, cancelling validation for now. We will re-enable validation for these resources when the FHIR .NET API has been updated
An $expand using incorrect data returned a 500 (instead of the correct 400)
Vonk now returns a 406 (Not Acceptable) when the Accept header contains an unsupported format
Deletes did not work for R4
Search parameters
Search parameters were read twice (at startup and upon the first request)
Search parameter ‘CommunicationRequest.occurrence’ is not correctly specified in the specification. We provide a correct version.
_history
_history was not usable in a multi information model setup
The resulting Bundle.entry in an STU3 _history response contained the unallowed response field
Added Bundle.entry.response to the R4 _history entry
Batches
Valid entries in batches also containing invalid entries were not processed
Duplicate fullUrls are no longer accepted in a batch request, which previously led to a processing error
An R4 transaction resulted in STU3 entries
Transactional errors did not include fullUrl
Plugin and Facade API
Improved the message you get when the sorting/shaping operator is not implemented by your facade
VonkOutcome (and VonkIssue) has been simplified
VonkConstants has moved from Vonk.Core.Context to Vonk.Core.Common
IResourceChangeRepository.Delete requires a new second parameter:
string informationModel
. Information model constants can be found in Vonk.Core.Common.VonkConstants.ModelExclude Vonk.Fhir.R3 or Vonk.Fhir.R4 from the PipelineOptions if you don’t support it in your Facade.
Updated the minimal PipelineOptions for a Facade Plugin in the example appsettings.json:
updated
Vonk.Core.Operations.SearchConfiguration
toVonk.Core.Operations.Search
removed
Vonk.UI.Demo
removed
Vonk.Core.Operations.Validate.SpecificationZipSourceConfiguration
from theExclude
updated
Vonk.Core.Operations.Terminology
toVonk.Plugins.Terminology
Note
Early Facade implementations were built with by using Vonk services and middleware in a self-built ASP.NET Core web server. This can be seen in the Vonk.Facade.Starter project in the repository with the same name. Due to changes in Vonk this does not work with Vonk 3.0.0. It will be fixed in 3.1.0. But after that such projects cannot be upgraded anymore and will have to be refactored to a proper plugin (as the ViSi.Repository project in the same repository). Please contact us in case of any questions.
Release 3.0.0-beta2
Attention
We updated the Security notifications for Firely Server.
Database
Note the changes in 3.0.0-beta1, but there are no new changes in beta2.
Feature
Subscriptions works for R4 also. Note that a Subscription will only be activated for resource changes in the same FHIR version as the Subscription itself.
Load Conformance Resources from disk works for R4 also. Use a directoryname that ends with
.R4
for R4 conformance resources.Re-indexing for new or changed SearchParameters works for R4 also. Issue a reindex with a fhirVersion parameter in the Accept header, and it will be executed for the SearchParameters defined for that FHIR version.
Allow for non-hl7 prefixed canonical urls for conformance resources (since sdf-7 is lifted). See Custom Resources.
Custom Resources can be validated, both individually and as part of a bundle. See Custom Resources.
If the Accept header lacks a fhirVersion parameter, it will fall back to the fhirVersion parameter of the Content-Type header and vice versa. If both are missing, Vonk will default to STU3.
Fix
_include did not work for R4.
_include gave a 500 responsecode if a resource contains absolute references.
A resource with unknown elements could result in an uncaught
Hl7.Fhir.ElementModel.StructuralTypeException
.The homepage stated that Vonk was only for STU3. Fixed that.
Bundle.timestamp element (new in R4) was not populated in bundles returned from Search and History operations.
Some operations could return an OperationOutcome with an issue and a success message.
Better error message if a resource without any meta.profile is not accepted by Validating incoming resources.
Requesting an invalid FHIR version resulted in a ArgumentNullException.
Plugin and Facade API
NuGet package
Vonk.Fhir.R4
had a dependency on Vonk.Administration.API, but the latter is not published. We removed the dependency.IResourceExtensions.UpdateMetadata
did not update the id of the resource.VonkOutcome.RemoveIssue()
method has been removed.
Examples
Plugin example (Vonk.Plugin.ExampleOperation):
Added an example of middleware directly interacting with the
HttpContext
(instead of just theVonkContext
), see the file VonkPluginMiddleware.csCapabilityStatementBuilder was not called.
DocumentOperation (Vonk.Plugin.DocumentOperation):
Composition ID was not determined correctly when using POST.
Release 3.0.0-beta1
Vonk 3.0.0 is a major upgrade that incorporates handling FHIR R4. This runs in the same server core as FHIR STU3. See Multiple versions of FHIR for background info.
Attention
If you have overridden the PipelineOptions in your own settings, you should review the new additions to it in the appsettings.default.json.
In particular we added Vonk.Fhir.R4
that is needed to support FHIR R4.
Attention
MacOS: you may need to clean your temp folder from previous specification.zip expansions. Find the location of the temp folder by running echo $TMPDIR
.
Database
SQL Server, SQLite:
vonk.entry got a new column ‘InformationModel’, set to ‘Fhir3.0’ for existing resources.
vonk.ref got a new column ‘Version’.
Database indexes have been updated accordingly.
Vonk will automatically update both the Administration and the Data databases when you run Vonk 3.0.0.
MongoDb / CosmosDb:
The documents in the vonkentries collection got a new element im (for InformationModel), set to ‘Fhir3.0’ for existing resources.
The documents in the vonkentries collection got a new element ref.ver (for Version).
Database indexes have been updated accordingly.
MongoDb / CosmosDb: Got a light mechanism of applying changes to the document structure. A single document is added to the collection for that, containing
VonkVersion
andLatestMigration
.MongoDb: The default name for the main database was changed from ‘vonkstu3’ to ‘vonkdata’. If you want to continue using an existing ‘vonkstu3’ database, override
MongoDbOption:DatabaseName
, see Hierarchy of settings.
Feature
Support for FHIR R4 next to FHIR STU3. Vonk will choose the correct handling based on the fhirVersion parameter in the mimetype. The mimetype is read from the Accept header and (for POST/PUT) the Content-Type header. See Multiple versions of FHIR for background info.
Upgrade to HL7.Fhir.Net API 1.3, see its releasenotes.
Administration API imports both STU3 and R4 conformance resources, see Conformance Resources
Note: Terminology operations are still only available for STU3.
Note: Subscriptions are still only available for STU3.
Conditional delete on the Administration API. It works just as on the root, see Create, read, update, patch, delete.
Defining a custom SearchParameter on a Custom ResourceType is now possible.
Canonical uris are now recognized when searching on references (specification)
Vonk calls
UseIISIntegration
for better integration with IIS (if present).
Fix
In the settings, PipelineOptions.Branch.Path for the root had to be
/
. Now you can choose your own base (like e.g./fhir
)$meta:
enabled on history endpoint (e.g.
/Patient/123/_history/v1
)disabled on type and system level
returned empty Parameters resource if resource had no
meta.profile
, now returns the resourcesmeta
element.when called on a non-existing resource, returns 404 (was: empty Parameters resource)
added to the CapabilityStatement
History on non-existing resource returned OperationOutcome instead of 404.
The setting for SupportedInteractions was not enforced for custom operations.
CapabilityStatement.name is updated from
Vonk beta conformance
toVonk FHIR Server <version> CapabilityStatement
.-
$lookup did not work on GET /CodeSystem
$lookup did not support the
coding
parameter$expand did not fill in the expansion element.
Operations were not listed in the CapabilityStatement.
Namespace changed to Vonk.Plugins.Terminology, and adjusted accordingly in the default PipelineOptions.
A SearchParameter of type token did not work on an element of type string, e.g. CodeSystem.version.
Search with POST was broken.
If a long running task is active (responsecode 423, see Import of Conformance Resources and Re-indexing for new or changed SearchParameters), the OperationOutcome reporting that will now hide issues stating that all the arguments were not supported (since that is not the cause of the error).
Overriding an array in the settings was hard - it would still inherit part of the base setting if the base array was longer. We changed this: an array will always overwrite the complete base array. Note that this may trick you if you currently override a single array element with an environment variable. See Hierarchy of settings.
The element
meta.source
cannot be changed on subsequent updates to a resource (R4 specific)SearchParameter
StructureDefinition.ext-context
yielded many errors in the log because the definition of the fhirpath in the specification is not correct. We provided a corrected version in errataFhir40.zip (see Errata to the specification).Enable or disable interactions was not evaluated for custom operations.
Delete of an instance accepted searchparameters on the url.
Transactions: references to other resources in the transaction were not updated if the resource being referenced was in the transaction as an update (PUT). (this error was introduced in 2.0.0).
Plugin and Facade API
A new NuGet package is introduced: Vonk.Fhir.R4.
VonkConstants
moved to the namespaceVonk.Core.Common
(was:Vonk.Core.Context
)IResource.Navigator
element is removed (was already obsolete). Instead: Turn it into anITypedElement
and use that for navigation with FhirPath.InformationModel
element is added toIResource
: the model in which the resource is defined (VonkConstants.Model.FhirR3
orVonkConstants.Model.FhirR4
)IVonkContext
: the model that was specified in the Accept headerIModelService
: the model for which this service is valid (implementations are available for R3 and R4)InteractionHandler
attribute: to allow you to specify that an operation is only valid for a specific FHIR version. This can also be done in the fluent interface with the new methodAndInformationModel
. See Interaction Handling
Dependency injection: if there are implementations of an interface for R3 and R4, the dependency injection in Vonk will automatically inject the correct one based on the InformationModel in the request.
If you want to register your own service just for one informationmodel, do that as follows:
Add a ContextAware attribute to the implementation class:
[ContextAware (InformationModels = new[] {VonkConstants.Model.FhirR3}] public class MySearchRepository{...}
Then register the service as being ContextAware:
services.TryAddContextAware<ISearchRepository, MySearchRepository>(ServiceLifeTime.Scoped);
FhirPropertyIndexBuilder
is moved to Vonk.Fhir.R3 (and was already marked obsolete - avoid using it)Implementations of the following that are heavily dependent upon version specific Hl7.Fhir libraries have been implemented in both Vonk.Fhir.R3 and Vonk.Fhir.R4.
IModelService
IStructureDefinitionSummaryProvider
(to add type information to anIResource
and turn it into anITypedElement
)ValidationService
IConformanceContributor
is changed toICapabilityStatementContributor
. The methods on it have changed slightly as well because internally they now work on a version-independent model. Please review your IConformanceContributor implementations.
Examples
Document plugin:
Upgraded to Vonk 2.0.0 libraries (no, not yet 3.0.0-beta1)
Facade example
Added support for searching directly on a reference from Observation to Patient (e.g.
/Observation?patient=Patient/3
).Fixed support for _revinclude of Observation on Patient (e.g.
/Patient?_revinclude:Observation:subject:Patient
).Upgraded to Vonk 2.0.0 libraries (no, not yet 3.0.0-beta1)
Plugin example
Added examples for pre- and post handlers.
Known to-dos
Re-indexing for new or changed SearchParameters: does not work for R4 yet.
Preloading resources: does not work for R4 yet.
Subscriptions: do not work for R4 yet.
Terminology services: operations do not work for R4.
During Import of Conformance Resources: Files in the import directory and Simplifier projects are only imported for R3.
How to upgrade Firely Server?
The process for upgrading Firely Server depends on whether you have a vanilla Firely Server, you added your own plugins or are running a Facade. This page describes the general process for each situation. Please refer to the Release notes Firely Server for details per each released version of Firely Server.
Upgrading Firely Server
Using the binary distribution
Download the latest version of Firely Server, see Getting Started, and extract it to where you want it installed.
Copy your appsettings.instance.json and logsettings.instance.json files from the current installation to the new installation.
Check the Release notes Firely Server for any new settings that you may want to apply or change from their defaults.
Check the Release notes Firely Server for any actions that you need to take specifically for this upgrade.
Make sure the new installation can find the license file (see License, general advice is to put the license file outside of the installation directory).
Create a backup of your current databases, both the main Resource database and the Administration database. See Repository to find the details on your configured database connection.
Stop the running instance of Firely Server (Ctrl + C if running from the console).
Switch to the new installation directory and start Firely Server from there (
> dotnet ./Vonk.Server.dll
)Firely Server will now do several upgrade tasks, during which any web request will be responded to with 423 - Locked:
If needed, an update is applied to the database structure.
If Firely Server introduces a new version of the FHIR .NET API, Firely Server will load a new set of Conformance Resources from the specification.zip into the Administration database, for both FHIR STU3 and FHIR R4. In a specific case you can prevent this step from happening.
When Firely Server is done with the tasks above, it is again available to process requests.
Check the log for warnings stating that you use obsolete settings. If so, adjust them and restart Firely Server.
If anything went wrong, go back:
Stop the (new) running instance of Firely Server.
Restore both databases from your backup.
Switch to the old installation directory and start the old version of Firely Server from there (
> dotnet .\Vonk.Server.dll
)It should start as it did before you began the upgrade.
Report the problem to the Firely Server helpdesk, see Contact us.
You may be able to avoid the import of specification.zip if:
The Administration database is in SQLite and
You have not made alterations to the Administration API through the Web API.
In this case you can simply replace the old database (usually with the filename vonkadmin.db
) with the one from the new installation directory (in ./data/vonkadmin.db
).
Do so before you start the new Firely Server installation.
Anything specified in AdministrationImportOptions will be re-imported into the new database.
Using Docker
Revisit Using Firely Server on Docker.
Stop the running container for Firely Server:
> docker stop vonk.server
.Pull the latest image for Firely Server:
> docker pull simplifier/vonk
Check the Release notes Firely Server for any new settings that you may want to apply or change from their defaults, and apply that to the
environment
setting in the docker-compose file.Check the Release notes Firely Server for any action that you need to take specifically for this upgrade.
Create a backup of your current databases, both the main Resource database and the Administration database. See Repository and your docker-compose file to find the details on where your databases are.
Start the new version (see Using Firely Server on Docker for the various commands to run the Firely Server container).
Firely Server will now do several upgrade tasks, during which any web request will be responded to with 423 - Locked:
If needed, an update is applied to the database structure.
If Firely Server introduces a new version of the FHIR .NET API, Firely Server will load a new set of Conformance Resources from the specification.zip into the Administration database, for both FHIR STU3 and FHIR R4. In a specific case you can prevent this step from happening.
When Firely Server is done with the tasks above, it is again available to process requests.
Check the log for warnings stating that you use obsolete settings. If so, adjust them and restart Firely Server.
If anything went wrong, go back:
Stop the (new) running container of Firely Server.
Restore both databases from your backup.
Specify your previous image of Firely Server in the docker command or in the docker-compose file:
simplifier\vonk:<previous-version-tag>
Start the container based on this previous image.
It should start as it did before you began the upgrade.
Report the problem to the Firely Server helpdesk, see Contact us.
Upgrading Plugins
Since a Plugin runs in the context of a Firely Server we advise you to start by upgrading your Firely Server, without loading your Plugin. Check the section on Configuring the Firely Server Pipeline to see how you can exclude your plugin from the pipeline.
Attention
We do not guarantee that a plugin built against version x.y.z of Firely Server can be run within a newer or older version as-is. Between minor versions recompilation is usually enough to update your plugin. Between major versions you should prepare for breaking changes in the public programming API. Sometimes we choose to apply such changes even on a minor version update, if we are fairly sure that you will not or only slightly be affected by it.
Upgrade the references in your plugin:
Open the source code of your plugin, and open the project file (
yourplugin.csproj
).Change the references to the Firely Server.* packages to the version that you want to upgrade to.
Build and check the errors.
Check the list of breaking changes for the new Firely Server version in the Release notes Firely Server. Applying the changes should fix the errors.
With some releases Firely Server is also upgraded to a newer version of the Firely .NET SDK. That is then mentioned in the release notes. If this is the case, also check the SDK release notes for breaking changes.
Still errors? Maybe we have overlooked a change. Please report it to us, see Contact us. And if it is easy to fix - do so :-)
Build and publish your plugin.
Put the resulting dll’s in the plugin directory of the new installation of Firely Server.
Re-include your plugin in the pipeline.
(Re)start Firely Server and test the working of your plugin.
Upgrading Facades
A Facade implementation is technically also a plugin, but one that only adds repository access services. For this it makes no sense to try to run Firely Server without the Facade as is described for plugins. So start with upgrading the references right away.
Especially for Facades to relational databases: match the version of EntityFrameworkCore that the new version of Firely Server is using. Check the list of changes to see whether we upgraded.
Firely Server Roadmap
This page lists the features and changes we have planned for the foreseeable future. This planning is volatile, so changes will happen. We will update this page accordingly. You are also very welcome to provide input to us on features or fixes that you would like to see prioritized.
Disclaimer: No rights can be derived from this roadmap.
2021
Q1
Full ONC/CMS compliance
Firely Server: ONC Edition
Downloadable version of Firely Server that is fully compliant with the ONC Cures Act Final Rule
CAR factory/files
Greatly reduces start-up time of Firely Server and other services
Firely Validator (CLI)
Separate validation service as a CLI tool
Q2
Bulk data export for MongoDB
Bulk data import tool for MS SQL Server
CLI tool for bulk ingestion of data into Firely Server
Firely Validator for cloud environments
Separate validation service optimized for usage in cloud environments
Firely Server: CMS Edition
Downloadable version of Firely Server that is fully compliant with the CMS Final Rule
Full compliance with MedMij standard
Q3
Firely Server: MedMij Edition
Downloadable version of Firely Server that is fully compliant with the MedMij standards
Bulk data export as a separate service - deploy and scale it independently of Firely Server itself.
Bulk data import tool for MongoDB
CLI tool for bulk ingestion of data into Firely Server
Q4
FHIR transaction support for MongoDB
Enhanced support for subscriptions
Frequently asked questions
… and known issues.
Conflicting resources upon import
When importing specification.zip for R4, Firely Server will report errors like these:
::
Artifact C:Users<user>AppDataLocalTempFhirArtifactCache-1.5.0-Hl7.Fhir.R4.Specificationspecification_Fhir4_0dataelements.xml could not be imported. Error message: Found multiple conflicting resources with the same resource uri identifier.
- Url: http://hl7.org/fhir/StructureDefinition/de-Quantity.value
File: C:UsersChristiaanAppDataLocalTempFhirArtifactCache-1.5.0-Hl7.Fhir.R4.Specificationspecification_Fhir4_0dataelements.xml File: C:UsersChristiaanAppDataLocalTempFhirArtifactCache-1.5.0-Hl7.Fhir.R4.Specificationspecification_Fhir4_0dataelements.xml File: C:UsersChristiaanAppDataLocalTempFhirArtifactCache-1.5.0-Hl7.Fhir.R4.Specificationspecification_Fhir4_0dataelements.xml
The error message is actually correct, since there are duplicate fullUrls in dataelements.xml in the specification. This has been reported in Jira issue FHIR-25430.
Searchparameter errors for composite parameters
When importing specification.zip for various FHIR versions, Firely Server will report errors like these:
Composite SearchParameter 'CodeSystem.context-type-quantity' doesn't have components.
A searchparameter of type ‘composite’ should define which components it consists of. Firely Server checks whether all the components of such a composite searchparameter are present. If no components are defined at all - that is, SearchParameter.component is empty - it will display this error. This indicates an error in the definition of the searchparameter and should be solved by the author of it.
However, the implementation of this check seems to have an error so too many composite parameters are reported as faulty. We will address this issue in the next release.
.NET SDK not found
Since version 4.0 Vonk was renamed to Firely Server, including the main entrypoint. It changed from vonk.server.dll
to firely.server.dll
.
If you now still run dotnet vonk.server.dll
on .NET runtime 3.1 it will state this error:
- ::
It was not possible to find any installed .NET Core SDKs Did you mean to run .NET Core SDK commands? Install a .NET Core SDK from: https://aka.ms/dotnet-download
This is very misleading. The actual error is that you probably tried to run dotnet vonk.server.dll
but this dll no longer exists.
The same error can happen if you have built a Docker image of your own with dotnet vonk.server.dll
as entrypoint.
.NET 5 fixed this and more clearly states that the dll is missing.
Configuring Firely Server
In this section we assume you have downloaded and installed the Firely Server binaries, and have obtained a license file. If not, please see the Getting Started and follow the steps there first.
The steps you followed to get started will provide you with a basic Firely Server, that runs on a standard port and keeps the data in a SQLite database.
If you need to adjust the port, or want to use a MongoDB, SQL or CosmosDB database, you can configure Firely Server by adjusting the Firely Server settings.
If you want to change the way Firely Server logs its information, you can adjust the Log settings.
Firely Server settings
Firely Server settings are controlled in json configuration files called appsettings(.*).json
. The possible settings in these files are all the same and described below.
The different files are read in a hierarchy so you can control settings on different levels. All appsettings files are in the Firely Server distribution directory, next to Firely.Server.dll.
We go through all the sections of this file and refer you to detailed pages on each of them.
You can also control Firely Server settings with Environment Variables.
Changes to the settings require a restart of Firely Server.
Hierarchy of settings
Firely Server reads its settings from these sources, in this order:
- appsettings.default.json
Installed with Firely Server, contains default settings and a template setting if no sensible default is available.
- appsettings.json
You can create this one for your own settings. Because it is not part of the Firely Server distribution, it will not be overwritten by a next Firely Server version.
- environment variables
- appsettings.instance.json
You can create this one to override settings for a specific instance of Firely Server. It is not part of the Firely Server distribution. This file is especially useful if you run multiple instances on the same machine.
Settings lower in the list override the settings higher in the list (think CSS, if you’re familiar with that).
Warning
JSON settings files can have arrays in them. The configuration system can NOT merge arrays. So if you override an array value, you need to provide all the values that you want in the array. In the Firely Server settings this is relevant for e.g. Validation.AllowedProfiles and for the PipelineOptions.
Note
By default in ASP.NET Core, if on a lower level the array has more items, you will still inherit those extra items. We fixed this in Firely Server, an array will always overwrite the complete base array. To nullify an array, add the value with an array with just an empty string in it:
"PipelineOptions": {
"Branches": [
{
"Path": "myroot",
"Exclude": [""]
}
]
}
This also means you cannot override a single array element with an environment variable. (Which was tricky anyway - relying on the exact number and order of items in the original array.)
Changing the settings
In general you do not change the settings in appsettings.default.json
but create your own overrides in appsettings.json
or appsettings.instance.json
. That way your settings are not overwritten by a new version of Firely Server (with a new appsettings.default.json
therein), and you automatically get sensible defaults for any new settings introduced in appsettings.default.json
.
Settings after first install
After you installed Firely Server (see Getting Started), either:
copy the
appsettings.default.json
toappsettings[.instance].json
and remove settings that you do not intend to alter, orcreate an empty
appsettings[.instance].json
and copy individual parts from theappsettings.default.json
if you wish to adjust them.
Adjust the new appsettings[.instance].json
to your liking using the explanation below.
When running Firely Server on Docker you probably want to adjust the settings using the Environment Variables.
Settings after update
If you install the binaries of an updated version of Firely Server, you can:
copy the new binaries over the old ones, or
deploy the new version to a new directory and copy the
appsettings[.instance].json
over from the old version.
In both cases, check the Release notes Firely Server to see if settings have changed, or new settings have been introduced.
If you want to adjust a changed / new setting, copy the relevant section from appsettings.default.json
to your own appsettings[.instance].json
and then adjust it.
Commenting out sections
JSON formally has no notion of comments. But the configuration system of ASP.Net Core (and hence Firely Server) accepts double slashes just fine:
"Administration": {
"Repository": "SQLite", //Memory / SQL / MongoDb
"SqlDbOptions": {
"ConnectionString": "connectionstring to your Firely Server Admin SQL Server database (SQL2012 or newer); Set MultipleActiveResultSets=True",
"SchemaName": "vonkadmin",
"AutoUpdateDatabase": true,
"MigrationTimeout": 1800 // in seconds
//"AutoUpdateConnectionString" : "set this to the same database as 'ConnectionString' but with credentials that can alter the database. If not set, defaults to the value of 'ConnectionString'"
},
This will ignore the AutoUpdateConnectionString.
Log of your configuration
Because the hierarchy of settings can be overwhelming, Firely Server logs the resulting configuration.
To enable that, the loglevel for Vonk.Server
must be Information
or more detailed. That is set for you by default in logsettings.default.json
.
Refer to Log settings for information on setting log levels.
Administration
"Administration": {
"Repository": "SQLite", //Memory / SQL / MongoDb are other options, but SQLite is advised.
"MongoDbOptions": {
"ConnectionString": "mongodb://localhost/vonkadmin",
"EntryCollection": "vonkentries"
},
"SqlDbOptions": {
"ConnectionString": "connectionstring to your Firely Server Admin SQL Server database (SQL2012 or newer); Set MultipleActiveResultSets=True",
"SchemaName": "vonkadmin",
"AutoUpdateDatabase": true,
"MigrationTimeout": 1800 // in seconds
//"AutoUpdateConnectionString" : "set this to the same database as 'ConnectionString' but with credentials that can alter the database. If not set, defaults to the value of 'ConnectionString'"
},
"SQLiteDbOptions": {
"ConnectionString": "Data Source=./data/vonkadmin.db",
"AutoUpdateDatabase": true
},
"Security": {
"AllowedNetworks": [ "::1" ], // i.e.: ["127.0.0.1", "::1" (ipv6 localhost), "10.1.50.0/24", "10.5.3.0/24", "31.161.91.98"]
"OperationsToBeSecured": [ "reindex", "reset", "preload" ]
}
},
The Administration
section is to Configure the Administration API and its repository.
License
"License": {
"LicenseFile": "firelyserver-trial-license.json",
"RequestInfoFile": "./.vonk-request-info.json",
"WriteRequestInfoFileInterval": 15 // in minutes
}
The Getting Started explains how to obtain a licensefile for Firely Server. Once you have it, put the path to it in the LicenseFile
setting. Note that in json you either use forward slashes (/) or double backward slashes (\\\) as path separators.
Other settings:
RequestInfoFile
sets the location of the file with request information. This file will be used in future releases.WriteRequestInfoFileInterval
sets the time interval (in minutes) to write aggregate information about processed requests to the RequestInfoFile.
Repository
"Repository": "SQLite", //Memory / SQL / MongoDb / CosmosDb
Repository
: Choose which type of repository you want. Valid values are:
Memory
SQL, for Microsoft SQL Server
SQLite
MongoDb
CosmosDb
Memory
"MemoryOptions": {
"SimulateTransactions": "false"
},
Refer to Using the In-Memory storage for configuring the In-Memory storage.
MongoDB
"MongoDbOptions": {
"ConnectionString": "mongodb://localhost/vonkstu3",
"EntryCollection": "vonkentries",
"SimulateTransactions": "false"
},
Refer to Using MongoDB for configuring the connection to your MongoDB databases.
SQL
"SqlDbOptions": {
"ConnectionString": "connectionstring to your Firely Server SQL Server database (SQL2012 or newer); Set MultipleActiveResultSets=True",
"SchemaName": "vonk",
"AutoUpdateDatabase": true,
"MigrationTimeout": 1800 // in seconds
//"AutoUpdateConnectionString" : "set this to the same database as 'ConnectionString' but with credentials that can alter the database. If not set, defaults to the value of 'ConnectionString'"
},
Refer to Using SQL server for configuring access to your SQL Server databases.
SQLite
"SQLiteDbOptions": {
"ConnectionString": "Data Source=./data/vonkdata.db",
"AutoUpdateDatabase": true
},
Refer to Using SQLite for configuring access to your SQLite Server databases.
CosmosDb
"CosmosDbOptions": {
"ConnectionString": "mongodb://<password>@<server>:10255/vonk?ssl=true&replicaSet=globaldb",
"EntryCollection": "vonkentries"
},
Refer to Using Microsoft Azure CosmosDB for configuring access to your CosmosDb databases.
http and https
"Hosting": {
"HttpPort": 4080,
//"HttpsPort": 4081, // Enable this to use https
//"CertificateFile": "<your-certificate-file>.pfx", //Relevant when HttpsPort is present
//"CertificatePassword" : "<cert-pass>" // Relevant when HttpsPort is present
},
Refer to Configure http and https for enabling https and adjusting port numbers.
Validation
"Validation": {
"Parsing": "Permissive", // Permissive / Strict
"Level": "Off", // Off / Core / Full
"AllowedProfiles": []
},
Refer to Validating incoming resources.
Search and History
"BundleOptions": {
"DefaultCount": 10,
"MaxCount": 50,
"DefaultSort": "-_lastUpdated"
},
The Search and History interactions return a bundle with results. Users can specify the number of results that they want to receive in one response with the _count
parameter.
DefaultCount
sets the number of results if the user has not specified a_count
parameter.MaxCount
sets the number of results in case the user specifies a_count
value higher than this maximum. This is to protect Firely Server from being overloaded.DefaultCount
should be less than or equal toMaxCount
DefaultSort
is what search results are sorted on if no sort order is specified in the request. If a sort order is specified, this is still added as the last sort clause.
Batch and transaction
"BatchOptions": {
"MaxNumberOfEntries": 100
},
This will limit the number of entries that are accepted in a single Batch or Transaction bundle.
Note
This setting has been moved to the SizeLimits
setting as of Firely Server (Vonk) version 0.7.1, and the logs will show a warning that it
is deprecated when you still have it in your appsettings file.
Protect against large input
"SizeLimits": {
"MaxResourceSize": "1MiB",
"MaxBatchSize": "5MiB",
"MaxBatchEntries": 150
},
MaxResourceSize
sets the maximum size of a resource that is sent in a create or update.MaxBatchSize
sets the maximum size of a batch or transaction bundle. (Note that a POST http(s)://<firely-server-endpoint>/Bundle will be limited by MaxResourceSize, since the bundle must be processed as a whole then.)MaxBatchEntries
limits the number of entries that is allowed in a batch or transaction bundle.The values for
MaxResourceSize
andMaxBatchSize
can be expressed in b (bytes, the default), kB (kilobytes), KiB (kibibytes), MB (megabytes), or MiB (mebibytes). Do not put a space between the amount and the unit.
SearchParameters and other Conformance Resources
"AdministrationImportOptions": {
"ImportDirectory": "./vonk-import",
"ImportedDirectory": "./vonk-imported", //Do not place ImportedDirectory *under* ImportDirectory, since an import will recursively read all subdirectories.
"SimplifierProjects": [
{
"Uri": "https://stu3.simplifier.net/<your-project>",
"UserName": "Simplifier user name",
"Password": "Password for the above user name",
"BatchSize": 20
}
]
}
Cache of Conformance Resources
"Cache": {
"MaxConformanceResources": 5000
}
Firely Server caches StructureDefinitions and other conformance resources that are needed for (de)serialization and validation in memory. If more than MaxConformanceResources
get cached, the ones that have not been used for the longest time are discarded. If you frequently encounter a delay when requesting less used resource types, a larger value may help. If you are very restricted on memory, you can lower the value.
Reindexing for changes in SearchParameters
"ReindexOptions": {
"BatchSize": 100,
"MaxDegreeOfParallelism": 10
},
Restrict supported resources and SearchParameters
"SupportedModel": {
"RestrictToResources": [ "Patient", "Observation" ],
"RestrictToSearchParameters": ["Patient.active", "Observation.patient", "Resource._id", "StructureDefinition.url"],
"RestrictToCompartments": ["Patient"]
},
By default, Firely Server supports all ResourceTypes, SearchParameters and CompartmentDefinitions from the specification. They are loaded from the specification.zip. If you want to limit support, you can do so with the configuration above. This is primarily targeted towards Facade builders, because they have to provide an implementation for everything that is supported.
Be aware that:
support for _type and _id must not be disabled
the Administration API requires support for the ‘url’ SearchParameter on the conformance resourcetypes
this uses the search parameter names, not the path within the resource - so for example to specify Patient.address.postalCode as a supported location, you’d use
"Patient.address-postalcode"
.
Enable or disable interactions
By default, the value SupportedInteractions
contains all the interactions that are implemented in Firely Server.
But you can disable interactions by removing them from these lists.
"SupportedInteractions": {
"InstanceLevelInteractions": "read, vread, update, delete, history, conditional_delete, conditional_update, $validate",
"TypeLevelInteractions": "create, search, history, $validate, $snapshot, conditional_create",
"WholeSystemInteractions": "capabilities, batch, transaction, history, search, $validate"
},
If you implement a custom operation in a plugin, you should also add the name of that operation at the correct level. E.g. add $convert
to TypeLevelInteractions
to allow <base>/<resourcetype>/$convert
.
Subscriptions
"SubscriptionEvaluatorOptions": {
"Enabled": true,
"RepeatPeriod": 20000,
"SubscriptionBatchSize" : 1
},
See Subscriptions.
Information model
Firely Server supports the use of multiple information models (currently FHIR STU3 and R4) simultaneously. The InformationModel
section contains the related settings.
By default, Firely Server serves both versions from the root of your web service, defaulting to STU3 when the client does not use Accept or _format to specify either one. Mapping a path or a subdomain to a specific version creates an additional URI serving only that particular version.
"InformationModel": {
"Default": "Fhir4.0", // For STU3: "Fhir3.0". Information model to use when none is specified in either mapping, the _format parameter or the ACCEPT header.
"Mapping": {
"Mode": "Off"
//"Mode": "Path", // yourserver.org/r3 => FHIR STU3; yourserver.org/r4 => FHIR R4
//"Map": {
// "/R3": "Fhir3.0",
// "/R4": "Fhir4.0"
//}
//"Mode": "Subdomain", // r3.yourserver.org => FHIR STU3; r4.yourserver.org => FHIR R4
//"Map":
// {
// "r3": "Fhir3.0",
// "r4": "Fhir4.0"
// }
}
},
Patient Everything Operation
"PatientEverythingOperation": {
"AdditionalResources": [ "Organization", "Location", "Substance", "Medication", "Device" ] // included referenced resources, additional to the Patient compartment resources
},
The Patient $everything operation returns all resources linked to a patient that are listed in the Compartment Patient. This section allows you to define additional resources that will be included in the resulting searchset bundle.
See Patient $everything.
FHIR Capabilities
"FhirCapabilities": {
"ConditionalDeleteOptions": {
"ConditionalDeleteType": "Single", // Single or Multiple,
"ConditionalDeleteMaxItems": 1
}
},
History size
"HistoryOptions": {
"MaxReturnedResults": 100
}
See History.
Configuring the Firely Server Pipeline
You can add your own plugins to the Firely Server pipeline, or control which of the standard Firely Server plugins
are used for your Firely Server, by changing the PipelineOptions
.
"PipelineOptions": {
"PluginDirectory": "./plugins",
"Branches": [
{
"Path": "/",
"Include": [
"Vonk.Core",
"Vonk.Fhir.R3",
"Vonk.Fhir.R4",
// etc.
],
"Exclude": [
]
},
{
"Path": "/administration",
"Include": [
"Vonk.Core",
"Vonk.Fhir.R3",
"Vonk.Fhir.R4",
// etc.
],
"Exclude": [
"Vonk.Core.Operations"
]
}
]
}
It is possible to disable a specific information model by removing Vonk.Fhir.R3 or Vonk.Fhir.R4 from the pipeline
Please note the warning on merging arrays in Hierarchy of settings.
See Firely Server Plugins for more information and an example custom plugin.
Firely Server settings with Environment Variables
Environment Variables for appsettings
All the settings in Firely Server settings can be overridden by environment variables on your OS.
This can be useful if you want to deploy Firely Server to several machines, each having their own settings for certain options.
For Using Firely Server on Docker using environment variables in the docker-compose file is currently the only way to pass settings to the container.
Or if you don’t want a database password in the appsettings.json
file.
The format for the environment variables is:
VONK_<setting_level_1>[:<setting_level_n>]*
So you start the variable name with the prefix ‘VONK_’, and then follow the properties in the json settings, separating each level with a colon ‘:’. Some examples:
appsettings.json:
"Repository" : "SQL"
environment variable:
VONK_Repository=SQL
To access an embedded value, using the ‘:’ separator:
appsettings.json:
"Administration" : {
"SqlDbOptions" : {
"ConnectionString" : "<some connectionstring>"
}
}
environment variable:
VONK_Repository:SqlDbOptions:ConnectionString=<some connectionstring>
To access an array item, use 0-based indexing:
VONK_PipelineOptions:Branches:0:Exclude:0=Vonk.Repository.Memory
VONK_PipelineOptions:Branches:0:Exclude:1=Vonk.Repository.Sql
Arrays in Environment Variables
Sometimes the appsettings allow for an array of values, e.g. in the setting for AllowedProfiles
in Validating incoming resources. You can address them by appending an extra colon and an index number.
appsettings.json:
"Validation": {
"ValidateIncomingResources": "true",
"AllowedProfiles":
[
http://hl7.org/fhir/StructureDefinition/daf-patient,
http://hl7.org/fhir/StructureDefinition/daf-allergyintolerance
]
},
environment variables:
VONK_Validation:ValidateIncomingResources=true
VONK_Validation:AllowedProfiles:0=http://hl7.org/fhir/StructureDefinition/daf-patient
VONK_Validation:AllowedProfiles:1=http://hl7.org/fhir/StructureDefinition/daf-allergyintolerance
Log settings with Environment Variables
You can control the Log settings with Environment Variables the same way as the Environment Variables for appsettings above. The difference is in the prefix. For the log settings we use ‘VONKLOG_’.
logsettings.json
- “Serilog”: {
- “MinimumLevel”: {
- “Override”: {
“Vonk.Configuration”: “Information”,
environment variable:
VONKLOG_Serilog:MinimumLevel:Override:Vonk.Configuration=Information
Changing Environment Variables on Windows
In Windows you can change the Environment Variables with Powershell or through the UI. Based on the first example above:
In Powershell run:
> $env:VONK_Repository="SQL"
or go to your System, open the Advanced system settings –> Environment variables and create a new variable with the name
VONK_Repository
and set the value to “SQL” (you don’t need to enter the quotes here).
Configure the Administration API
This configuration is part of Firely Server settings.
"Administration": {
"Repository": "SQLite", //Memory / SQL / MongoDb
"MongoDbOptions": {
"ConnectionString": "mongodb://localhost/vonkadmin",
"EntryCollection": "vonkentries"
},
"SqlDbOptions": {
"ConnectionString": "connectionstring to your Firely Server Admin SQL Server database (SQL2012 or newer); Set MultipleActiveResultSets=True",
"SchemaName": "vonkadmin",
"AutoUpdateDatabase": true,
"MigrationTimeout": 1800 // in seconds
//"AutoUpdateConnectionString" : "set this to the same database as 'ConnectionString' but with credentials that can alter the database. If not set, defaults to the value of 'ConnectionString'"
},
"SQLiteDbOptions": {
"ConnectionString": "Data Source=./data/vonkadmin.db",
"AutoUpdateDatabase": true,
"MigrationTimeout": 1800 // in seconds
},
"Security": {
"AllowedNetworks": [ "::1" ], // i.e.: ["127.0.0.1", "::1" (ipv6 localhost), "10.1.50.0/24", "10.5.3.0/24", "31.161.91.98"]
"OperationsToBeSecured": [ "reindex", "reset", "preload" ]
}
},
Choosing your storage
Repository
: Choose which type of repository you want. Valid values are:
Memory
SQL
SQLite
MongoDb
MongoDbOptions
: Use these with"Repository": "MongoDb"
, see Using MongoDB for details.SqlDbOptions
: Use these with"Repository": "SQL"
, see Using SQL server for details.SQLiteDbOptions
: Use these with"Repository": "SQLite"
, see Using SQLite for details.
Limited access
Security
: You can restrict access to the operations listed inOperationsToBeSecured
to only be invoked from the IP addresses listed inAllowedNetworks
.
Operations that can be secured are:
reindex
(see Re-indexing for new or changed SearchParameters)
reset
(see Reset the database)
preload
(see Preloading resources)
StructureDefinition
(restrict both read and write)
SearchParameter
(restrict both read and write)
ValueSet
(restrict both read and write)
CodeSystem
(restrict both read and write)
CompartmentDefinition
(restrict both read and write)
Subscription
: (restrict both read and write, see Subscriptions)The
AllowedNetworks
have to be valid IP addresses, either IPv4 or IPv6, and masks are allowed.
Conformance Resources
Firely Server uses Conformance Resources along with some Terminology Resources for various operations:
SearchParameter: For indexing resources and evaluating search interactions.
StructureDefinition: For snapshot generation, and of course – along with ValueSet and CodeSystem – for validation.
CompartmentDefinition: For access control and Compartment Search.
ValueSet and CodeSystem: For Terminology services operations.
StructureMap and ConceptMap: for mapping
You can control the behaviour of Firely Server for these interactions by loading resources of these types into Firely Server. There are two ways of doing this:
With regular FHIR interactions (create, update, delete) on the Firely Server Administration API.
With the Import of Conformance Resources.
No matter which method you use, all Conformance resources are persisted in the Administration API database (see Configure the Administration API for configuring that database), and available through the Administration API endpoint (<firely-server-endpoint>/administration
)
For each resourcetype the base profile is listed in the CapabilityStatement under CapabilityStatement.rest.resource.profile
and (since FHIR R4) all the other profiles are listed under CapabilityStatement.rest.resource.supportedProfile
. So by requesting the CapabilityStatement you can easily check whether your changes to the StructureDefinitions were correctly processed by Firely Server.
Attention
Please be aware that Conformance Resources have to have a unique canonical url within the FHIR Version they are loaded, in their url element. Firely Server does not allow you to POST two conformance resources with the same canonical url. For SearchParameter resources, the combination of base and code must be unique.
Attention
Creates or updates of SearchParameter resources should be followed by a re-index.
Before you delete a SearchParameter, be sure to remove it from the index first, see the exclude
parameter in re-index.
Changes to the other types of resources have immediate effect.
Attention
A StructureDefinition can only be posted in the context of a FHIR Version that matches the StructureDefinition.fhirVersion. See Multiple versions of FHIR.
Note
The import process will retain any id that is already in the resource, or assign a new id if there is none in the resource. For FHIR versions other than STU3, a postfix is appended to the id to avoid collissions between FHIR versions. See also Conformance resources.
Import of Conformance Resources
The import process of conformance resources runs on every startup of Firely Server, and on demand.
The process uses these locations on disk:
ImportDirectory;
ImportedDirectory;
a read history in the file .vonk-import-history.json, written in ImportedDirectory.
Attention
Please make sure that the Firely Server process has write permission on the ImportedDirectory.
The process follows these steps for each FHIR version (currently STU3 and R4, and experimentally for R5)
Load the Default Conformance Resources, if they have not been loaded before.
Load the Errata to the specification, if they have not been loaded before.
Load Conformance Resources from disk. After reading, the read files are registered in the read history.
Load Conformance Resources from simplifier.net. After reading, the project is registered in the read history. Subsequent reads will query only for resources that have changed since the last read.
Loading the conformance resources from the various sources can take some time, especially on first startup when the Default Conformance Resources have to be imported. During the import Firely Server will respond with 423 ‘Locked’ to every request to avoid storing or retrieving inconsistent data.
The read history keeps a record of files that have been read, with an MD5 hash of each. If you wish to force a renewed import of a specific file, you should:
manually edit the read history file and delete the entry about that file;
provide the file again in the ImportDirectory (if you deleted it previously - Vonk does not delete it).
Retain the import history
If you run the Administration database on SQL Server or MongoDb it is important to retain the .vonk-import-history
file. This means that if you run Firely Server on something stateless like a Kubernetes pod, or a webapp service, you need to attach file storage on which to store this file. If you do not do that, Firely Server will import all the conformance resources on every start.
Running imports with multiple instances
If you run multiple instances of Firely Server each will have its own /administration
pipeline. So you need to make sure that only 1 instance will perform the import. The import at startup will happen when:
we upgraded to a new version on the FHIR .NET API (always mentioned in the releasenotes)
you add new resources to the
ImportDirectory
resources retrieved from Simplifier are renewed.
To ensure that only one instance runs the import you can do two things:
Make sure only 1 instance is running:
Stop Firely Server
Scale down to 1 instance
Upgrade Firely Server (by referring to a newer image, or installing newer binaries)
Start Firely Server
Let it do the import
Then scale back up to multiple instances.
Exclude the namespace
Vonk.Administration.Api.Import
from the PipelineOptions in branchadministration
on all but one instance.
If you want to use the manual import (<url>/administration/import
) you are advised to apply solution nr. 1 above. In the second solution the call may or may not end up on an instance having the Import functionality.
We are aware that this can be a bit cumbersome. On the Firely Server Roadmap is therefore the story to host the Administration API in its own microservice.
Default Conformance Resources
Firely Server comes with the specification.zip file from the HL7 FHIR API. It contains all the Conformance resources from the specification. These are loaded and used for validation and snapshot generation by default.
Some of the conformance resources (especially SearchParameters) contain errors in the core specification. We try to correct all errors in Errata to the specification. You can also override them yourself by:
updating them through the administration api, as described below;
providing an altered version in the ImportDirectory, with the same id and canonical url.
Attention
The Core Specification provides almost 4000 Conformance Resources. Depending on the machine it may take a few minutes to load and index them.
Load Conformance Resources from disk
Firely Server can read SearchParameter and CompartmentDefinition resources from a directory on disk at startup. The AdministrationImportOptions in the Firely Server settings control from which directory resources are loaded:
"AdministrationImportOptions": {
"ImportDirectory": "<path to the directory you want to import from, default ./vonk-import>",
"ImportedDirectory": "<path to the directory where imported files are moved to, default ./vonk-imported>"
},
- ImportDirectory
All files and zip files will be read, and any conformance resources in them will be imported. By default, STU3 is assumed. If you have R4 conformance resources, place them in a sibling directory that has the same name as your “ImportDirectory” with
.R4
appended to it – so for example./vonk-import.R4
.- ImportedDirectory
This directory will contain the read history in the .vonk-import-history.json file. Please note, that this information is stored directly in the administration database when running on SQlite.
Note that in json you either use forward slashes (/) or double backward slashes (\\) as path separators.
Load Conformance Resources from simplifier.net
You are encouraged to manage and publish your profiles and related Conformance Resources on simplifier.net. If you do that, you can have Firely Server read those. You configure this in the Firely Server settings:
"AdministrationImportOptions": {
"SimplifierProjects": [
{
"Uri": "FHIR endpoint for retrieving StructureDefinitions",
"UserName": "UserName for retrieving the StructureDefinitions",
"Password": "Password for the above user name",
"BatchSize": "<number of resources imported at once, optional - default is 20>"
}
],
}
- Uri
must point to a Simplifier project endpoint, see below on how to get this
- UserName
your username, if you access a private Simplifier project
- Password
password with the username
- BatchSize
you normally don’t need to change this parameter
You can load from multiple Simplifier projects by adding them to the list. The environment variable version of this is:
VONK_Administration:SimplifierProjects:0:Uri=<FHIR endpoint for retrieving StructureDefinitions>
Vonk automatically finds the FHIR version for each project and imports it only for the matching FHIR version.
Get a FHIR endpoint for a Simplifier project
Open the project of your choice on https://simplifier.net. There are two limitations:
You must have access to the project (so either public or private but accessible to you)
The project must be STU3
Then on the overview page of the project click ‘Endpoint’ and copy the value you see there:
By default the endpoint is https://stu3.simplifier.net/<projectname>
Load Conformance Resources on demand
It can be useful to reload the profiles, e.g. after you have finalized changes in your project. Therefore you can instruct Firely Server to actually load the profiles from the source(s) with a separate command:
POST http(s)://<firely-server-endpoint>/administration/importResources
The operation will return an OperationOutcome resource, containing details about the number of resources created and updated, as well as any errors that occurred. Please note that this will also respect the history of already read files, and not read them again.
Manage Conformance Resources with the Administration API
The Firely Server Administration API has a FHIR interface included, on the https://<firely-server-endpoint>/administration
endpoint. On this endpoint you can do most of the FHIR interactions (create, read, update, delete, search) on these resourcetypes:
SearchParameter
StructureDefinition
ValueSet
CodeSystem
CompartmentDefinition
If you are not permitted to access the endpoint for the resource you want to manage (e.g. <firely-server-endpoint>/administration/StructureDefinition
), Firely Server will return statuscode 403.
Note
You can also do the same interactions on the same resourcetypes on the normal (or ‘data’) FHIR endpoint https://<firely-server-endpoint>
. This will only result in storing, updating or deleting the resource. But it will not have any effect on the way Firely Server operates.
Example
To add a StructureDefinition to Firely Server
POST <firely-server-endpoint>/administration/StructureDefinition
In the body provide the StructureDefinition that you want to add.
The Content-Type header must match the format of the body (application/fhir+json or application/fhir+xml)
If you prefer to assign your own logical id to e.g. StructureDefinition ‘MyPatient’, you can use an update:
PUT <firely-server-endpoint>/administration/StructureDefinition/MyPatient
Validating incoming resources
You can have Firely Server validate all resources that are sent in for create or update. The setting to do that is like this:
"Validation": {
"Parsing": "Permissive", // Permissive / Strict
"Level": "Off", // Off / Core / Full
"AllowedProfiles":
[
"http://hl7.org/fhir/StructureDefinition/daf-patient",
"http://hl7.org/fhir/StructureDefinition/daf-allergyintolerance"
]
},
Parsing
Every incoming resource - xml or json - has to be syntactically correct. That is not configurable.
Beyond that, you can choose between Permissive or Strict parsing. Permissive allows for:
empty elements (not having any value, child elements or extensions)
the fhir_comments element
errors in the xhtml of the Narrative
- json specific:
array with a single value instead of just a value, or vice versa (json specific)
- xml specific:
repeating elements interleaved with other elements
elements out of order
mis-representation (element instead of attribute etc.)
Validation
You can choose the level of validation:
Off: no validation is performed.
Core: the resource is validated against the core definition of the resourcetype.
Full: the resource is validated against the core definition of the resourcetype and against any profiles it its
meta.profile
element.
Allow for specific profiles
To enable this feature, set Level
to Full
.
If you leave the list of AllowedProfiles empty, any resource will be allowed (provided it passes the validations set in Parsing and Level).
When you add canonical urls of StructureDefinitions to this list, Firely Server will:
check whether the incoming resource has any of these profiles listed in its meta.profile element
validate the resource against the profiles listed in its meta.profile element.
So in the example above, Firely Server will only allow resources that conform to either the DAF Patient profile or the DAF AllergyIntolerance profile.
Note that the resource has to declare conformance to the profile in its meta.profile
element. Firely Server will not try to validate a resource against all the Validation.AllowedProfiles
to see whether the resource conforms to any of them, only those that the resource claims conformance to.
Upgrading from < 2.0
Previous to version 2.0.0, the setting was "ValidateIncomingResources":"true" // or false
. The corresponding settings since version 2.0.0 are:
true / no AllowedProfiles =>
"Level":"Core"
true / with AllowedProfiles =>
"Level":"Full"
, keep the AllowedProfiles as it is.false =>
"Level":"Off"
Using the In-Memory storage
Navigate to your Firely Server working directory
Changing a setting means overriding it as described in Changing the settings.
Find the
Repository
setting:"Repository": "Memory",
If it is not already set to
Memory
, do so now.You can set SimulateTransactions to “true” if you want to experiment with FHIR transactions. The In-Memory implementation does not support real transactions, so in case of an error already processed entries will NOT be rolled back:
"MemoryOptions": { "SimulateTransactions": "true" },
Using the In-Memory storage for the Administration API database
This works the same as with the normal Firely Server database, except that you put the settings within the Administration
section
E.g.:
"Administration": {
"Repository": "Memory",
"MemoryOptions": {
"SimulateTransactions": "false"
}
}
Warning
Using the In-Memory storage for the administration layer will cause Firely Server to load the specification files on each startup. This takes several minutes, and Firely Server will respond with a ‘423 - Locked’ error to all requests during that time. As of version 0.7.1 we have implemented support for SQLite, which we recommend to use instead of the In-Memory storage. See Prefer SQLite for Firely Server Administration for more information.
Using MongoDB
We assume you already have MongoDB installed. If not, please refer to the MongoDB download pages.
Firely Server can work with MongoDb 4.0 and higher. Since Firely Server (Vonk) version 3.7.0 Firely Server uses the MongoDb Aggregation Framework heavily, and you are advised to upgrade to MongoDb 4.4 (or newer). In this version issue SERVER-7568 <https://jira.mongodb.org/browse/SERVER-7568> is solved, so the most selective index is used more often.
Navigate to your Firely Server working directory
Changing a setting means overriding it as described in Changing the settings.
Find the
Repository
setting:"Repository": "SQLite",
Change the setting to
MongoDb
If you have your own database in MongoDB already, change the
MongoDbOptions
to reflect your settings:"MongoDbOptions": { "ConnectionString": "mongodb://localhost/vonkstu3", "EntryCollection": "vonkentries", "SimulateTransactions": "false" },
If MongoDB does not have a database and/or collection by this name, Firely Server will create it for you.
Find the section called
PipelineOptions
. Make sure it contains the MongoDB repository in the root path for Firely Server Data:"PipelineOptions" : { "Branches" : [ "/" : { "Include" : [ "Vonk.Repository.MongoDb.MongoDbVonkConfiguration" //... ] } ] }
You can set SimulateTransactions to “true” if you want to experiment with FHIR transactions. MongoDB does not support real transactions across documents, so in case of an error already processed entries will NOT be rolled back.
Using MongoDB for the Administration API database
Although we encourage you to use SQLite for Firely Server Administration, you can still use MongoDB for Firely Server Administration as well.
This works the same as with the normal Firely Server database, except that you:
put the settings within the
Administration
sectionprovide a different ConnectionString and/or EntryCollection, e.g.:
"Administration": { "Repository": "MongoDB", "MongoDbOptions": { "ConnectionString": "mongodb://localhost/vonkstu3", "EntryCollection": "vonkadmin", "SimulateTransactions": "false" } }
Find the section called
PipelineOptions
. Make sure it contains the MongoDB repository in the administration path for Firely Server Administration:"PipelineOptions" : { "Branches" : [ "/administration" : { "Include" : [ "Vonk.Repository.MongoDb.MongoDbAdministrationConfiguration" //... ] } ] }
Attention
For MongoDb it is essential to retain the .vonk-import-history.json
file. Please read Retain the import history for details.
Tips and hints for using MongoDb for Firely Server
If searches and/or creates and updates are excessively slow, you may be limited by the IOPS on your MongoDb deployment (e.g. MongoDb Atlas). Try upgrading it and check the timings again.
If for any reason you would like to see how Firely Server is interacting with MongoDb, make the following adjustments to the Log settings:
In the section
Serilog.MinimumLevel.Override
add"Vonk.Repository.DocumentDb": "Verbose"
. Add it before any broader namespaces likeVonk
.In the section on the File sink, change the
restrictedToMinimumLevel
toVerbose
.
- With regards to Firely Server version and MongoDB version:
If you are on a Firely Server (Vonk) version < v3.6, you can keep using MongoDB v4.0 or higher.
If you are on Firely Server (Vonk) v3.6 or higher and are unable to migrate to MongoDB 4.4 (relatively soon), please contact us if you need assistance.
Using SQL server
There are two ways to create the Firely Server database on a SQL Server instance: Have Firely Server create it for you entirely or create an empty database and users yourself and have Firely Server create the schema (tables etc.).
In both cases:
Prepare an instance of SQL Server 2012 or newer. Any edition - including SQL Server Express - will do. The instance will have a servername and possibly an instancename:
server/instance
.Changing a setting means overriding it as described in Changing the settings.
Find the
Repository
setting:"Repository": "SQLite",
Change the setting to
SQL
Find the section called
SqlDbOptions
. It has these values by default:"SqlDbOptions": { "ConnectionString": "connectionstring to your Firely Server SQL Server database (SQL2012 or newer); Set MultipleActiveResultSets=True", "SchemaName": "vonk", "AutoUpdateDatabase": true, "MigrationTimeout": 1800 // in seconds //"AutoUpdateConnectionString" : "set this to the same database as 'ConnectionString' but with credentials that can alter the database. If not set, defaults to the value of 'ConnectionString'" },
Find the section called
PipelineOptions
. Make sure it contains the SQL repository in the root path for Firely Server Data. For Firely Server versions older than 4.3.0 useVonk.Repository.Sql.SqlVonkConfiguration
. For Firely Server v4.3.0 and above useVonk.Repository.Sql.Raw.KSearchConfiguration
:"PipelineOptions" : { "Branches" : [ "/" : { "Include" : [ //"Vonk.Repository.Sql.SqlVonkConfiguration", // use this for FS versions < v4.3.0 "Vonk.Repository.Sql.Raw.KSearchConfiguration", // use this for FS versions >= v4.3.0 //... ] } ] }
The site connectionstrings.com is useful for determining the correct connectionstring for your environment.
If you will only use Windows Accounts, you can use the (default) Authentication Mode, which is Windows Authentication Mode. But if you also want to use SQL Server accounts, you have to run it in Mixed Mode. Refer to Authentication in SQL Server for more information.
Although we encourage you to use SQLite for Firely Server Administration, you can still use SQL Server for Firely Server Administration as well:
"Administration": { "Repository": "SQL", "SqlDbOptions": { "ConnectionString": "Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=VonkAdmin;Data Source=Server\Instance;MultipleActiveResultSets=true", "SchemaName": "vonk", "AutoUpdateDatabase": true, "MigrationTimeout": 1800 // in seconds } }, //... "PipelineOptions" : { "Branches" : [ "/administration" : { "Include" : [ //"Vonk.Repository.Sqlite.SqliteTaskConfiguration", // use this for FS versions < v4.3.0 "Vonk.Repository.Sql.Raw.KAdminSearchConfiguration", // use this for FS versions >= v4.3.0 //... ] } ] }
Have Firely Server create your database
This option is mainly for experimentation as it effectively requires sysadmin privileges for the connecting user.
Prepare a login on SQL Server with the following role:
sysadmin
Set the
SqlDbOptions
for the Firely Server database as follows (the values are example values for connecting with your own Windows login):"SqlDbOptions": { "ConnectionString": "Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=VonkData;Data Source=Server\Instance;MultipleActiveResultSets=true", "SchemaName": "vonk", "AutoUpdateDatabase": true, "MigrationTimeout": 1800 // in seconds },
Set the
SqlDbOptions
underAdministration
for the Administration database likewise:"Administration": { "Repository": "SQL", "SqlDbOptions": { "ConnectionString": "Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=VonkAdmin;Data Source=Server\Instance;MultipleActiveResultSets=true", "SchemaName": "vonk", "AutoUpdateDatabase": true, "MigrationTimeout": 1800 // in seconds } }
You don’t need to set AutoUpdateConnectionString since the ConnectionString will already have enough permissions.
Start Firely Server. It will display in its log that it applied pending migrations. After that the database is created and set up with the correct schema.
If an upgrade to a new version of Firely requires a migration then a SQL time out might occur, halting the upgrade and resulting in a rollback of the migration. The duration of the SQL time out for migrations can be controlled with
MigrationTimeout
. The default value is 1800 seconds (30 min).
Attention
For SQL Server it is essential to retain the .vonk-import-history.json
file. Please read Retain the import history for details.
Create a database and users by script, and have Firely Server create the schema
Log into SQL Server as the Administrator user.
From the working directory open
data01-CreateDatabases.sql
In SQL Server Management Studio, in the menu select Query|SQLCMD Mode.
In the script uncomment and adjust the variable names
dbName
andAdminDbName
as well as any other variables to your own liking.Run the script to create both the Firely Server database and the Administration API database.
From the working directory open
data02-CreateDBUser.sql
In SQL Server Management Studio, in the menu select Query|SQLCMD Mode.
In the script uncomment and adjust the variables at the top names to your own liking.
Run the script to create two users, one with access to the Firely Server database, the other with access to the Administration database. This script grants the database role db_ddladmin to both users, to enable the AutoUpdateDatabase feature. Refer to Overview of permissions for an overview of neccessary authorization for different features.
Set the
SqlDbOptions
for the Firely Server database as follows:"SqlDbOptions": { "ConnectionString": "User Id=<dbUserName>;Password=<dbPassword>;Initial Catalog=<DataDbName>;Data Source=server\\instance;MultipleActiveResultSets=True", "SchemaName": "vonk", "AutoUpdateDatabase": "true" }
If you have set up a different user for running the AutoUpdateDatabase feature, you can provide that:
"SqlDbOptions": { "ConnectionString": "User Id=<dbUserName>;Password=<dbPassword>;Initial Catalog=<DataDbName>;Data Source=server\\instance;MultipleActiveResultSets=True", "SchemaName": "vonk", "AutoUpdateDatabase": "true" "AutoUpdateConnectionString": "User Id=<updateUserName>;Password=<updatePassword>;Initial Catalog=<DataDbName>;Data Source=server\\instance;MultipleActiveResultSets=True", }
Set the
SqlDbOptions
underAdministration
for the Administration database likewise:"Administration" : { "Repository": "SQL", "SqlDbOptions": { "ConnectionString": "User Id=<AdminDbUserName>;Password=<AdminDbPassword>;Initial Catalog=<AdminDbName>;Data Source=server\\instance;MultipleActiveResultSets=True", "SchemaName": "vonk", "AutoUpdateDatabase": "true" } }
For the administration you can also provide different credentials for performing the auto update:
"Administration" : { "Repository": "SQL", "SqlDbOptions": { "ConnectionString": "User Id=<AdminDUserName>;Password=<AdminDbPassword>;Initial Catalog=<AdminDbName>;Data Source=server\\instance;MultipleActiveResultSets=True", "SchemaName": "vonk", "AutoUpdateDatabase": "true" "AutoUpdateConnectionString": "User Id=<updateAdminUserName>;Password=<updateAdminPassword>;Initial Catalog=<AdminDbName>;Data Source=server\\instance;MultipleActiveResultSets=True", } }
Overview of permissions
This table lists the permissions needed to perform specific actions on the SQL database. Recommended roles are listed in the third column. Note that you can create your own database roles with the required permissions in order to execute finely granulated control over permissions.
Action |
Required SQL Permission |
Recommended SQL Role |
Notes |
---|---|---|---|
Create Database |
‘Create any database’ |
sysadmin |
|
AutoUpdateDatabase feature including application of pending migrations |
‘Alter Trace’ + ‘Alter any database’ |
db_ddladmin |
Applies to the normal Firely Server database and its administration database |
AutoUpdateDatabase feature excluding application of pending migrations |
‘Alter Trace’ |
db_ddladmin |
|
Read resources |
db_datareader |
||
Write resources |
db_datawriter |
||
Execute ResetDb feature |
db_ddladmin |
Only applies to the normal Firely Server database |
If the AutoUpdate feature is enabled, we recommend creating two users in order to achieve maximum security while keeping the configuration simple:
User 1 with the ‘db_datareader’ and ‘db_datawriter’ and ‘db_ddladmin’ roles (This user should be used in the AutoUpdateConnectionString)
User 2 with the ‘db_datareader’ and ‘db_datawriter’ roles as well as ‘Alter Trace’ permissions for day to day usage (This user should be used in the ConnectionString)
Using SQLite
SQLite is a file based database engine. The engine itself does not run separately on the server, but in-process in the application, Firely Server in this case.
For more background on SQLite please refer to the SQLite documentation.
SQLite is the default configuration of Firely Server. For the Administration database there is little reason to change this. For the actual runtime data, (the ‘Firely Server database’) itself, you may run into limitations of SQLite if you put it through its paces. You may find one of the other repositories a better fit then. You can safely use different storage engines for Firely Server Data and Firely Server Administration.
Prefer SQLite for Firely Server Administration
Until Firely Server (Vonk) version 0.7.0 you could use any of the storage engines for both Firely Server Data and Firely Server Administration. Starting with Firely Server (Vonk) 0.7.1 you are encouraged to use SQLite for Firely Server Administration. Over time we will deprecate support for running Firely Server Administration on the SQL Server, MongoDb and Memory storage engines. For Firely Server Data you can of course still use the storage engine of your preference.
Firely Server Administration poses very limited stress on its storage engine, therefore SQLite is adequate. And it provides several advantages:
Runs out of the box: SQLite requires no installation of a database engine, but still provides durable storage (unlike the Memory storage). Thus, you don’t need to setup anything to run Firely Server Administration. And you can download the Firely Server binaries and run them without any further configuration.
Flexible on updates: Many of the features that we will add to Firely Server require changes to the schema of the Administration database. By only supporting SQLite for this, we can provide these features to you more quickly.
Readymade database: In the other storage engines, the conformance resources from the specification had to be imported before Firely Server could start. This would take a couple of minutes. Because SQLite is file based, we can run the import process for you and provide you with a readymade Administration database.
Runs with Facades: perhaps the most important feature. If you build a Firely Server Facade, the facade will not provide support for hosting conformance resources. With Firely Server Administration on SQLite the facade has its own storage and you can use Firely Server Administration out of the box. This enables e.g. validation against your custom resources (that can be imported from your Simplifier project), subscriptions, and other use cases.
Settings for using SQLite for Firely Server Data
Changing a setting means overriding it as described in Changing the settings.
Find the
Repository
setting and set it to SQLite if it not already set to that:"Repository": "SQLite",
Find the section called
SQLiteDbOptions
. It has these values by default:"SQLiteDbOptions": { "ConnectionString": "Data Source=./data/vonkdata.db", "AutoUpdateDatabase": true },
Firely Server will create the database file, but please make sure the directory already exists.
Find the section called
PipelineOptions
. Make sure it contains the SQLite repository in the root path:"PipelineOptions" : { "Branches" : [ "/" : { "Include" : [ "Vonk.Repository.SQLite.SqliteVonkConfiguration" //... ] }, //... ] }
Settings for using SQLite for Firely Server Administration
Set the
SqlDbOptions
underAdministration
for the Administration database similar to those above:"Administration" : { "Repository": "SQLite", "SQLiteDbOptions": { "ConnectionString": "Data Source=./data/vonkadmin.db", "AutoUpdateDatabase": "true" } }
Firely Server will create the database file, but please make sure the directory already exists.
Find the section called
PipelineOptions
. Make sure it contains the SQLite repository in the administration path:"PipelineOptions" : { "Branches" : [ "/": { //... }, "/administration" : { "Include" : [ "Vonk.Repository.SQLite.SqliteAdministrationConfiguration" //... ] } ] }
Administration import history in SQLite
When Firely Server imports Conformance resources, it keeps record of what is has imported. Unlike the SQL Server and MongoDb engines, the SQLite storage engine does not use the .vonk-import-history.json file for that. Instead, in SQLite the import history is stored within the Administration database itself.
Using Microsoft Azure CosmosDB
You can connect Firely Server to CosmosDB the same way you connect to MongoDB. There are a few limitations that we will work out later. They are listed below.
Attention
You cannot use CosmosDb for the Firely Server Administration database. Use SQLite instead.
Create a CosmosDB account on Azure, see the Quickstart Tutorial
Make sure you choose the MongoDB API
In the Azure Portal, open your CosmosDB account and go to the ‘Connection Strings’ blade. Copy the ‘Primary Connection String’ to your clipboard.
Now on your own machine, navigate to your Firely Server working directory
Changing a setting means overriding it as described in Changing the settings.
Find the
Repository
setting:"Repository": "Sqlite",
Change the setting to
CosmosDb
If you have your own database in CosmosDB already, change the
CosmosDbOptions
to reflect your settings:"CosmosDbOptions": { "ConnectionString": "<see below>", "EntryCollection": "vonkentries", "SimulateTransactions": "false" },
Paste the ConnectionString from step 3, and add the databasename that you want to use. The connectionstring looks like this:
mongodb://<accountname>:<somerandomstring>==@<accountname>.documents.azure.com:10255?ssl=true&replicaSet=globaldb
You can add the databasename after the portnumber, like this:
mongodb://<accountname>:<somerandomstring>==@<accountname>.documents.azure.com:10255/vonk?ssl=true&replicaSet=globaldb
If your CosmosDB account does not have a database or collection by this name, Firely Server will create it for you.
You can set SimulateTransactions to “true” if you want to experiment with FHIR transactions. Firely Server does not utilize the CosmosDB way of supporting real transactions across documents, so in case of an error already processed entries will NOT be rolled back.
CosmosDB Request Units
If you upload a lot of data in a short time (as is done on reindexing), you quickly exceed the default maximum of 1000 Request Units / second. If you encounter its limits, the Firely Server log will contain errors stating ‘Request rate is large’. This is likely to happen upon reindexing or when using Vonkloader. Solutions are:
Raise the limit to at least 5000 RU/s. See the Microsoft documentation for instructions.
Lower the load
on Reindexing, lower the MaxDegreeOfParallelism, see this warning
with Vonkloader, lower the value of the -parallel parameter.
Limitations
Request size for insertions to CosmosDB is limited to around 5 MB. Some bundles in the examples from the specification exceed that limit. Then you will get an error stating ‘Request size too large’. You can avoid this by limiting the size of incoming resources in the SizeLimits setting.
The CosmosDB implementation of the MongoDB API is flawed on processing
$not
on arrays. This inhibits the use of these searches in Firely Server:Using the
:not
modifierUsing
:missing=true
Configure http and https
You can enable http and/or https and adjust the port numbers for them in Firely Server settings.
Changing the port number
By default Firely Server will run on port 4080 of your system. You can change the port setting by overriding it as described in Changing the settings:
Navigate to your Firely Server working directory
Find this setting:
"Hosting": { "HttpPort": 4080 }
Change the number to the port number you want
Changing from http to https
If you need your server to run on https instead of http, follow these steps:
Navigate to the location where you extracted the Firely Server files.
Find these settings:
"Hosting": { "HttpPort": 4080, "HttpsPort": 4081, // Enable this to use https "CertificateFile": "<your-certificate-file>.pfx", //Relevant when HttpsPort is present "CertificatePassword" : "<cert-pass>" // Relevant when HttpsPort is present },
Uncomment the lines for
HttpsPort
,CertificateFile
andCertificatePassword
.Set the
HttpsPort
to the port of your liking (standard https port is 443)Set
CertificateFile
to the location of the .pfx file that contains the certificate for your siteSet
CertificatePassword
to the password for the certificate file.
Note
We recommend setting this value as an environment variable for security reasons:
VONK_Hosting:CertificatePassword=<password>
To set this:
In Powershell run:
> $env:VONK_Hosting:CertificatePassword="my_password"
where my_password is the password for the .pfx fileor go to your System, open the Advanced system settings –> Environment variables and create a new variable with the name
VONK_Hosting:CertificatePassword
and the value set to your password
You can choose to comment-out the
HttpPort
setting, so Firely Server will no longer be available through unsecured http.
Cross Origin Resource Sharing (CORS)
CORS is enabled in Firely Server. Since Firely Server provides an API that is expected to be consumed by applications from different domains, CORS is enabled for any origin. Currently there is no setting to control this behaviour.
Log settings
Firely Server uses Serilog for logging. The logging settings are controlled in json configuration files called logsettings(.*).json
. The files are read in a hierarchy, exactly like the appsettings files are.
Firely Server comes with default settings in logsettings.default.json
. You can adjust the way Firely Server logs its information by overriding these default settings in logsettings.json
or logsettings.instance.json
. You need to create this file yourself.
Alternatively you can control Log settings with Environment Variables.
Firely Server by default does nog log any Patient Health Information data, regardless of the level of the log. The only PHI part that can be included in the log is the User Name, when running with Smart authorization (so the user is identified) and including this part in the outputTemplate (see below).
Changing the log event level
Serilog defines several levels of log events. From low to high, these are Verbose
, Debug
, Information
,
Warning
, Error
and Fatal
. You can set the minimum level you want to log, meaning that events for that
level or higher will be logged. By default, Firely Server uses Error
as the minimum level of recording information.
To change the level of logging, follow these steps:
Find this setting:
"MinimumLevel": { "Default": "Error", },
Change the setting for
Default
fromError
to the level you need, from the choice ofVerbose
,Debug
,Information
,Warning
,Error
andFatal
.
You can deviate from the default mimimum level for specific namespaces. You do this by specifying the namespace and the log event level you would like for this namespace, for example:
"MinimumLevel": {
"Default": "Error",
"Override": {
"Vonk": "Warning"
}
},
Some additional namespaces you might want to log are:
Vonk.Configuration
to log configuration information on startupVonk.Core.Licensing
to show license information in your logsVonk.Repository.Sql.Raw
to log SQL repository events for Firely Server v4.3.0 and aboveVonk.Repository.Sql
to log SQL repository events for Firely Server v4.2.0 and belowVonk.Repository.Document.Db
to log MongoDB repository eventsVonk.Repository.Memory
to log memory database repository eventsVonk.Core.Repository.EntryIndexerContext
, set it to"Error"
if you have excessive warnings about indexing (mostly when importing Synthea <https://synthea.mitre.org/downloads> data)Microsoft
to log events from the Microsoft librariesMicrosoft.AspNetCore.Diagnostics
to report request handling timesSystem
to log events from the System libraries
Please note that the namespaces are evaluated in order from top to bottom, so more generic ‘catch all’ namespaces should be at the bottom of the list.
So this will log events on Vonk.Repository.Sql.Raw
on Information
level:
"MinimumLevel": {
"Default": "Error",
"Override": {
"Vonk.Repository.Sql.Raw": "Information",
"Vonk": "Warning"
}
},
But in this (purposefully incorrect) example the Warning
level on the Vonk
namespace will override the Information
level on the Vonk.Repository.Sql.Raw
namespace:
"MinimumLevel": {
"Default": "Error",
"Override": {
"Vonk": "Warning",
"Vonk.Repository.Sql.Raw": "Information"
}
},
Changing the sink
Another setting you can adjust is WriteTo
. This tells Serilog which sink(s) to log to.
Serilog provides several sinks, and for Firely Server you can use Console
, File
, ApplicationInsights
and Seq
. All of which can be wrapped in an Async
sink to avoid blocking Firely Server when waiting for the sink to process the log statements.
Console
The Console sink will write to your shell.
Find the
WriteTo
setting:"WriteTo": [ { "Name": "Async", "Args": { "configure": [ { "Name": "Console", "Args": { "restrictedToMinimumLevel": "Information", "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {UserId} {Username} [{Level}] [ReqId: {RequestId}] {Message}{NewLine}{Exception}" } } ] } }, { //Settings for other sinks }
The Console is notoriously slow at processing log statements, so it is recommended to limit the number of statements for this sink. Use the restrictedToMinimumLevel
to do so. Also, if you are on Windows, the Powershell command window appears to be faster than the traditional Command Line window.
Settings for the Console sink:
outputTemplate
: What information will be in each log line. Besides regular text you can use placeholders for information from the log statement:
{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}
: When this was logged, with formatting
{UserId}
: Technical id of the logged in user - if applicable
{Username}
: Name of the logged in user - if applicable
{Application}
: Name of the application (in case other applications are logging to the same sink). Is set toVonk
at the bottom of the logsettings file
{Level}
: Level of the log, see the values in Changing the log event level
{MachineName}
: Name of the machine hosting the Firely Server instance. Expecially useful when running multiple instances all logging to the same file.
{RequestId}
: Unique id of the web request, useful to correlate log statements
{Message}}
: Actual message being logged
{Exception}
: If an error is logged, Firely Server may include the original exception. That is then formatted here.
{SourceContext}
: The class from which the log statement originated (this is usually not needed by end users).
{NewLine
}: Well, ehh, continue on the next line
restrictedToMinimumLevel
: Only log messages from this level up are sent to this sink.
File
The File
sink will write to a file, possibly rolling it by interval or size.
Find the
WriteTo
setting:"WriteTo": [ { { //Settings for Console } }, { "Name": "Async", "Args": { "configure": [ { "Name": "File", "Args": { "path": "%temp%/vonk.log", "rollingInterval": "Day", "fileSizeLimitBytes": "", "retainedFileCountLimit": "7", "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {UserId} {Username} [{Application}] [{Level}] [Machine: {MachineName}] [ReqId: {RequestId}] {Message}{NewLine}{Exception}", "restrictedToMinimumLevel": "Verbose" } } ] } }, { //Settings for Azure ApplicationInsights }
Under
File
, change the location of the logfiles by editing the value forpath
. For example:{ "Name": "RollingFile", "Args": { "path": "c:/logfiles/vonk.log" } },
Other values that you can set for the File log are:
rollingInterval
: When this interval expires, the log system will start a new file. The start datetime of each interval is added to the filename. Valid values areInfinite
,Year
,Month
,Day
,Hour
,Minute
.fileSizeLimitBytes
: Limit the size of the log file, which is 1GB by default. When it is full, the log system will start a new file.retainedFileCountLimit
: If more than this number of log files is written, the oldest will be deleted. Default value is 31. Explicitly setting it to an empty value means files are never deleted.outputTemplate
: as described for Console.restrictedToMinimumLevel
: as described for Console.
Application Insights
Firely Server can also log to Azure Application Insights (“Application Insights Telemetry”). What you need to do:
Create an Application Insights instance on Azure.
Get the InstrumentationKey from the Properties blade of this instance.
Add the correct sink to the logsettings.json:
"WriteTo": [ { "Name": "ApplicationInsightsTraces", "Args": { "instrumentationKey": "<the key you copied in step 2>", "restrictedToMinimumLevel": "Verbose" //Or a higher level } }, ],
This also enables Dependency Tracking for access to your database. This works for both SQL Server and MongoDB. And for the log sent to Seq if you enabled that.
If you set the level for Application Insights to
Verbose
, and combine that with Database details, you get all the database commands right into Application Insights.
Seq
Seq is a web interface to easily inspect structured logs.
For the Seq
sink, you can also specify arguments. One of them is the server URL for your
Seq server:
"WriteTo": [
{
"Name": "Seq",
"Args": { "serverUrl": "http://localhost:5341" }
}
Change
serverUrl
to the URL of your Seq serverrestrictedToMinimumLevel
: as described for Console.
Database details
Whether you use MongoDB or SQL Server, you can have Firely Server log in detail what happens towards your database. Just set the appropriate loglevel to ‘Verbose’:
"MinimumLevel": {
"Default": "Error",
"Override": {
"Vonk.Repository.Sql.Raw": "Verbose",
"Vonk.Repository.Document.Db": "Verbose",
"Vonk": "Warning"
}
},
If you do so you probably don’t want all this detail in your console sink, so you can limit the level for that, see Console above.
Firely Server deployment options
Firely Server can be deployed as:
Binaries, see Getting Started
As Docker image, see Using Firely Server on Docker
With Yellow Button - Firely Server for your Simplifier project
The Binaries can be deployed on your own machines and on cloud services. For deployment on Azure we included instructions.
In order to test the Access control and SMART you will need to Set up an Identity Provider.
Using Firely Server on Docker
We have created a Docker image for Firely Server, so you can run the server in any environment that supports Docker. For this section we assume you have Docker installed on your system. If you want to install Docker for Windows, please read Installing Docker For Windows for specific installation details.
Getting started
Before you can run Firely Server, you will need to pull the Docker Firely Server container and request a license.
Open your favourite command line tool and execute this command:
> docker pull simplifier/vonk
Go to the Simplifier website, login and download your evaluation license.
Create a working directory for Firely Server and place the license file there.
Warning
If you use Docker, you may want to run multiple instances of Firely Server (e.g. with Kubernetes). Read Running imports with multiple instances for caveats with the Administration endpoint.
Running a Docker Firely Server in SQLite mode
The easiest and the default way to run a Docker Firely Server container is to run Firely Server in SQLite repository mode. Note that this is not the most performant mode - see MongoDB and SQL Server options below for that.
Open your command prompt and execute this command:
> docker images simplifier/vonk
You will get a list that looks like:

Navigate to your working directory for Firely Server and run the container with this command:
in cmd.exe:
docker run -d -p 8080:4080 --name vonk.server -v %CD%/firelyserver-license.json:/app/firelyserver-license.json simplifier/vonk
in Powershell:
docker run -d -p 8080:4080 --name vonk.server -v ${PWD}/firelyserver-license.json:/app/firelyserver-license.json simplifier/vonk
If your license file has a different name, use that name instead of firelyserver-license
on the left side of the -v parameter in the command above. E.g. -v ${PWD}/my-fancy-license.json:/app/firelyserver-license.json
This will spin up a Firely Server container. It maps the host port 8080 to the container port 4080 with the switch -p 8080:4080
. It will give the
container the name vonk.server with the switch --name vonk.server
.
Furthermore it mounts the local licensefile into the /app
directory, which is the directory where Firely Server resides in the container. Finally it will run the container in background mode with the switch -d
.
To test whether the container is running correctly, type the command:
> docker ps

You can also take a look at the logs for Firely Server with:
> docker logs vonk.server
Open a browser and use the address http://localhost:8080/
. This will show the landing page of Firely Server.
To stop the container just type:
> docker stop vonk.server
And to start it again:
> docker start vonk.server
To completely remove the container:
> docker rm vonk.server
Adjust settings when running in a Docker container
In the paragraph above we showed how to use -v
to mount the license file into the app directory. We can use the same technique to mount a custom settings file or logsettings file. Or a directory to host the SQLite database outside of the container. Or have the logfile written to your working directory. Below is a full example using PowerShell. For creating appsettings.instance.json
refer to Firely Server settings. For creating logsettings.instance.json
refer to Log settings.
appsettings.instance.json
{
"SQLiteDbOptions": {
"ConnectionString": "Data Source=./resourcedata/vonkdata.db;Cache=Shared",
"AutoUpdateDatabase": true
}
}
logsettings.instance.json
Settings for console and levels not included for brevity.
{
"Serilog": {
"WriteTo": [
{
"Name": "Async",
"Args": {
"configure": [
{
"Name": "File",
"Args": {
"path": "./log/vonk.log",
"rollingInterval": "Day",
"fileSizeLimitBytes": "",
"retainedFileCountLimit": "7",
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {UserId} {Username} [{Application}] [{Level}] [Machine: {MachineName}] [ReqId: {RequestId}] {Message}{NewLine}{Exception}",
"restrictedToMinimumLevel": "Information"
}
}
]
}
},
],
"Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ],
"Properties": {
"Application": "Firely Server",
"Environment": "Default"
}
}
}
Powershell
mkdir logs
mkdir resourcedata //do not use 'data' - the administration database is already in that folder in the container
//create the appsettings.instance.json above
//create the logsettings.instance.json above
docker run -d -p 8080:4080 --name firely.server `
-v ${PWD}/firelyserver-license.json:/app/firelyserver-license.json `
-v ${PWD}/appsettings.instance.json:/app/appsettings.instance.json `
-v ${PWD}/logsettings.instance.json:/app/logsettings.instance.json `
-v ${PWD}/resourcedata:/app/resourcedata `
-v ${PWD}/log:/app/log `
simplifier/vonk:4.0.0
You should see a vonkdata.db
appear in the ./resourcedata
folder, and a log file in the ./log
folder. From here you can experiment with other settings. You can also easily keep different settings files side-by-side, mapping the one you want to test into the container, e.g. -v ${PWD}/some-weird-settings.json:/app/appsettings.instance.json
.
Spinning up with a docker-compose file
Another way to spin up a Firely Server container is to use a docker-compose file. The above example can also be established by the following docker-compose-sqlite.yml
:
1version: '3'
2
3services:
4
5 vonk-web:
6 image: simplifier/vonk
7 ports:
8 - "8080:4080"
9 environment:
10 - VONK_Repository=SQLite
11 - VONK_Administration:Repository=SQLite
12 - VONK_License:LicenseFile=./license/firelyserver-trial-license.json
13 volumes:
14 - .:/app/license
Save the text above to a file in your working directory with the name docker-compose.sqlite.yml
and then run the following command:
> docker-compose -f docker-compose.sqlite.yml up -d
If your license file has a different name, use that name instead of firelyserver-trial-license
in the text above - but make sure to keep ./license
as that maps to a Docker volume inside the container.

To stop the container, run:
> docker-compose -f docker-compose.sqlite.yml down
Note
Strictly the settings for VONK_Repository
and VONK_Administration
are not needed here, since SQLite is the default setting. The settings are included to show where to configure the type of database to use.
Much more information on that topic is in the paragraphs below.
Running Docker with a SQL Server container
Firely Server can use also other repositories than Memory, for example SQL Server. This section describes how to spin up a Firely Server container and a SQL Server container. We will use docker-compose to achieve this.
Warning
SQL Server container uses at least 3.25 GB of RAM. Make sure to assign enough memory to the Docker VM if you’re running on Docker for Mac or Windows.
Warning
If you also run the Administration database on SQL Server, please read Retain the import history.
1version: '3'
2
3services:
4 vonk-web:
5 image: simplifier/vonk
6 ports:
7 - "8080:4080"
8 depends_on:
9 - vonk-sqlserver-db
10 environment:
11 - VONK_Repository=SQL
12 - VONK_SqlDbOptions:ConnectionString=Initial Catalog=VonkStu3;Data Source=vonk-sqlserver-db,1433;User ID=sa;Password=SQLServerStrong(!)Password*
13 - VONK_SqlDbOptions:SchemaName=vonk
14 - VONK_SqlDbOptions:AutoUpdateDatabase=true
15 - VONK_SqlDbOptions:AutoUpdateConnectionString=Initial Catalog=VonkStu3;Data Source=vonk-sqlserver-db,1433;User ID=sa;Password=SQLServerStrong(!)Password*
16 - VONK_Administration:Repository=SQL
17 - VONK_Administration:SqlDbOptions:ConnectionString=Initial Catalog=VonkAdmin;Data Source=vonk-sqlserver-db,1433;User ID=sa;Password=SQLServerStrong(!)Password*
18 - VONK_Administration:SqlDbOptions:SchemaName=vonkadmin
19 - VONK_Administration:SqlDbOptions:AutoUpdateDatabase=true
20 - VONK_Administration:SqlDbOptions:AutoUpdateConnectionString=Initial Catalog=VonkAdmin;Data Source=vonk-sqlserver-db,1433;User ID=sa;Password=SQLServerStrong(!)Password*
21 - VONK_License:LicenseFile=./license/firelyserver-trial-license.json
22 volumes:
23 - .:/app/license
24
25 vonk-sqlserver-db:
26 image: microsoft/mssql-server-linux
27 ports:
28 - "1433:1433"
29 environment:
30 - ACCEPT_EULA=Y
31 - SA_PASSWORD=SQLServerStrong(!)Password*
32 healthcheck:
33 test: /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'SQLServerStrong(!)Password*' -Q 'SELECT 1 FROM VonkSTU3.sys.tables'
34 interval: 1m30s
35 timeout: 10s
36 retries: 3
Save the text above to a file in your working directory with the name docker-compose.mssqlserver.yml
. Make sure your Firely Server license file is named
firelyserver-trial-license.json
and is residing in your working directory (see Getting started on how to obtain the license), not in a subdirectory named license
(that is an internal directory inside the container).
If your license file has a different name, use that name instead of firelyserver-trial-license
in the text above.
Then use this command to spin up a Firely Server container and SQL container:
> docker-compose -f docker-compose.mssqlserver.yml up -d
Open a browser and use the address http://localhost:8080/
. This will show the landing page of Firely Server.
Warning
Wait for about 2 minutes, because it takes a while to fire up the SQL container
Running Docker with a SQL Server on host
Another possibility is to run a Firely Server container with a SQL Server repository on the host. You will need a Microsoft SQL Server running on your host. The version of SQL Server must at least be version 2012.
Warning
If you also run the Administration database on SQL Server, please read Retain the import history.
To run the Firely Server container we will use the following docker-compose file:
1version: '3'
2
3services:
4
5 vonk-web:
6 image: simplifier/vonk
7 ports:
8 - "8080:4080"
9 environment:
10 - VONK_Repository=SQL
11 - VONK_SqlDbOptions:ConnectionString=Database=VonkStu3;Server=my_host\<myInstanceName>;User ID=<myUser>;Password=<myPassword>
12 - VONK_SqlDbOptions:SchemaName=vonk
13 - VONK_SqlDbOptions:AutoUpdateDatabase=true
14 - VONK_SqlDbOptions:AutoUpdateConnectionString=Database=VonkStu3;Server=my_host\<myInstanceName>;User ID=<DLLUser>;Password=<myPassword>
15 - VONK_Administration:Repository=SQL
16 - VONK_Administration:SqlDbOptions:ConnectionString=Database=VonkAdmin;Server=my_host\<myInstanceName>;User ID=<myUser>;Password=<myPassword>
17 - VONK_Administration:SqlDbOptions:SchemaName=vonkadmin
18 - VONK_Administration:SqlDbOptions:AutoUpdateDatabase=true
19 - VONK_Administration:SqlDbOptions:AutoUpdateConnectionString=Database=VonkAdmin;Server=my_host\<myInstanceName>;User ID=<DLLUser>;Password=<myPassword>
20 - VONK_License:LicenseFile=./license/firelyserver-trial-license.json
21 volumes:
22 - .:/app/license
23 extra_hosts:
24 - "my_host:192.0.2.1"
Save the text above to a file in your working directory with the name docker-compose.mssqlserver_host.yml
. Before we spin up the container we have
to adjust the docker-compose.mssqlserver_host.yml
:
On lines 11, 14, 16 and 19 the connection string to the database server is stated. Change the
Server
to your database server and instance name.Also change the
User ID
andPassword
on lines 11, 14, 16 and 19 to your credentials.Furthermore we have to tell Docker which IP address the host uses. This is done on line 24. In this case the host (named my_host) uses IP address 192.0.2.1. Change this to the appropriate address.
After saving your settings, make sure your Firely Server license file is named firelyserver-trial-license.json
and is residing in your working directory
(see Getting started on how to obtain the license), not in a subdirectory named license
(that is an internal directory inside the container). Or use the name of your license file instead of firelyserver-trial-license
in the text above.
You can run the Firely Server container as follows:
> docker-compose -f docker-compose.mssqlserver_host.yml up -d
A database will automatically be created if is not already present on the database server. See this page for an overview of permissions the database user needs for creating the database and/or schema.
Open a browser and use the address http://localhost:8080/. This will show the landing page of Firely Server.
Warning
When you have a firewall installed on your host machine, it can block traffic from your Firely Server container to your host. Provide an inbound rule to allow traffic from the container to the host.
Run Docker with a MongoDB container
This section describes how to spin up a Firely Server container and a MongoDB container using a docker-compose. We assume you already have MongoDB installed.
Warning
If you also run the Administration database on MongoDb, please read Retain the import history.
1version: '3'
2
3services:
4
5 vonk-web:
6 image: simplifier/vonk
7 environment:
8 - VONK_Repository=MongoDb
9 - VONK_MongoDbOptions:ConnectionString=mongodb://vonk-mongo-db/vonkstu3
10 - VONK_MongoDbOptions:EntryCollection=vonkentries
11 - VONK_Administration:Repository=MongoDb
12 - VONK_Administration:MongoDbOptions:ConnectionString=mongodb://vonk-mongo-db/vonkadmin
13 - VONK_Administration:MongoDbOptions:EntryCollection=vonkentries
14 - VONK_License:LicenseFile=./license/firelyserver-trial-license.json
15 volumes:
16 - .:/app/license
17 ports:
18 - "8080:4080"
19
20 vonk-mongo-db:
21 image: mongo
Save the text above to a file in your working directory with the name docker-compose.mongodb.yml
. Make sure your Firely Server license file is named firelyserver-trial-license.json
and is residing in your working directory (see Getting started on how to obtain the license), not in a subdirectory named license
(that is an internal directory inside the container).
If your license file has a different name, use that name instead of firelyserver-trial-license
in the text above.
Use this command to spin up a Firely Server container and MongoDB container:
> docker-compose -f docker-compose.mongodb.yml up -d
Open a browser and use the address http://localhost:8080/. This will show the landing page of Firely Server.
Firely Server deployment on Azure Web App Service
In this section we explain how you can deploy Firely Server in the Azure cloud.
Getting started
Before you can run Firely Server, you will need to download the Firely Server binaries and request a license:
Go to the Simplifier website, login and download the Firely Server binaries from https://simplifier.net/vonk/download
Download the trial license file from the same location.
Deployment
Go to Azure (https://portal.azure.com) and create a web app:
Choose a name for the webapp, we will use the placeholder <webapp>. Fill in an existing resource group or create a new one and select Windows for the operation system (OS):
Add the trial license file (firelyserver-trial-license.json) to the vonk_distribution.zip by dragging the license file into the zipfile.
Open a webbrowser, navigate to
https://<webapp>.scm.azurewebsites.net/ZipDeployUI
and drag vonk_distribution.zip into the browser window. This will install the Firely Server as a Web App in Azure. In our example the url ishttps://<webapp>.scm.azurewebsites.net/ZipDeployUI
This method of deployment does not work in Internet Explorer. It does work in Firefox, Chrome and Edge.Open a browser and go to the site
https://<webapp>.azurewebsites.net/
. This will show the Firely Server home page.
Change database
In this example Firely Server is using a memory repository. If you want to change it to another kind of repository then you could change that on the page Application Settings of the Web App. Here you can set Environment Variables with the settings for either SQL Server or MongoDB. For example for MongoDB it will look like this:

More information
About Azure zip deployment: https://docs.microsoft.com/en-us/azure/app-service/app-service-deploy-zip#deploy-zip-file
Deploy Firely Server on a reverse proxy
Why
For ASP.NET Core 1.0 Microsoft suggested to always use another web server in front of Kestrel for public websites. For ASP.NET Core 2.0, while this is not a hard constraint anymore there are still a series of advantages in doing so:
some scenarios like sharing the same IP and port by multiple applications are not yet supported in Kestrel
helps in limiting the exposed surface area
provides an additional layer of configuration and defense
provides process management for the ASP.NET Core application (ensuring it restarts after it crashes)
in some scenarios a certain web server already integrates very well
helps simplifying load balancing and SSL setup
Hence using a reverse proxy together with the Kestrel server allows us to get benefits from both technologies at once.
With IIS
A common option on Windows is using IIS: for instructions on how to deploy Firely Server on IIS see Deploy Firely Server on IIS.
For a comparison of IIS and Kestrel features at the moment of this writing you can check here.
With Nginx
A popular open source alternative is Nginx. For instruction on how to deploy Firely Server on Nginx see Deploy Firely Server on Nginx on Linux
Deploy Firely Server on IIS
Prerequisites
The following operating systems are supported: Windows 7 or Windows Server 2008 R2 and later
Have IIS windows feature turned on on the hosting machine. You can do this in Control Panel -> Programs and Features -> Turn Windows features on or off . You need to check Internet Information Services -> Web Management Tools -> IIS Management Console and Internet Information Services -> World Wide Web Services to accept the default features or customize IIS to fit your needs.
In order to have IIS start the Firely Server right away, you will have to enable the “Application Initialization” setting:
To enable PUT and DELETE interactions, you will have to turn off WebDAV:
See https://stackoverflow.com/questions/6739124/iis-7-5-enable-put-and-delete-for-restful-service-extensionless for some background information. If you do not want to disable WebDAV for all of IIS, you can also disable it just for Firely Server using a setting in web.config, see Configuration.
Choose a solution to deploy/move the application to the hosting system. Multiple alternatives exist like Web Deploy, Xcopy, Robocopy or Powershell. One popular choice is using Web Deploy in Visual Studio. For using that you will need to install Web Deploy on the hosting system. To install Web Deploy, you can use the Web Platform Installer (https://www.microsoft.com/web/downloads/platform.aspx).
Install the .NET Core Runtime & Hosting bundle on the hosting system. After installing it, you may need to do a “net stop was /y” and “net start w3svc” to ensure all the changes are picked up for IIS. The bundle installs the .NET Core Runtime, .NET Core Library, and the ASP.NET Core Module. ASP.NET Core Module (ANCM) allows you to run ASP.NET Core applictions using Kestrel behind IIS. For more information about ANCM check https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/aspnet-core-module
Prepare binaries. You can either download the binaries for Firely Server (see Getting Started), or create your own solution by building a facade.
If you are deploying a Firely Server facade in your own web server application, take the additional following prerequisites into consideration:
Make sure you use the IISIntegration NuGet package. You can install this as part of one of the metapackages (
Microsoft.AspNetCore
andMicrosoft.AspNetCore.All
) or independentlyMicrosoft.AspNetCore.Server.IISIntegration
. This is needed for the interoperability between Kestrel and ANCM.Provide a web.config file for configuring the ANCM module or make sure the selected deploy options generates one for you. Using dotnet publish or Visual studio publish would generate a web.config for you. Check https://docs.microsoft.com/en-us/aspnet/core/hosting/aspnet-core-module for guidance on configuring the ANCM.
Create Website in IIS
Publish the application to a location on the host server, using the solution selected in the Prerequisites step 3.
In IIS Manager create a new website or a new application under existing IIS site. Fill the Site name, the Binding and link the Physical path to the folder created in the previous step, similar to the picture below. The bindings defined in IIS override any bindings set in the application by calling either Listen or UseUrls.

Edit the application pool to set the .NET CLR VERSION to NO Managed Code, similar to the picture below (we use IIS as a reverse proxy, so it isn’t actually executing any .NET code). To edit the application pool, go to the Application Pools panel, right-click the website’s app pool and select Advanced Settings… from the popup menu.

Attention
IIS will pause a website if it is idle for a while. Pausing a dotnet process is the same as shutting
it down, so this means that IIS shuts down the Firely Server.
This causes problems with each first request that is sent to Firely Server after an idle period. Firely Server needs a couple of seconds to start up
again, and answers with a 423 Lock Error
while it loads.
Make sure to set Start Mode to AlwaysRunning to prevent IIS from shutting down Firely Server.
Go to your IIS Sites, and click on your Firely Server website. On the right, choose Advanced settings….
Set Preload Enabled to True to make IIS load the Firely Server right away, instead of on the very first request. Otherwise this first request results in a423 Lock Error
as described above.

Configuration
You can use web.config to configure ASP.NET Core Module and IIS using the <system.webServer> section. Read more about configuring ANCM at https://docs.microsoft.com/en-us/aspnet/core/hosting/aspnet-core-module.
If you need to disable WebDAV for Firely Server, so you can perform PUT and DELETE interactions, add this to the web.config file:
<modules> <remove name="WebDAVModule" /> </modules> <handlers> <remove name="WebDAV" /> </handlers>
You can configure the Firely Server using the appsettings.json file (see Configuring Firely Server).
If you are building a Firely Server facade, you can use IISOptions to configure IISIntegration service options. You can read more about IISOptions at https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.iisoptions?view=aspnetcore-2.0.
services.Configure<IISOptions>(options => { ... });
SQL
In order to use the Sql Repository option in IIS you should make sure that the identity of the IIS application pool has rights to use the database considering the provided connection string. To change the identity the application pool is using open IIS ➡️ Application Pools ➡️ select your application pool ➡️ right click and select “Advanced Settings…” You should see something similar to the image below:

Extra
If you’d like to set Firely Server environment variables via an Azure pipelines task, you can do so by setting the application pool’s environment variables. For example, to pass the variable TEST
to Firely Server that’s housed in the fhir
application pool, do the following:
%systemroot%\system32\inetsrv\APPCMD set config -section:system.applicationHost/applicationPools /-"[name='fhir'].environmentVariables.[name='VONK_TEST']" /commit:apphost
%systemroot%\system32\inetsrv\APPCMD set config -section:system.applicationHost/applicationPools /+"[name='fhir'].environmentVariables.[name='VONK_TEST',value='some_value_here']" /commit:apphost
Deploy Firely Server on Nginx on Linux
About Nginx
NGINX is a popular open source web server. It can act as a reverse proxy server for TCP, UDP, HTTP, HTTPS, SMTP, POP3, and IMAP protocols, as well as a load balancer and a HTTP cache. You can find the documention for the Nginx server at https://nginx.org/en/docs/.
Prerequisites
The following linux distribution are supported: Ubuntu, RHEL, Debian, Fedora, CentOS, SLES
Install .Net Core on the machine (see https://www.microsoft.com/net/learn/get-started/linuxubuntu)
Install Nginx
sudo apt-get install nginx
Start Kestrel Firely Server
Download the binaries for Firely Server (see Getting Started), open a terminal console and start the Firely Server process by using: dotnet Vonk.Server.dll. You should be able to reach to home page at http://localhost:4080 (or a different port if you changed the default configurations)
Configure Nginx as a reverse proxy
To configure Nginx as a reverse proxy to forward requests to our ASP.NET Core application, modify /etc/nginx/sites-available/default. Open it in a text editor, and replace the contents with the following:
server {
listen 80;
# Match incoming requests with the following path and forward them to
# the location of the Kestrel server.
# See http://nginx.org/en/docs/http/ngx_http_core_module.html#location
location / {
#This should match the location where you deployed the Firely Server binaries with the Kestrel server.
#This can be on the same machine as the Nginx server or on a separate dedicated machine
proxy_pass http://localhost:4080;
# The Kestrel web server we are forwarding requests to only speaks HTTP 1.1.
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
# Adds the 'Connection: keep-alive' HTTP header.
proxy_set_header Connection keep-alive;
# Forwards the Host HTTP header.
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
Now you can run the Firely Server.
Configuration
To configure the Firely Server, you can use the appsettings.json file (see Configuring Firely Server).
To configure Nginx you need to add extra options to the /etc/nginx/sites-available/default or to the nginx.conf file.
To monitor the application you can use systemd and create a service for starting, stopping and managing the process.
Set up an Identity Provider
About Identity Providers and Firely Server
In order to use Access control and SMART you need an Identity Provider that can provide OAuth2 JWT Tokens with claims that conform to SMART on FHIR. In a production scenario, you typically already have such a provider. It could be the EHR system, the Active Directory, or a provider set up specifically for let’s say a Patient Portal. It is also very well possible that the provider handing the correct claims uses a federated OAuth2 provider to do the authentication.
An Identity Provider for testing
To allow you to test Access control and SMART, we provide you with instructions to build and run an Identity Provider in which you can configure the necessary clients, claims and users yourself to test different scenarios. The instructions are based on the excellent IdentityServer4 project on GitHub by Dominick Baier and Brock Allen.
By default, the configuration is such that you can test many different cases. If you wish to adjust the configuration, that will require a bit of programming.
The Identity Provider is built in Microsoft .NET Core. Therefore it should also run cross-platform, just as Firely Server itself. However, we did not try that.
Note
The project below is provided for your convenience. It comes with no warranty and is not supported by Firely.
In order to get tokens from the Identity Provider you need an http client. We included instructions on Access Control Tokens with Postman.
Instructions
Clone the project Vonk.IdentityServer.Test from GitHub
Run the Powershell script .\scripts\GenerateSSLCertificate.ps1
This will generate an SSL Certificate in .\Vonk.IdentityServer.Test\ssl_cert.pfx, with the password
‘cert-password’. This is preconfigured in Program.cs.Open the solution Vonk.IdentityServer.Test.sln in Visual Studio
Build the solution
Run the Vonk.IdentityServer.Test project
Visual Studio should automatically open http://localhost:5100 in your browser.
You should see a page like this.
Also try https://localhost:5101 for the https connection. Your browser will ask you to make a security exception for the self-signed certificate.
Get the openid connect configuration at http://localhost:5100/.well-known/openid-configuration. You can see all the available scopes in this document.
Configuration
The Identity Server is preconfigured with two users and one client:
Client
- ClientId
Postman
- Secret
secret
- Redirect Uri
This client is allowed to request any of the available scopes.
It is called Postman, since many users use the Postman REST client to test FHIR Servers. If you use another client, you can still use it as the ClientId, or alter the values in Config.cs.
Users
Alice
- UserName
Alice
- Password
password
- Launch context
patient=alice-identifier
Bob
- UserName
Bob
- Password
password
- Launch context
patient=bob-identifier
You can add or alter users in Config.cs.
Access Control Tokens with Postman
You can use Postman to get a JWT Token from the IdentityServer, and use that in a subsequent request to your local Firely Server instance.
Make sure IdentityServer is running (see Set up an Identity Provider), I assume at http://localhost:5100
Open Postman Settings (menu: File | Settings) and turn ssl certificate validation off, otherwise your self-signed certificate will not be accepted.
Open a request in Postman, let’s say GET /Patient
Verify that you get a 401 (smile)
Go to the Headers tab and make sure there is no Authorization header (if there is, it might have an outdated token, and you don’t want that)
Go to the Authorization tab, that looks like this:
In the ‘Type’ dropdown choose OAuth2 (SMART uses OpenIdConnect, which is a specialization of OAuth2)
In the ‘Add authorization data to’ dropdown choose ‘Request headers’ (probably preselected)
Now fill in the blank fields under section ‘Configure New Token’.
Take special care to use https in the AUTH URL and Access Token URL fields.
You can alter the values in ‘Scope’ to get other claims in the token.
Click ‘Get New Access Token’ and you’ll be presented with the login screen of IdentityServer:
Log in as Bob or Alice and you return to Postman with the newly retrieved token:
Optionally, you can copy the value of the access token and paste it into JWT.io. It will show you the contents of the token.
Back in Postman, click ‘Use Token’.
The token will be added as Authorization header to the request (make sure you have disabled ‘Hide auto-generated headers’ in the Headers tab):
Issue the original request again. Provided there is a Patient with the identifier of Bob or Alice (or whomever you chose), it will be in the search results.
Performance of Firely Server
This section contains a general approach to Firely Server performance and SQL Server guidelines:
Performance of Firely Server
About Performance
What is the performance of Firely Server? That is a simple question, but unfortunately it has no answer. Or more precisely, the answer depends on many variables. On this page we try to give you insight into those variables, introduce you to testing performance yourself, and finally present the results of the tests that we run ourselves.
Performance variables
Firely Server Configuration
Firely Server can be run as self contained FHIR Server or as a Facade on top of an existing system. The performance of a Facade is heavily dependent on the performance of the system it is built on top of. The self contained server can run on different databases. On top of that you can configure a couple of features in the settings that influence the performance. These are the most important configuration variables to take into account:
Memory: Memory is only meant for quick tests, and for use in unittests. Do not use it in any serious scenario, much less for performance critical scenarios.
SQLite: SQLite is mainly used for the Administration database of Firely Server, but you can also use it for the main database. Deployment is very easy because of the zero footprint of the driver, but be aware of its limits. Firely Server must have very fast access to the database file, so effectively it has to be on a local disk. Multithreading does work, but does not scale as well as other databases.
SQL Server: Performance tuning of SQL Server is a topic on its own. Firely Server manages the tables it needs, and the indexes on top of it are optimized for the way Firely Server queries them. See SQL Server Guidelines for more details about SQL Server database maintenance and tuning.
MongoDB: Performance tuning of MongoDB is, as well, a topic on its own. Firely Server manages the collections it needs, and the indexes on top of it are optimized for the way Firely Server queries them. MongoDB is used in our own performance tests, see below.
By default, a resource that is sent to Firely Server is fully validated against its StructureDefinition. This requires extra processing and thus extra time. But you can disable full validation if needed, with the ValidateIncomingResources setting. We have no tests in place yet to time the difference caused by this setting.
When a resource is sent to Firely Server for storage, Firely Server indexes the resource for all the search parameters that are applicable to the resource. The more search parameters are known to Firely Server, the more resources will be used for indexing - both time (for the indexing processing) and storage (for storing the values of each search parameter). This also increases the size of the index tables and indexes, and therefore querying times. Thus, if you know you will only use a portion of the predefined search parameters you can choose to delete the others from the Administration API - see Conformance Resources and Restrict supported resources and SearchParameters.
Additionally, these search parameters can have an impact on the search performance:
_total
: Searchbundles contain the elementtotal
which indicates the total number of resources that match the query’s search parameters. Setting this parameter to_total = none
results in faster searches as it saves the query that would generate the result of the aforementioned element.
Firely Server is made up of a pipeline of plugins. You can leave out any plugin that you don’t need - so if you don’t need conditional processing (create, update, delete), just exclude them from the pipeline. Excluded plugins are not loaded and thus never executed - see Configuring the Firely Server Pipeline.
Platform
Firely Server can run on Windows, Linux and MacOS. Directly or in a Docker container. On real hardware, virtual machine, app service or Kubernetes cluster. And then you can choose the dimensions of the platform, scaling up (bigger ‘hard’ware) or scaling out (more (virtual) machines) as you see fit. Each of these choices will influence performance.
Besides the way Firely Server is deployed, the way the database is deployed is an important factor. Firely Server needs a very low latency connection between the Firely Server process(es) and the database server. If you have configured Application Insights, the calls to the database are recorded as separate dependencies so you can check whether this may be a bottleneck.
Firely Server is optimized for multithreaded processing. This means that it will fully benefit from extra processing cores, either in the same machine (multi core processor) or by adding additional machines (and thus processors).
Firely Server is fully stateless, so no data about connections is saved between requests. First of all this helps in scaling out, since you don’t need thread affinity. On top of that this reduces the memory requirements of Firely Server.
Usage patterns
How will Firely Server be used in your environment?
Mostly for querying, or rather for creating and updating resources? Altering resources requires more processing than reading them. Also see the comment on indexing and search parameters above.
How is the distribution of values in the resources that you query on? E.g. if you use only a few types of resources, query them just by tag and the resources have only about 5 different tags, calculating the number of results will take a lot of time. Using more finegrained distributed values to query on solves this.
With many individual resources or with (large) batches or transactions? Transactions take a lot longer to process and require more memory, proportionally to the number of resources in them. If many transactions are run in parallel, requests may queue up.
Many users with a low request rate each, or a few heavy users? Since Firely Server is stateless, this has little influence. The total request rate is what counts.
Testing performance yourself
Because of all the variables mentioned above the best way to find out whether Firely Server’s performance is sufficient for your use is: test it yourself.
We provide an evaluation license that you can use for any testing, including performance testing. See Getting Started.
Variables
Before you start testing, study the variables above and provide answers to them. Then you can configure your platform and your tests in a way that comes closest to the expected real use.
Requests
You need a set of requests that you want to test. Based on your use case, identify the 5 (or more) most frequent requests. For extra realism you should provide the parameters to the requests from a dataset (like a .csv file with search parameter values).
What to measure?
There are essentially two questions that you can investigate:
Given this deployment, (mix of) requests and an expected request rate, what are the response times?
Given this deployment and (a mix of) requests, how many requests can Firely Server handle before it starts returning time-outs?
Besides response times more insight can be gained by measuring the load on the server (processor / memory usage, disk and network latency, for both the Firely Server and the database server) as well as the machine you are generating the requests from (to ensure that is not bottlenecked).
Always make sure to use at least 2 separate machines for testing: one for Firely Server, and a separate one for generating the requests. Testing Firely Server on the same machine as you’re generating requests from will make Firely Server compete with the load testing tool for resources which’ll hamper the legitimacy of the test results.
Based on the answers you can retry with different parameters (e.g. add/remove hardware) to get a sense of the requirements for real use deployment.
Data
Performance testing is best done with data as realistic to your situation as possible. So if you happen to have historic data that you can transform to FHIR resources, that is the best data to test with.
But if you don’t have data of your own, you can use synthesized data. We use data from the Synthea project for our own tests. And we provide VonkLoader to upload the collection bundles from Synthea to Firely Server (or any FHIR Server for that matter).
If you build a Facade, the historical data is probably already in a test environment of the system you build the Facade on. That is a perfect start.
Test framework
To run performance tests you need a framework to send the requests in parallel and measure the response times. Test automation is a profession in itself so we cannot go into much detail here. You can search for ‘REST Performance test tools’ to get some options.
Available performance figures
We are in the process of setting up performance tests as part of our Continuous Integration and Deployment. Here we describe how this test is currently set up. Because of the beta phase this is in, the output is not yet complete nor fully reliable. Nevertheless we share the preliminary results to give you a first insight.
Firely Server performance test setup
Configuration
Repository: MongoDB, both for Administration and for the main database.
Prevalidation: off
Search parameters: support all types of resources and all search parameters from the FHIR specification.
Pipeline: load all available plugins except authorization.
Platform
Azure Kubernetes Service, 2 nodes.
Each node: Standard F2s (2 vcpus, 4 GB memory), running Linux
1 MongoDB pod and 2 Firely Server pods, plus the Kubernetes manager
Usage pattern - we created a simple mix of requests
Upload the first 100 Synthea bundles from the precalculated set, each collection bundle transformed to a Batch.
A ‘general’ test, consisting of:
Query Patient by name:
GET {url}/Patient?name=...
Query Patient by name and maximum age:
GET {url}/Patient?name={name}&birthdate=ge{year}
Query all Conditions:
GET {url}/Condition
Query a Patient by identifier, with Observations:
GET {url}/Patient?identifier={some identifier}&_revinclude=Observation:subject
Query a Patient by identifier, with Observations and DiagnosticReports:
GET {url}/Patient?identifier={some identifier}&_revinclude=Observation:subject&_revinclude=DiagnosticReport:patient
Page through all the CarePlan resources:
GET {url}/CarePlan?_count=10
, and follownext
links.Page through 1/5 of the Patient resources and delete them:
DELETE {url}/Patient/{id}
20 concurrent users, randomly waiting up to 1 second before issuing the next request.
Test run of 5 minutes
Test framework
Locust for defining and running tests
Telegraf agents for collection metrics
InfluxDB for storing results
Grafana for displaying results
Test results
Upload: not properly timed yet.
General test: 75 percentile of response times around 200 ms. Note that the responses on queries with ‘_revinclude’ contain over 30 resources on average, sometimes over 100.
Page through all CarePlan resources: 75 percentile of response times around 110 ms.
Delete patients: This test always runs with 40 concurrent users, and 75 percentile of response times are around 350ms. Note that in Firely Server a delete is essentially an update, since all old versions are retained.
SQL Server Guidelines
Performance tuning of SQL Server is a topic on its own. Firely Server manages the tables it needs, and the indexes on top of it are optimized for the way Firely Server queries them. In this section we offer general guidelines on how to configure and maintain your SQL database. This topic only covers the most common options and should by no means be regarded as exhaustive.
Memory Settings
The total amount of memory needed by SQL Server depends on your use case and (expected) workload. SQL Server will claim memory from the OS when needed but will not give it back to the OS once claimed. If no limit is specified you will run the risk of starving the host server and OS of enough free memory to operate efficiently. It is therefore strongly advised to limit the maximum amount SQL Server is allowed to use.
To set a fixed amount of memory using SSMS 1:
In Object Explorer, right-click a server and select Properties.
Click the Memory node.
Under Server Memory Options, enter the amount that you want for ‘Minimum server memory (in MB)’ and ‘Maximum server memory (in MB)’.

Use the default settings to allow SQL Server to change its memory requirements dynamically based on available system resources. It is recommended to set a max server memory as detailed above.
Windows Server with Desktop Experience requires a minimum memory size of 2 GB 2, but 4 GB or more is recommended (more is better). Example: If your server has 64GB of memory, set the amount of memory in SQL Server to 60,000MB (i.e. 64GB - 4GB = 60GB)
Disk Configuration
When optimizing SQL Server it is highly recommended to set the tempDB, the data disk and the log disk on seperate drives (preferably SSDs) 1.
TempDB
Place the tempDB database on a separate disk and adjust the number of secondary data files according to how many (logical) processors the host features 2. If the number of logical processors is less than or equal to eight, use the same number of data files as logical processors. If the number of logical processors is greater than eight, use eight data files. For TempDB it usually is sufficient to create 8 equally sized data files. As a general rule of thumb:
- Number of logical processors < 8
–> Set the number of data files equal to the number of logical processors
- Number of logical processors >= 8
–> Set the number of data files to 8
Data & Log files
Placing both data AND log files on the same device can cause contention for that device, resulting in poor performance. Placing the files on separate drives allows the I/O activity to occur at the same time for both the data and log files.
Also format your data disk to use 64-KB allocation unit size for all data files placed on a drive 3. Use SSDs with high IOPS where possible.
Backup Maintenance Plan
When Firely Server is used in conjunction with SQL Server, a maintenance plan should be in place. Furthermore, if the recovery log is not set to ‘Simple’, regular back-ups of the transaction logs should be made.
Microsoft advises that if a database uses either the full or bulk-logged recovery model, you must back up the transaction log regularly enough to protect your data, and to prevent the transaction log from filling. This truncates the log and supports restoring the database to a specific point in time.
For more information on how to back up the Transaction Log consult the official Microsoft documentation, which can be found here.
Index Maintenance
You can check the index fragmentation with the following script:
SELECT OBJECT_SCHEMA_NAME(i.object_id) AS schema_name,
OBJECT_NAME(i.object_id) AS object_name,
i.name AS index_name,
i.type_desc AS index_type,
100.0 * (ISNULL(SUM(rgs.deleted_rows), 0)) / NULLIF(SUM(rgs.total_rows), 0) AS avg_fragmentation_in_percent
FROM sys.indexes AS i
INNER JOIN sys.dm_db_column_store_row_group_physical_stats AS rgs
ON i.object_id = rgs.object_id
AND
i.index_id = rgs.index_id
WHERE rgs.state_desc = 'COMPRESSED'
GROUP BY i.object_id, i.index_id, i.name, i.type_desc
ORDER BY schema_name, object_name, index_name, index_type;
Some guidelines 1:
Do not assume that index maintenance will always noticeably improve your workload.
If you observe that rebuilding indexes improves performance, try replacing it with updating statistics. This may result in a similar improvement. In that case, you may not need to rebuild indexes as frequently, or at all, and instead can perform periodic statistics updates.
Monitor index fragmentation and page density over time to see if there is a correlation between these values trending up or down, and query performance. If higher fragmentation or lower page density degrade performance unacceptably, reorganize or rebuild indexes. It is often sufficient to only reorganize or rebuild specific indexes used by queries with degraded performance. This avoids a higher resource cost of maintaining every index in the database.
Establishing a correlation between fragmentation/page density and performance also lets you determine the frequency of index maintenance. Do not assume that maintenance must be performed on a fixed schedule. A better strategy is to monitor fragmentation and page density, and run index maintenance as needed before performance degrades to an unacceptable level.
If you have determined that index maintenance is needed and its resource cost is acceptable, perform maintenance during low resource usage times, if any, keeping in mind that resource usage patterns may change over time.
There is also a general rule of thumb with regards to fragmentation (YMMV):
< 10%: no action needed.
10-30%: reorganize the index
>30%: rebuild the index
SQL Server Statistics
The maintenance of statistics falls outside the scope of Firely Server.
Normally SQL Server will update and maintain the statistics automatically. But when the database becomes large this process might happen too infrequently. There is no magic bullet for updating statistics, so you are advised to experiment and test what works best for your setup.
Consider updating statistics for the following conditions 1:
Query execution times are slow.
Insert operations occur on ascending or descending key columns.
After maintenance operations.
To manually update statistics there are two options 2 3:
A stored procedure
sp_updatestats
: this runsUPDATE STATISTICS
against all user-defined and internal tables in the current database.A SQL statement against a single table (here we use the Entry table)
UPDATE STATISTICS [vonk].[entry]
;
Security
This section lists some general recommendations and steps to configure SMART on FHIR on Firely Server, to help secure access to the data in your server.
Access control and SMART
Contents
Concepts
This explanation of access control and SMART in Firely Server requires basic understanding of the architecture of Firely Server, so you know what is meant by middleware components and repository interfaces. It also presumes general knowledge about authentication and OAuth2.
Access control generally consists of the following parts, which will be addressed one by one:
Identification: Who are you? – usually a user name, login, or some identifier.
Authentication: Prove your identification – usually with a password, a certificate or some other (combination of) secret(s) owned by you.
Authorization: What are you allowed to read or change based on your identification?
Access Control Engine: Enforce the authorization in the context of a specific request.
Identification and Authentication
Firely Server does not authenticate users itself. It is meant to be used in an OAuth2 environment in which an OAuth2 provider is responsible for the identification and authentication of users. Typically, a user first enters a Web Application, e.g. a patient portal, or a mobile app. That application interactively redirects the user to the OAuth2 provider - which is the authorization server and may or may not handle authentication as well - to authenticate, and receives an OAuth2 token back. Then, the application can do an http request to Firely Server to send or receive resource(s), and provide the OAuth2 token in the http Authentication header, thereby acting on behalf of the user. Firely Server can then read the OAuth2 token and validate it with the OAuth2 authorization server. This functionality is not FHIR specific.
Access Control Engine
The Access Control Engine in Firely Server evaluates two types of authorization:
Type-Access: Is the user allowed to read or write resource(s) of a specific resourcetype?
Compartment: Is the data to be read or written within the current compartment (if any)?
As you may have noticed, Type-Access aligns with the concept of scopes, and Compartment aligns with the concept of launch context in SMART on FHIR.
The Firely Server SmartContextMiddleware
component extracts the claims defined by SMART on FHIR from the OAuth2 token, and puts it into two classes that are then available for Access Control Decisions in all the interaction handlers (e.g. for read, search, create etc.)
SMART on FHIR defines launch contexts for Patient, Encounter and Location, extendible with others if needed. If a request is done with a Patient launch context, and the user is allowed to see other resource types as well, these other resource types are restricted by the Patient CompartmentDefinition.
Custom Authentication
You may build a plugin with custom middleware to provide authentication in a form that suits your needs. One example could be that you want to integrate ASP.NET Core Identity into Firely Server. Then you don’t need the OAuth2 middleware, but instead can use the Identity framework to authenticate your users. See Custom authorization plugin for more details.
Configuration
You will need to add the Smart plugin to the Firely Server pipeline. See Firely Server Plugins for more information. In appsettings[.instance].json
, locate the pipeline
configuration in the PipelineOptions
section, or copy that section from appsettings.default.json
(see also Changing the settings):
"PipelineOptions": {
"PluginDirectory": "./plugins",
"Branches": [
{
"Path": "/",
"Include": [
"Vonk.Core",
"Vonk.Fhir.R3",
...
Add Vonk.Smart
to the list of included plugins. When you restart Firely Server, the Smart service will be added to the pipeline.
You can control the way Access Control based on SMART on FHIR behaves with the SmartAuthorizationOptions in the Firely Server settings:
"SmartAuthorizationOptions": {
"Enabled": true,
"Filters": [
{
"FilterType": "Patient", //Filter on a Patient compartment if a 'patient' launch scope is in the auth token
"FilterArgument": "identifier=#patient#" //... for the Patient that has an identifier matching the value of that 'patient' launch scope
},
{
"FilterType": "Encounter", //Filter on an Encounter compartment if an 'encounter' launch scope is in the auth token
"FilterArgument": "identifier=#encounter#" //... for the Encounter that has an identifier matching the value of that 'encounter' launch scope
},
{
"FilterType": "RelatedPerson", //Filter on a RelatedPerson compartment if a 'relatedperson' launch scope is in the auth token
"FilterArgument": "identifier=#relatedperson#" //... for the RelatedPerson that has an identifier matching the value of that 'relatedperson' launch scope
},
{
"FilterType": "Practitioner", //Filter on a Practitioner compartment if a 'practitioner' launch scope is in the auth token
"FilterArgument": "identifier=#practitioner#" //... for the Practitioner that has an identifier matching the value of that 'practitioner' launch scope
},
{
"FilterType": "Device", //Filter on a Device compartment if a 'device' launch scope is in the auth token
"FilterArgument": "identifier=#device#" //... for the Device that has an identifier matching the value of that 'device' launch scope
}
],
"Authority": "url-to-your-identity-provider",
"AdditionalEndpointBaseAddresses": ["different-base-url-that-is-also-used-by-your-identity-provider"], // optional, only needed for certain identity provider setups
"Audience": "name-of-your-fhir-server" //Default this is empty
//"ClaimsNamespace": "http://smarthealthit.org",
"RequireHttpsToProvider": false, //You want this set to true (the default) in a production environment!
"Protected": {
"InstanceLevelInteractions": "read, vread, update, delete, history, conditional_delete, conditional_update, $validate",
"TypeLevelInteractions": "create, search, history, conditional_create",
"WholeSystemInteractions": "batch, transaction, history, search"
}
}
Enabled: With this setting you can disable (‘false’) the authentication and authorization altogether. When it is enabled (‘true’), Firely Server will also evaluate the other settings. The default value is ‘false’. This implies that authorization is disabled as if no SmartAuthorizationOptions section is present in the settings.
Filters: Defines how different launch contexts are translated to search arguments. See Compartments for more background.
FilterType: Both a launch context and a CompartmentDefinition are defined by a resourcetype. Use FilterType to define for which launch context and related CompartmentDefinition this Filter is applicable.
FilterArgument: Translates the value of the launch context to a search argument. You can use any supported search parameter defined on FilterType. It should contain the name of the launch context enclosed in hashes (e.g. #patient#), which is substituted by the value of the claim.
Authority: The base url of your identity provider, such that
{{base_url}}/.well-known/openid-configuration
returns a valid configuration response (OpenID Connect Discovery documentation). At minimum, thejwks_uri
,token_endpoint
andauthorization_endpoint
keys are required in addition to the keys required by the speficiation. See Set up an Identity Provider for more background.AdditionalEndpointBaseAddresses: Optional configuration setting. Add additional base authority endpoints that your identity provider also uses for operations that are listed in the .well-known document.
Audience: Defines the name of this Firely Server instance as it is known to the Authorization server. Default is ‘firelyserver’.
ClaimsNamespace: Some authorization providers will prefix all their claims with a namespace, e.g.
http://my.company.org/auth/user/*.read
. Configure the namespace here to make Firely Server interpret it correctly. It will then strip off that prefix and interpret it as justuser/*.read
. By default no namespace is configured.RequireHttpsToProvider: Token exchange with an Authorization server should always happen over https. However, in a local testing scenario you may need to use http. Then you can set this to ‘false’. The default value is ‘true’.
Protected: This setting controls which of the interactions actually require authentication. In the example values provided here, $validate is not in the TypeLevelInteractions. This means that you can use POST [base-url]/Patient/$validate without authorization. Since you only read Conformance resources with this interaction, this might make sense.
Compartments
In FHIR a CompartmentDefinition defines a set of resources ‘around’ a focus resource. For each type of resource that is linked to the focus resource, it defines the reference search parameters that connect the two together. The type of the focus-resource is in CompartmentDefinition.code, and the relations are in CompartmentDefinition.resource. The values for param in it can be read as a reverse chain.
An example is the Patient CompartmentDefinition, where a Patient resource is the focus. One of the related resourcetypes is Observation. Its params are subject and performer, so it is in the compartment of a specific Patient if that Patient is either the subject or the performer of the Observation.
FHIR defines CompartmentDefinitions for Patient, Encounter, RelatedPerson, Practitioner and Device. Although Firely Server is functionally not limited to these five, the specification does not allow you to define your own. Firely Server will use a CompartmentDefinition if:
the CompartmentDefinition is known to Firely Server, see Conformance Resources for options to provide them.
the OAuth2 Token contains a claim with the same name as the CompartmentDefinition.code (but it must be lowercase).
So some of the launch contexts mentioned in SMART on FHIR map to CompartmentDefinitions. For example, the launch context ‘launch/patient’ and ‘launch/encounter’ map to the compartment ‘Patient’ and ‘Encounter’. Please note that launch contexts can be extended for any resource type, but not all resource types have a matching CompartmentDefinition, e.g. ‘Location’.
A CompartmentDefinition defines the relationships, but it becomes useful once you combine it with a way of specifying the actual focus resource. In SMART on FHIR, the launch context can do that, e.g. patient=123. As per the SMART Scopes and Launch Context, the value ‘123’ is the value of the Patient.id. Together with the Patient CompartmentDefinition this defines a – what we call – Compartment in Firely Server:
Patient with id ‘123’
And all resources that link to that patient according to the Patient CompartmentDefinition.
There may be cases where the logical id of the focus resource is not known to the authorization server. Let’s assume it does know one of the Identifiers of a Patient. The Filters in the Configuration allow you to configure Firely Server to use the identifier search parameter as a filter instead of _id. The value in the configuration example does exactly that:
"Filters": [
{
"FilterType": "Patient", //Filter on a Patient compartment if a 'patient' launch scope is in the auth token
"FilterArgument": "identifier=#patient#" //... for the Patient that has an identifier matching the value of that 'patient' launch scope
},
...
]
Please notice that it is possible that more than one Patient matches the filter. This is intended behaviour of Firely Server, and it is up to you to configure a search parameter that is guaranteed to have unique values for each Patient if you need that. You can always stick to the SMART on FHIR default of _id by specifying that as the filter:
"Filters": [
{
"FilterType": "Patient", //Filter on a Patient compartment if a 'patient' launch scope is in the auth token
"FilterArgument": "_id=#patient#" //... for the Patient that has an identifier matching the value of that 'patient' launch scope
},
...
]
But you can also take advantage of it and allow access only to the patients from a certain General Practitioner, of whom you happen to know the Identifier:
"Filters": [
{
"FilterType": "Patient", //Filter on a Patient compartment if a 'patient' launch scope is in the auth token
"FilterArgument": "general-practitioner.identifier=#patient#" //... for the Patient that has an identifier matching the value of that 'patient' launch scope
},
...
]
In this example the claim is still called ‘patient’, although it contains an Identifier of a General Practitioner. This is because the CompartmentDefinition is selected by matching its code to the name of the claim, regardless of the value the claim contains.
If multiple resources match the Compartment, that is no problem for Firely Server. You can simply configure the Filters according to the business rules in your organization.
Tokens
When a client application wants to access data in Firely Server on behalf of its user, it requests a token from the authorization server (configured as the Authority in the Configuration). The configuration of the authorization server determines which claims are available for a certain user, and also for the client application. The client app configuration determines which claims it needs. During the token request, the user is usually redirected to the authorization server, which might or might not be the authentication server as well, logs in and is then asked whether the client app is allowed to receive the requested claims. The client app cannot request any claims that are not available to that application. And it will never get any claims that are not available to the user. This flow is also explained in the SMART App Authorization Guide.
The result of this flow should be a JSON Web Token (JWT) containing zero or more of the claims defined in SMART on FHIR. The claims can either be scopes or a launch context, as in the examples listed in Authorization. This token is encoded as a string, and must be sent to Firely Server in the Authorization header of the request.
A valid access token for Firely Server at minimum will have:
the
iss
claim with the base url of the OAuth serverthe
aud
the same value you’ve entered inSmartAuthorizationOptions.Audience
the
scope
field with the scopes granted by this access tokenoptionally, the compartment claim, if you’d like to limit this token to a certain compartment. Such a claim may be requested by using a context scope or launching a part of an EHR launch. See Requesting context with scopes for more details. For example, in case of Patient data access where the
launch/patient
scope is used, include thepatient
claim with the patient’s id or identifier (Compartments) and make sure to request thepatient/<permissions>
scope permissions. Compartment claims combined withuser/<permissions>
claims are not taken into acccount.
Warning
Firely Server will not enforce any access control for resources outside of the specified compartment. Some compartment definitions do not include crucial resource types like ‘Patient’ or their corresponding resource type, i.e. all resources of this type regardless of any claims in the access token will be returned if requested. Please use this feature with caution! Additional custom access control is highly recommended.
Access Control Decisions
In this paragraph we will explain how Access Control Decisions are made for the various FHIR interactions. For the examples assume a Patient Compartment with identifier=123 as filter.
Search
Direct search on compartment type
- Request
GET [base]/Patient?name=fred
- Type-Access
User must have read access to Patient, otherwise a 401 is returned.
- Compartment
If a Patient Compartment is active, the Filter from it will be added to the search, e.g.
GET [base]/Patient?name=fred&identifier=123
Search on type related to compartment
- Request
GET [base]/Observation?code=x89
- Type-Access
User must have read access to Observation, otherwise a 401 is returned.
- Compartment
If a Patient Compartment is active, the links from Observation to Patient will be added to the search. In pseudo code:
GET [base]/Obervation?code=x89& (subject:Patient.identifier=123 OR performer:Patient.identifier=123)
Search on type not related to compartment
- Request
GET [base]/Organization
- Type-Access
User must have read access to Organization, otherwise a 401 is returned.
- Compartment
No compartment is applicable to Organization, so no further filters are applied.
Search with include outside the compartment
- Request
GET [base]/Patient?_include=Patient:organization
- Type-Access
User must have read access to Patient, otherwise a 401 is returned. If the user has read access to Organization, the _include is evaluated. Otherwise it is ignored.
- Compartment
Is applied as in case 1.a.
Search with chaining
- Request
GET [base]/Patient?general-practitioner.identifier=123
- Type-Access
User must have read access to Patient, otherwise a 401 is returned. If the user has read access to Practitioner, the search argument is evaluated. Otherwise it is ignored as if the argument was not supported. If the chain has more than one link, read access is evaluated for every link in the chain.
- Compartment
Is applied as in case 1.a.
Search with chaining into the compartment
- Request
GET [base]/Patient?link:Patient.identifier=456
- Type-Access
User must have read access to Patient, otherwise a 401 is returned.
- Compartment
Is applied to both Patient and link. In pseudo code:
GET [base]/Patient?link:(Patient.identifier=456&Patient.identifier=123)&identifier=123
In this case there will probably be no results.
Read: Is evaluated as a Search, but implicitly you only specify the _type and _id search parameters.
VRead: If a user can Read the current version of the resource, he is allowed to get the requested version as well.
Create
Create on the compartment type
- Request
POST [base]/Patient
- Type-Access
User must have write access to Patient. Otherwise a 401 is returned.
- Compartment
A Search is performed as if the new Patient were in the database, like in case 1.a. If it matches the compartment filter, the create is allowed. Otherwise a 401 is returned.
Create on a type related to compartment
- Request
POST [base]/Observation
- Type-Access
User must have write access to Observation. Otherwise a 401 is returned. User must also have read access to Patient, in order to evaluate the Compartment.
- Compartment
A Search is performed as if the new Observation were in the database, like in case 1.b. If it matches the compartment filter, the create is allowed. Otherwise a 401 is returned.
Create on a type not related to compartment
- Request
POST [base]/Organization
- Type-Access
User must have write access to Organization. Otherwise a 401 is returned.
- Compartment
Is not evaluated.
Update
Update on the compartment type
- Request
PUT [base]/Patient/123
- Type-Access
User must have write access and read access to Patient, otherwise a 401 is returned.
- Compartment
User should be allowed to Read Patient/123 and Create the Patient provided in the body. Then Update is allowed.
Update on a type related to compartment
- Request
PUT [base]/Observation/xyz
- Type-Access
User must have write access to Observation, and read access to both Observation and Patient (the latter to evaluate the compartment)
- Compartment
User should be allowed to Read Observation/123 and Create the Observation provided in the body. Then Update is allowed.
Delete: Allowed if the user can Read the current version of the resource, and has write access to the type of resource.
History: Allowed on the resources that the user is allowed to Read the current versions of (although it is theoretically possible that an older version would not match the compartment).
Testing
Testing the access control functionality is possible on a local instance of Firely Server. It is not available for the publicly hosted test server.
You can test it using a dummy authorization server and Postman as a REST client. Please refer to these pages for instructions:
You might also find it useful to enable more extensive authorization failure logging - Firely Server defaults to a secure setup and does not show what exactly went wrong during authorization. To do so, set the ASPNETCORE_ENVIRONMENT
environment variable to Development
.
Features
Firely Server offers many features as defined in the FHIR Specification and beyond.
FHIR RESTful API
Firely Server supports most of the features in the FHIR RESTful API.
FHIR Versions
All the operations below can be called for FHIR STU3 or FHIR R4. Firely Server supports the fhirVersion mimetype parameter and fhir version endpoint mappings for that purpose. See Multiple versions of FHIR for more information.
Create, read, update, patch, delete
These five operations to manage the contents of the Firely Server, commonly referenced by the acronym CRUD, are implemented as per the specification. Patch is implemented as FHIR Patch, as this is the most versatile one. This includes version-read and the conditional variations. Only a few limitations apply.
Firely Server enables create-on-update: If you request an update and no resource exists for the given id, the provided resource will be created under the provided id.
Firely Server can reject a resource based on Validating incoming resources.
Configuration
A conditional delete interaction may match multiple resources. You can configure the server to delete all matches, or reject the operation (effectively only allowing single matches to be deleted). Allowing multiple deletes requires support for transactions on the database (SQL Server or SQLite). If you allow for multiple deletes, you have to specify a maximum number of resources that can be deleted at once, to save you from accidentally deleting too many resources.
"FhirCapabilities": {
"ConditionalDeleteOptions": {
"ConditionalDeleteType": "Single", // Single or Multiple,
"ConditionalDeleteMaxItems": 1
}
}
Limitations on CRUD
Simultaneous conditional creates and updates are not entirely transactionally safe:
Two conditional updates may both result in a
create
, although the result of one may be a match to the other.Two conditional creates may both succeed, although the result of one may be a match to the other.
A conditional create and a simultaneous conditional update may both result in a
create
, although the result of one may be a match to the other.
Parameter
_pretty
is not yet supported.XML Patch and JSON Patch are not supported.
Versioning
Firely Server keeps a full version history of every resource, including the resources on the Firely Server Administration API.
Search
Search is supported as per the specification, with a few Limitations on search.
In the default configuration the SearchParameters from the FHIR specification are available. But Firely Server also allows Custom Search Parameters.
Chaining and reverse chaining is fully supported.
Quantity search on UCUM quantities automatically converts units to a canonical form. This means you can have kg in an Observation and search by lbs, or vice versa.
Compartment Search is supported.
Warning
Queries that request resource types not included in the current compartment’s CompartmentDefinition will yield default search results. Example: Searching for Practitioner resources within a Patient’s compartment will return all Practitioner resources, including the ones not linked to the patient.
Firely Server also supports _include:iterate
and _revinclude:iterate
, as well as its STU3 counterparts _include:recurse
and _revinclude:recurse
. See the specification for the definition of those. You can configure the maximum level of recursion:
"FhirCapabilities": {
"SearchOptions": {
"MaximumIncludeIterationDepth": 1
}
},
Sorting
_sort
is implemented for searchparameters of types:
string
number
uri
reference
datetime
token
for the repositories:
SQL
SQLite
Memory
How is sort evaluated?
A searchparameter may be indexed with multiple values for a single resource. E.g. Patient.name for Angelina Jolie would have name=Angelina and name=Jolie. And George Clooney: name=George and name=Clooney. As the FHIR Specification phrases it: “In this case, the sort is based on the item in the set of multiple parameters that comes earliest in the specified sort order when ordering the returned resources.” Here is an example of how Firely Server evaluates this.
In ascending order:
Patient?_sort=name
Name values
Asc. per resource
Asc. resources
Angelina
Angelina
Angelina Jolie
Jolie
Jolie
George
Clooney
George Clooney
Clooney
George
Now in descending order:
Patient?_sort=-name
Name values
Desc. per resource
Desc. resources
Angelina
Jolie
Angelina Jolie
Jolie
Angelina
George
George
George Clooney
Clooney
Clooney
The searchparameter to sort on may not be indexed at all for some of the resources in the resultset. E.g. a Patient without any identifier will not be indexed for Patient.identifier. Resources not having that parameter always end up last (both in ascending and descending order). This is similar to the ‘nulls last’ option in some SQL languages.
Token parameters are sorted only on their code element. The system element is ignored in the sorting.
Firely Server uses the default collation as configured on the database server. This collation defines the ordering of characters.
Limitations on search
The following parameters and options are not yet supported:
_text
_content
_query
_containedType
_filter
:approx
modifier on a quantity SearchParameter:text
modifier on a string SearchParameter:above
,:below
,:in
,:not-in
modifiers on a token SearchParameter:above
on a uri SearchParameter (:below
is supported)*
wildcard on_include
and_revinclude
_pretty
Furthermore:
Paging is supported, but it is not isolated from intermediate changes to resources.
History
History is supported as described in the specification, on the system, type and instance level.
The _since
and _count
parameters are also supported.
Configuration
"HistoryOptions": {
"MaxReturnedResults": 100
}
If a _history
call would result in more than MaxReturnedResults
, Firely Server asks the user to be more specific.
Use this to avoid overloading the server or the connection.
Limitations on history
_at
parameter is not yet supported.Paging is supported, but it is not isolated from intermediate changes to resources.
Batch
Batch is fully supported on the usual endpoint. You can limit the number of entries accepted in a single batch. See Batch and transaction.
Note that batches are not supported in the /administration
endpoint.
Transaction
Transactions are supported, with these limitations:
Of the three storage implementations, only SQL Server and SQLite truly support transactions. On MongoDB and Memory, transaction support can be simulated at the FHIR level, but not be enforced on the database level.
References between resources in the transaction can point backwards or forwards. Only circular references are not supported.
The
/administration
endpoint does not support transactions.
You can limit the number of entries accepted in a single transaction. See Batch and transaction.
Capabilities
On the Capabilities interaction (<firely-server-endpoint>/metadata
) Firely Server returns a CapabilityStatement that is built dynamically from the
supported ResourceTypes, SearchParameters and interactions. E.g. if you Configure Search Parameters, the SearchParameters that are actually loaded appear in the CapabilityStatement.
Not supported interactions
These interactions are not yet supported by Firely Server:
HEAD
Besides that, Firely Server does not yet return the date
header as specified in HTTP return values
Bulk Data Export
Firely Server provides the option to export resources with the Bulk Data Export Service. The Bulk Data Export Service enables the $export operation from the Fhir specification. Read more about the $export request flow
Appsettings
To start using the Bulk Data Export Service (BDE) you will first have to add the plugin (Vonk.Plugin.BulkDataExport) to the PipelineOptions in the appsettings.
"PipelineOptions": {
"PluginDirectory": "./plugins",
"Branches": [
{
"Path": "/",
"Include": [
"Vonk.Core",
"Vonk.Fhir.R3",
"Vonk.Fhir.R4",
//"Vonk.Fhir.R5",
"Vonk.Repository.Sql.SqlVonkConfiguration",
"Vonk.Repository.Sqlite.SqliteVonkConfiguration",
"Vonk.Repository.MongoDb.MongoDbVonkConfiguration",
"Vonk.Repository.CosmosDb.CosmosDbVonkConfiguration",
"Vonk.Repository.Memory.MemoryVonkConfiguration",
"Vonk.Subscriptions",
"Vonk.Smart",
"Vonk.UI.Demo",
"Vonk.Plugin.DocumentOperation.DocumentOperationConfiguration",
"Vonk.Plugin.ConvertOperation.ConvertOperationConfiguration",
"Vonk.Plugin.BinaryWrapper",
"Vonk.Plugin.Audit",
"Vonk.Plugins.TerminologyIntegration",
"Vonk.Plugin.BulkDataExport"
],
"Exclude": [
"Vonk.Subscriptions.Administration"
]
}, ...etc...
Note
We did not implement BDE for all database types. Make sure the admin database is configured for either SQL Server or SQLite and the data database is configured for SQL Server.
Bulk Data Export Service works as an asynchronous operation. To store the all operation-related information, it is necessary to enable a “Task Repository” on the admin database. Please configure either “Vonk.Repository.Sqlite.SqliteTaskConfiguration” or “Vonk.Repository.Sql.SqlTaskConfiguration” on the administration pipeline.
"PipelineOptions": {
"PluginDirectory": "./plugins",
"Branches": [
{
"Path": "/administration",
"Include": [
"Vonk.Core",
"Vonk.Fhir.R3",
"Vonk.Fhir.R4",
//"Vonk.Fhir.R5",
//"Vonk.Repository.Sql.SqlTaskConfiguration",
//"Vonk.Repository.Sql.SqlAdministrationConfiguration",
"Vonk.Repository.Sql.Raw.KAdminSearchConfiguration",
"Vonk.Repository.Sqlite.SqliteTaskConfiguration",
"Vonk.Repository.Sqlite.SqliteAdministrationConfiguration",
"Vonk.Repository.MongoDb.MongoDbAdminConfiguration",
"Vonk.Repository.Memory.MemoryAdministrationConfiguration",
"Vonk.Subscriptions.Administration",
"Vonk.Plugins.Terminology",
"Vonk.Administration",
"Vonk.Plugin.BinaryWrapper"
],
"Exclude": [
"Vonk.Core.Operations"
], ...etc...
BDE introduces two new parts to the appsettings, namely TaskFileManagement and BulkDataExport.
"TaskFileManagement": {
"StoragePath": "./taskfiles"
},
"BulkDataExport": {
"RepeatPeriod" : 60000, //ms
"AdditionalResources": [ "Organization", "Location", "Substance", "Device", "BodyStructure", "Medication", "Coverage" ]
},
In StoragePath you can configure the folder where the exported files will be saved to. Make sure the server has write access to this folder.
In RepeatPeriod you can configure the polling interval (in milliseconds) for checking the Task queue for a new export task.
A patient-based or group-based Bulk Data Export returns resources based on the Patient compartment definition (https://www.hl7.org/fhir/compartmentdefinition-patient.html). These resources may reference resources outside the compartment as well, such as a Practitioner who is the performer of a Procedure. Using the AdditionalResources-setting, you can determine which types of referenced resources are exported in addition to the compartment resources.
$export
There are three different levels for which the $export operation can be called:
System
url: [firely-server-base]/$export
This will create a system level export task, exporting all resources in the Firely Server database to a .ndjson file per resourcetype.
Patient
url: [firely-server-base]/Patient/$export
This will create a type level export task, exporting all resources included in the Patient Compartment in the Firely Server database to an .ndjson file per resourcetype.
Group
url: [firely-server-base]/Group/<group-id>/$export
This will create an instance level export task. For each Patient in the Group, the task will export all resources included in the Patient Compartment in the Firely Server database to an .ndjson file per resourcetype.
Note
For now we only support inclusion in a Group through Group.member.
Making an $export request will create a new task in the database with status “Queued”. The request should return an absolute $exportstatus URL in the Content-Location header and the OperationOutcome in the body.
$exportstatus
The $export request should return the $exportstatus url for your export task. This url can be used to request the current status of the task through a GET request, or to cancel the task through a DELETE request.
There are six possible status options:
Queued
Active
Complete
Failed
CancellationRequested
Cancelled
If a task is Queued or Active, GET $exportstatus will return the status in the X-Progress header
If a task is Complete, GET $exportstatus will return the results with a $exportfilerequest url per exported .ndjson file. This url can be used to retrieve the files per resourcetype. If there were any problems with parts of the export, an url for the generated OperationOutcome resources can be found in the error section of the result.
If a task is Failed, GET $exportstatus will return HTTP Statuscode 500 with an OperationOutcome.
If a task is on status CancellationRequested or Cancelled, GET $exportstatus will return HTTP Statuscode 410 (Gone).
$exportfilerequest
If a task has the Complete status, the GET $exportstatus request should return one or more $exportfilerequest urls. Performing a GET request on this $exportfilerequest url returns a body of FHIR resources in newline delimited json (ndjson).
Note
The Accept header for this request has to be:
application/fhir+ndjson
Patient $everything
Description
This plugin implements the Patient $everything operation, as described in https://www.hl7.org/fhir/operation-patient-everything.html. This operation returns all resources associated with a single patient. The resources returned are determined by the Patient compartment, defined in https://www.hl7.org/fhir/compartmentdefinition-patient.html. Currently, the functionality is only available if you use SQL server for your data.
GET <base-url>/Patient/<patient-id>/$everything
Accept: <any supported FHIR media type>
Optional parameters:
_since: Get only resources changed since this moment
_type: Limit the returned resource types to only the types in this list
Please note that other defined operation parameters have not been implemented (yet).
So if you would want to fetch only Patient 1 and their Observations, changed since the 1st of January, 2021 in FHIR JSON format, you would use:
GET <base-url>/Patient/1/$everything?_type=Patient,Observation&_since=2021-01-01
Accept: application/fhir+json
Configuration
Many resources in the Patient compartment reference resources outside the compartment. For example: An Observation might have a performer which is a Practitioner. As Practitioner itself is not in the Patient compartment, the resource would normally not be returned. But using a setting you can control which additional resource types are returned if they are referenced by any of the resources you requested.
"PatientEverythingOperation": {
"AdditionalResources": [ "Organization", "Location", "Substance", "Device", "Medication" ]
}
This is the default value for the setting. As you can see, Practitioner is not included by default out of privacy considerations but you can change that by overriding the setting.
Note: Device was added as an additional resource as it includes a reference to a patient but is not listed in the patient’s compartment yet. As soon as the specification is updated, it will be removed from the appsetttings and returned by default.
To include the plugin in your pipeline, add the following extra Include:
"PipelineOptions": {
"Branches": [
{
"Path": "/",
"Include": [
...
"Vonk.Plugin.PatientEverything"
]
},
...
]
}
License
The Patient $everything operation is licensed. To use it, you may need to renew your license.
$lastN Observations
Description
This plugin implements the Observation $lastn operation, as described in https://www.hl7.org/fhir/observation-operation-lastn.html. This operation returns the most recent or last n=number of observations for a patient within a given category (e.g. vital-signs, laboratory, etc.).
Currently, the functionality is only available if you use SQL Server for your data.
GET <base-url>/Observation/$lastn
Accept: <any supported FHIR media type>
Required parameters:
patient / subject: a reference to the Patient;
category: a category to get the most recent observations from.
Optional parameters:
max: maximum number of Observations to return from each group;
other search parameters defined for the Observation resource.
Attention
Current limitations are:
$lastn has been implemented only for SQL Server databases.
the patient or subject reference must be a direct reference (using the
id
of the Patient), see the examples. A chained argument likepatient.identifier=123
is not yet supported.
Appsettings
To start using the $lastn operation you will first have to add the plugin Vonk.Plugin.LastN
to the PipelineOptions in the appsettings. The plugin has a dependency on Vonk.Repository.Sql.Raw.KSearchConfiguration
, which, therefore, should also be included in the pipeline.
"PipelineOptions": {
"PluginDirectory": "./plugins",
"Branches": [
{
"Path": "/",
"Include": [
...
"Vonk.Repository.Sql.Raw.KSearchConfiguration",
"Vonk.Plugin.LastN",
],
"Exclude": [
...
]
}, ...etc...
Note
We did not yet implement $lastn for all database types. Make sure the data database is configured for SQL Server.
Examples
The examples below use a predefined set of resources. You can add those resources to your instance of the server to reproduce the examples. To do it, please execute the following transaction bundle: download
. The transaction bundle contains a patient with id = examplePatient and a list of Observations for that patient.
Fetch the last 3 results for all vitals for a patient
Request:
GET <base-url>/Observation/$lastn?max=3&patient=examplePatient&category=vital-signs
Accept: application/fhir+json
Response:
{
"resourceType": "Bundle",
"type": "searchset",
"timestamp": "2021-09-14T11:52:58.450+04:00",
"meta": {
"lastUpdated": "2021-09-14T11:52:58.450+04:00",
"versionId": "8f3c6c0a-37d7-4fde-b80b-57e72e655fe7"
},
"entry": [
{
"fullUrl": "<base-url>/Observation/bac874a3-8d89-bd0c-9157-f2fb153d6642",
"search": {
"mode": "match"
},
"resource": {
"resourceType": "Observation",
"id": "bac874a3-8d89-bd0c-9157-f2fb153d6642",
"status": "final",
"category": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/observation-category",
"code": "vital-signs",
"display": "vital-signs"
}
]
}
],
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "2708-6",
"display": "Oxygen saturation in Arterial blood"
},
{
"system": "http://loinc.org",
"code": "59408-5",
"display": "Oxygen saturation in Arterial blood by Pulse oximetry"
}
],
"text": "Oxygen saturation in Arterial blood"
},
"subject": {
"reference": "<base-url>/Patient/examplePatient"
},
"effectiveDateTime": "2020-03-03T01:58:48+04:00",
// ...
}
},
{/* Entry Observation with code [Body Weight(29463-7)] from 2020-03-03 */},
{/* Entry Observation with code [Body Weight(29463-7)] from 2019-04-22 */},
{/* Entry Observation with code [Body Weight(29463-7)] from 2016-04-18 */},
{/* Entry Observation with code [Body Mass Index(39156-5)] from 2019-04-22 */},
{/* Entry Observation with code [Body Mass Index(39156-5)] from 2016-04-18 */},
{/* Entry Observation with code [Body Mass Index(39156-5)] from 2013-04-15 */},
{/* Entry Observation with code [Pain severity - 0-10 verbal numeric rating [Score] - Reported(72514-3)] from 2019-04-22 */},
{/* Entry Observation with code [Pain severity - 0-10 verbal numeric rating [Score] - Reported(72514-3)] from 2016-04-18 */},
{/* Entry Observation with code [Pain severity - 0-10 verbal numeric rating [Score] - Reported(72514-3)] from 2013-04-15 */},
{/* Entry Observation with code [Body Height(8302-2)] from 2019-04-22 */},
{/* Entry Observation with code [Body Height(8302-2)] from 2016-04-18 */},
{/* Entry Observation with code [Body Height(8302-2)] from 2013-04-15 */},
{/* Entry Observation with code [Body temperature(8310-5)], [Oral temperature(8331-1)] from 2020-03-03 */},
{/* Entry Observation with code [Blood Pressure(85354-9)] from 2020-03-03 */},
{/* Entry Observation with code [Blood Pressure(85354-9)] from 2019-04-22 */},
{/* Entry Observation with code [Blood Pressure(85354-9)] from 2016-04-18 */},
{/* Entry Observation with code [Heart rate(8867-4)] from 2020-03-03 */},
{/* Entry Observation with code [Heart rate(8867-4)] from 2019-04-22 */},
{/* Entry Observation with code [Heart rate(8867-4)] from 2016-04-18 */},
{/* Entry Observation with code [Respiratory rate(9279-1)] from 2020-03-03 */},
{/* Entry Observation with code [Respiratory rate(9279-1)] from 2019-04-22 */},
{/* Entry Observation with code [Respiratory rate(9279-1)] from 2016-04-18 */}
],
"total": 23,
"link": [
{
"relation": "self",
"url": "<base-url>/Observation/$lastn?max=3&patient=examplePatient&category=vital-signs&_count=23&_skip=0"
}
],
"id": "6d6571c3-e6e0-461e-803f-c044c442191c"
}
Fetch the last laboratory results for a patient
Request
GET <base-url>/Observation/$lastn?patient=examplePatient&category=laboratory
Accept: application/fhir+json
Response
{
"resourceType": "Bundle",
"type": "searchset",
"timestamp": "2021-09-14T12:28:40.943+04:00",
"meta": {
"lastUpdated": "2021-09-14T12:28:40.943+04:00",
"versionId": "748c3f1e-1199-44b8-a3c7-f06f1b1b6b49"
},
"entry": [
{/* Entry Observation with code [Microalbumin Creatinine Ratio(14959-1)] from 2019-04-22 */},
{/* Entry Observation with code [Low Density Lipoprotein Cholesterol(18262-6)] from 2019-04-22 */},
{/* Entry Observation with code [Carbon Dioxide(20565-8)] from 2019-04-22 */},
{/* Entry Observation with code [Chloride(2069-3)] from 2019-04-22 */},
{/* Entry Observation with code [High Density Lipoprotein Cholesterol(2085-9)] from 2019-04-22 */},
{/* Entry Observation with code [Total Cholesterol(2093-3)] from 2019-04-22 */},
{/* Entry Observation with code [Erythrocyte distribution width [Entitic volume] by Automated count(21000-5)] from 2016-04-18 */},
{/* Entry Observation with code [Glucose(2339-0)] from 2019-04-22 */},
{/* Entry Observation with code [Triglycerides(2571-8)] from 2019-04-22 */},
{/* Entry Observation with code [Sodium(2947-0)] from 2019-04-22 */},
{/* Entry Observation with code [Platelet distribution width [Entitic volume] in Blood by Automated count(32207-3)] from 2016-04-18 */},
{/* Entry Observation with code [Platelet mean volume [Entitic volume] in Blood by Automated count(32623-1)] from 2016-04-18 */},
{/* Entry Observation with code [Estimated Glomerular Filtration Rate(33914-3)] from 2019-04-22 */},
{/* Entry Observation with code [Creatinine(38483-4)] from 2019-04-22 */},
{/* Entry Observation with code [Hematocrit [Volume Fraction] of Blood by Automated count(4544-3)] from 2016-04-18 */},
{/* Entry Observation with code [Hemoglobin A1c/Hemoglobin.total in Blood(4548-4)] from 2019-04-22 */},
{/* Entry Observation with code [Calcium(49765-1)] from 2019-04-22 */},
{/* Entry Observation with code [Potassium(6298-4)] from 2019-04-22 */},
{/* Entry Observation with code [Urea Nitrogen(6299-2)] from 2019-04-22 */},
{/* Entry Observation with code [Leukocytes [#/volume] in Blood by Automated count(6690-2)] from 2016-04-18 */},
{/* Entry Observation with code [Hemoglobin [Mass/volume] in Blood(718-7)] from 2016-04-18 */},
{/* Entry Observation with code [Platelets [#/volume] in Blood by Automated count(777-3)] from 2016-04-18 */},
{/* Entry Observation with code [MCH [Entitic mass] by Automated count(785-6)] from 2016-04-18 */},
{/* Entry Observation with code [MCHC [Mass/volume] by Automated count(786-4)] from 2016-04-18 */},
{/* Entry Observation with code [MCV [Entitic volume] by Automated count(787-2)] from 2016-04-18 */},
{/* Entry Observation with code [Erythrocytes [#/volume] in Blood by Automated count(789-8)] from 2016-04-18 */},
{/* Entry Observation with code [Rhinovirus RNA [Presence] in Respiratory specimen by NAA with probe detection(92130-4)] from 2020-03-03 */},
{/* Entry Observation with code [Respiratory syncytial virus RNA [Presence] in Respiratory specimen by NAA with probe detection(92131-2)] from 2020-03-03 */},
{/* Entry Observation with code [Human metapneumovirus RNA [Presence] in Respiratory specimen by NAA with probe detection(92134-6)] from 2020-03-03 */},
{/* Entry Observation with code [Parainfluenza virus 3 RNA [Presence] in Respiratory specimen by NAA with probe detection(92138-7)] from 2020-03-03 */},
{/* Entry Observation with code [Parainfluenza virus 2 RNA [Presence] in Respiratory specimen by NAA with probe detection(92139-5)] from 2020-03-03 */},
{/* Entry Observation with code [Parainfluenza virus 1 RNA [Presence] in Respiratory specimen by NAA with probe detection(92140-3)] from 2020-03-03 */},
{/* Entry Observation with code [Influenza virus B RNA [Presence] in Respiratory specimen by NAA with probe detection(92141-1)] from 2020-03-03 */},
{/* Entry Observation with code [Influenza virus A RNA [Presence] in Respiratory specimen by NAA with probe detection(92142-9)] from 2020-03-03 */},
{/* Entry Observation with code [Adenovirus A+B+C+D+E DNA [Presence] in Respiratory specimen by NAA with probe detection(94040-3)] from 2020-03-03 */},
{/* Entry Observation with code [SARS-CoV-2 RNA Pnl Resp NAA+probe(94531-1)] from 2020-03-03 */}
],
"total": 36,
"link": [
{
"relation": "self",
"url": "<base-url>/Observation/$lastn?patient=examplePatient&category=laboratory&_count=36&_skip=0"
}
],
"id": "b6521ba6-6235-4221-95cd-e0f25edd77dc"
}
Get the most recent Observations in category vital-signs conducted before January 1, 2015
Request
GET <base-url>/Observation/$lastn?patient=examplePatient&category=vital-signs&date=lt2015-01-01
Accept: application/fhir+json
Response
{
"resourceType": "Bundle",
"type": "searchset",
"timestamp": "2021-09-14T12:35:32.952+04:00",
"meta": {
"lastUpdated": "2021-09-14T12:35:32.952+04:00",
"versionId": "1b88af29-6f90-4a73-8d21-bf4594f45fec"
},
"entry": [
{/* Entry Observation with code [Body Weight(29463-7)] from 2013-04-15 */},
{/* Entry Observation with code [Body Mass Index(39156-5)] from 2013-04-15 */},
{/* Entry Observation with code [Pain severity - 0-10 verbal numeric rating [Score] - Reported(72514-3)] from 2013-04-15 */},
{/* Entry Observation with code [Body Height(8302-2)] from 2013-04-15 */},
{/* Entry Observation with code [Blood Pressure(85354-9)] from 2013-04-15 */},
{/* Entry Observation with code [Heart rate(8867-4)] from 2013-04-15 */},
{/* Entry Observation with code [Respiratory rate(9279-1)] from 2013-04-15 */}
],
"total": 7,
"link": [
{
"relation": "self",
"url": "<base-url>/Observation/$lastn?patient=examplePatient&category=vital-signs&date=lt2015-01-01&_count=7&_skip=0"
}
],
"id": "b4178262-9bd3-4d9e-b4de-1578cb5d92de"
}
Fetch the last 3 body weight and body height measurements for a patient
Request
GET <base-url>/Observation/$lastn?max=3&patient=examplePatient&category=vital-signs&code=29463-7,8302-2
Accept: application/fhir+json
Response
{
"resourceType": "Bundle",
"type": "searchset",
"timestamp": "2021-09-14T12:55:06.929+04:00",
"meta": {
"lastUpdated": "2021-09-14T12:55:06.929+04:00",
"versionId": "3dd3bcde-cbfb-4003-98d7-d7c2f3194c8a"
},
"entry": [
{/* Entry Observation with code [Body Weight(29463-7)] from 2020-03-03 */},
{/* Entry Observation with code [Body Weight(29463-7)] from 2019-04-22 */},
{/* Entry Observation with code [Body Weight(29463-7)] from 2016-04-18 */},
{/* Entry Observation with code [Body Height(8302-2)] from 2019-04-22 */},
{/* Entry Observation with code [Body Height(8302-2)] from 2016-04-18 */},
{/* Entry Observation with code [Body Height(8302-2)] from 2013-04-15 */}
],
"total": 6,
"link": [
{
"relation": "self",
"url": "<base-url>/Observation/$lastn?max=3&patient=examplePatient&category=vital-signs&code=29463-7,8302-2&_count=6&_skip=0"
}
],
"id": "49ee0b4b-00bd-40b7-8cb5-96a0e0892380"
}
License
The $lastn operation is part of the core Firely Server functionality. However, to use it, you may need to request an updated license from Firely. You can use your current license file if it contains http://fire.ly/vonk/plugins/lastn
.
Custom Operations
Validation
Firely Server can validate a resource against a profile as defined in the $validate operation.
You can call validate on three levels:
Besides that you can configure Firely Server to validate every incoming resource and even filter on specific profiles. See the section on Validating incoming resources. In all cases, the Precondition is that Firely Server must have access to all relevant StructureDefinitions.
Validation has some Limitations.
Note
The very first validation call will take a considerable amount of time, typically around 5 seconds. This is because Firely Server maintains a cache of validation information, and on the first call that cache is still empty. Subsequent calls are much faster.
Validate on the system level
POST <firely_server_endpoint>/$validate[?profile=<canonical-url-of-structuredefinition>]
There are two ways of calling $validate:
With a Resource or a Bundle of resources as body, and optionally, a profile parameter on the url.
With a Parameters resource as body, having
a parameter element with the Resource to validate in the resource parameter;
(optionally) the profile to validate against in the profile parameter
In both cases the request must have a Content-Type header matching the format of the body (application/fhir+json
or application/fhir+xml
).
If you do not specify a profile parameter, Firely Server will validate the Resource against any profiles mentioned in meta.profile
as well as the base profile from the FHIR Specification.
If you call $validate on the system level, Firely Server will make no assumptions about the ResourceType of the Resource to validate.
Validate on the ResourceType level
POST <firely_server_endpoint>/<resourcetype>/$validate[?profile=<canonical-url-of-structuredefinition>]
You can call $validate in the same two ways as with Validate on the system level.
If you call $validate on the ResourceType level, Firely Server will check whether the Resource to validate is of the same <resourcetype> as provided in the url.
Validate an instance from the database
GET <firely_server_endpoint>/<resourcetype>/<id>/$validate[?profile=<canonical-url-of-structuredefinition>]
This time you can only use the (optional) profile parameter on the url to specify a StructureDefinition to validate against.
Precondition
Firely Server must be aware of all the StructureDefinitions referenced directly via parameter or indirectly by a profile in meta.profile
. Refer to the Conformance Resources for more information.
Limitations
The mode parameter is not yet supported.
Implicit ValueSets (ones that use the .filter property) are not supported - create explicit ones instead (without the .filter property).
Snapshot generation
Firely Server is capable of generating a snapshot for a StructureDefinition. This operation is not defined in the FHIR Specification.
You can invoke this operation with
POST <firely-server-endpoint>/StructureDefinition/$snapshot
The body must contain the StructureDefinition that you want filled with a fresh snapshot. The StructureDefinition may contain an existing snapshot, it will be ignored.
The Content-Type header must match the format of the body (application/fhir+json or application/fhir+xml)
Firely Server will return the same StructureDefinition, but with the snapshot element (re-)generated.
Note
The very first call to $snapshot will take a considerable amount of time, typically around 5 seconds. This is because Firely Server maintains a cache of StructureDefinition information, and on the first call that cache is still empty. Subsequent calls are much faster.
Precondition
Firely Server must be aware of all the other StructureDefinitions that are referred to by the StructureDefinition in the body of the request. Refer to the Conformance Resources for more information.
$meta
Firely Server provides an implementation of the $meta operation as defined in the FHIR Specification.
By default the operation is only enabled on the level of a resource instance. It can also be enabled on the level of a resourcetype or system wide, but the cost of execution will then be high. On sufficient customer demand an optimized implementation is possible.
The FHIR Specification operations framework allows for the definition of custom operations and defines how to offer them in the FHIR RESTful API. Firely Server offers two custom operations out of the box.
Custom Resources
Custom Resources are not formally defined in the FHIR Specification. To Firely Server a Custom Resource is a resource with a definition that is a specialization of DomainResource, but that is not in the Core FHIR Specification. Firely Server can handle these, provided it knows about the StructureDefinition that defines it. This page explains how to register such a StructureDefinition and store custom resources.
Warning
Custom Resources are not interoperable. Do not use them for exchanging resources outside your own programming control boundaries.
What to use them for?
Firely Server can be used as a platform to build apps on. In these apps, structures arise outside of the FHIR Specification or even the Health domain. Still, it would be useful to also use Firely Server to store, search and version these structures. Note that this is only for internal use in the app.
Register the definition
Just like any resourcetype, the definition for a custom resource is formalized as a StructureDefinition. Firely Server will recognize it as the definition of a custom resourcetype if and only if:
base = DomainResource
derivation = specialization
kind = resource
abstract = false
This also means that a Logical Model as-is cannot be used as the definition of a custom resourcetype.
Examples of these can be found in the specification: each resourcetype is defined this way. The easiest way to get started is with the definition of Basic (in xml or json), and adjust that:
Choose a name for the type, let’s say ‘Foo’.
Choose a url for the type. In STU3 this has to start with http://hl7.org/fhir/StructureDefinition/ (constraint sdf-7), so http://hl7.org/fhir/StructureDefinition/Foo makes sense. Note that in R4 you are encouraged to use a url in a domain that you control and not within hl7.org.
Make sure the id, name and type elements align with the name ‘Foo’.
Adjust the description
Make sure all the elements in the differential start with ‘Foo.’
(Recommended) Store your definition in Simplifier.net for version management, comments and collaboration.
If you have created the StructureDefinition, register it in Firely Server using any of the methods mentioned in Conformance Resources. As an example we will issue an update interaction on the Administration API:
PUT <base-url>/administration/StructureDefinition/Foo
Content-Type=application/fhir+json; fhirVersion=3.0
By using an update we can choose the id and hence the location of this StructureDefinition. Firely Server does this by default for all the resourcetypes defined by the specification as well.
Use a resource
To test whether you can actually use the endpoint associated with your custom resourcetype, try a search on it: GET <base-url>/Foo
. This should return an empty bundle. If you get an OperationOutcome with the text “Request for not-supported ResourceType(s) Foo”, the registration of the definition is not correct.
Note
The CapabilityStatement will not list the custom definition. This is because the element CapabilityStatement.rest.resource.type has a Required binding to the ResourceType valueset. And obviously this valueset does not contain our ‘Foo’ resourcetype.
Now use your favorite editor to create a resource that conforms to the Foo StructureDefinition. And then create it on Firely Server: POST <base-url>/Foo
.
All the operations on specification-defined resourcetypes are also available for custom resources. You can also use them in a batch or transaction bundle. Custom Resources can also be validated. This also means that Validating incoming resources can be used in conjunction with Custom Resources.
Search parameters on a custom resource
In Firely Server you can define your own custom search parameters on any type of resource (see Custom Search Parameters). This includes Custom Resources. Just use the type name of the Custom Resource in the SearchParameter.base.
Terminology services
Firely Server provides support for Terminology operations and validation against terminologies. This is done through a local implementation based on the Administration database (‘Local Terminology Service’) and - if configured - extended with external FHIR Terminology Servers (‘Remote Terminology Services’). The configuration allows you to set preferred services for each CodeSystem and ValueSet. Firely Server will then transparently select and query either the Local or one of the Remote Terminology Services.
Of the operations listed below the following can be supported by the Local Terminology Service: $validate-code, $expand, $lookup, $find-matches. Note that it only supports simple ValueSets and CodeSystems like the ones part of the FHIR specification. It cannot support complex terminologies like LOINC or SNOMED-CT.
The terminology operations can be invoked for different FHIR versions as specified in Multiple versions of FHIR.
Terminology Integration
In earlier versions of Firely Server, local terminology services were separately configured from so-called Terminology Integration. These are now merged.
Operations
ValueSet $validate-code
- definition
http://www.hl7.org/implement/standards/fhir/valueset-operation-validate-code.html
- notes
Available on the type level
<firely-server-endpoint>/administration/ValueSet/$validate-code
and the instance level<firely-server-endpoint>/administration/ValueSet/<id>/$validate-code
.Only the parameters url, valueSet, valueSetVersion, code, system, display, coding, codeableConcept, abstract are supported.
The url and valueSetVersion input parameters are only taken into consideration if no valueSet resource was provided in the body. So the valueSet in the body takes priority.
Both
GET
andPOST
interactions are available.
ValueSet $expand
- definition
http://www.hl7.org/implement/standards/fhir/valueset-operation-expand.html
- notes
Available on the type level
<firely-server-endpoint>/administration/ValueSet/$expand
and the instance level<firely-server-endpoint>/administration/ValueSet/<id>/$expand
.Only the parameters url, valueSet, valueSetVersion and includeDesignations are supported.
The url and valueSetVersion input parameters are only taken into consideration if no valueSet resource was provided in the body. So the valueSet in the body takes priority.
Both
GET
andPOST
interactions are available.
CodeSystem $lookup
- definition
http://www.hl7.org/implement/standards/fhir/codesystem-operation-lookup.html
- notes
Available on the type level
<firely-server-endpoint>/administration/CodeSystem/$lookup
.Only the parameters code, system, version, coding and date are supported.
Code & system combination takes priority over the coding parameter.
Both
GET
andPOST
interactions are available.
CodeSystem $find-matches / $compose
- definition
http://www.hl7.org/implement/standards/fhir/codesystem-operation-find-matches.html
- notes
Available on the type level
<firely-server-endpoint>/administration/CodeSystem/$find-matches
and the instance level<firely-server-endpoint>/administration/CodeSystem/<id>/$find-matches
.Only the parameters system, exact, version, property.code and property.value are supported.
The url and valueSetVersion input parameters are only taken into consideration if no valueSet resource was provided in the body. So the valueSet in the body takes priority.
Both
GET
andPOST
interactions are available.$find-matches was named $compose in FHIR STU3. The operation is supported with both names.
CodeSystem $subsumes
- definition
http://www.hl7.org/implement/standards/fhir/codesystem-operation-subsumes.html
- notes
Available on the type level
<firely-server-endpoint>/administration/CodeSystem/$subsumes
.Only the parameters codeA, codeB, system, and version are supported.
The system input parameters is only taken into when called on the type level.
Both
GET
andPOST
interactions are available.
ConceptMap $closure
- definition
http://www.hl7.org/implement/standards/fhir/conceptmap-operation-closure.html
- notes
Available on the system level
<firely-server-endpoint>/administration/$closure
.This operation is passed on to a Remote Terminology Service supporting it. It supports any parameters that the Remote service supports.
Only
POST
interactions are available.
ConceptMap $translate
- definition
http://www.hl7.org/implement/standards/fhir/conceptmap-operation-translate.html
- notes
Available on the instance level
<firely-server-endpoint>/administration/ConceptMap/[id]/$translate
and the type level<firely-server-endpoint>/administration/ConceptMap/$translate
.Only the parameters url, conceptMap (on POST), conceptMapVersion, code, system, version, source, target, targetsystem and reverse are supported.
Both
GET
andPOST
interactions are available.
Configuration
Pipeline
Make sure to add the Vonk.Plugins.Terminology
plugin to the PipelineOptions in appsettings in order to make use of the TerminologyIntegration
plugin.
Additionally, to the “/administration” pipeline, Vonk.Plugins.Terminology
can be used on the regular FHIR pipeline “/”. Please note that in this case, CodeSystems and ValueSets are resolved from the Administration repository when executing a terminology operation and the correspoding resource is not provided as part of the request as a parameter.
"PipelineOptions": {
"PluginDirectory": "./plugins",
"Branches": [
{
"Path": "/",
"Include": [..]
},
{
"Path": "/administration",
"Include": [
"Vonk.Core",
"Vonk.Fhir.R3",
"Vonk.Fhir.R4",
"Vonk.Administration",
...
"Vonk.Plugins.Terminology"
],
"Exclude": [
"Vonk.Subscriptions.Administration"
]
}, ...etc...
To include or exclude individual operations in the pipeline, see the available plugins under Terminology.
Also make sure that the terminology operations are allowed at all in the SupportedInteractions
section:
"SupportedInteractions": {
"InstanceLevelInteractions": "$validate-code, $expand, $compose, $translate, $subsumes",
"TypeLevelInteractions": "$validate-code, $expand, $lookup, $compose, $translate, $subsumes",
"WholeSystemInteractions": "$closure"
},
Lastly, operation on the administration endpoint can be limited to specific IP addresses:
"Administration": {
"Security": {
"AllowedNetworks": [ "127.0.0.1", "::1" ], // i.e.: ["127.0.0.1", "::1" (ipv6 localhost), "10.1.50.0/24", "10.5.3.0/24", "31.161.91.98"]
"OperationsToBeSecured": [ "$validate-code", "$expand", "$compose", "$translate", "$subsumes", "$lookup", "$closure" ]
}
},
Options
You can enable the integration with one or more external terminology services by setting the required options in the appsettings file. There is a block for the Local Terminology Service and one for each Remote Terminology Service.
For each terminology service you can set the following options:
- Order
The order of the terminology service, or the priority. If multiple Terminology services could be used for a request, Firely Server will use the priority to select a service. Terminology services are arranged in a ascending order: so 1 will be selected over 2.
- PreferredSystem
If a request is directed at a specific code system, Firely Server will choose this terminology server over other available services. A system matches one of the preferred systems if the system starts with the preferred system. So
http://loinc.org
will match any CodeSystem or ValueSet with a canonical that starts with that url.- SupportedInteractions
The operations supported by the terminology service. Firely Server will only select this service if the operation is in this list. Valid values:
"ValueSetValidateCode" "CodeSystemValidateCode" "Expand" "FindMatches" / "Compose" "Lookup" "Translate" "Subsumes" "Closure"- SupportedInformationModels
The FHIR versions supported by the terminology service. Valid values:
"Fhir3.0" "Fhir4.0" "Fhir5.0"- Endpoint
The endpoint url where Firely Server can redirect the requests to.
- Username
If the terminology service uses Basic Authentication, you can set the required username here.
- Password
If the terminology service uses Basic Authentication, you can set the required password here.
- MediaType
Default Media-Type that should be used for serialization of the Parameters resources forwarded to the external terminology servie
Notes:
The Endpoint, Username and Password settings are not valid for the Local Terminology Server, just for the Remote services.
If a Remote Terminology Service has different endpoints for different FHIR versions, configure each endpoint separately.
The
SupportedInformationModels
cannot be broader than the correspondingFhir.Rx
plugins configured in the PipelineOptions.
A sample Terminology section in the appsettings can look like this:
"Terminology": {
"MaxExpansionSize": 650,
"LocalTerminologyService": {
"Order": 10,
"PreferredSystems": [ "http://hl7.org/fhir" ],
"SupportedInteractions": [ "ValueSetValidateCode", "Expand" ],
"SupportedInformationModels": [ "Fhir3.0", "Fhir4.0", "Fhir5.0" ]
},
"RemoteTerminologyServices": [
{
"Order": 20,
"PreferredSystems": [ "http://snomed.info/sct" ],
"SupportedInteractions": [ "ValueSetValidateCode", "Expand", "Translate", "Subsumes", "Closure" ],
"SupportedInformationModels": [ "Fhir4.0" ],
"Endpoint": "https://r4.ontoserver.csiro.au/fhir/",
"MediaType": "application/fhir+xml"
},
{
"Order": 30,
"PreferredSystems": [ "http://loinc.org" ],
"SupportedInteractions": [ "ValueSetValidateCode", "Expand", "Translate" ],
"SupportedInformationModels": [ "Fhir3.0", "Fhir4.0" ],
"Endpoint": "https://fhir.loinc.org/",
"Username": "",
"Password": ""
}
]
},
This means if you execute a terminology operation request, Firely Server will check whether the request is correct, redirect it to the preferred terminology service and finally return the result.
Additionally to the remote and local terminology services, you can configure the maximum number of concepts that are allowed to be included in a local ValueSet expansion (MaxExpansionSize). ValueSets stored in the local administration database larger than the configured setting will not be expanded, hence they cannot be used for $validate-code, $validate or $expand.
License
The Terminology plugin itself is licensed with the license token http://fire.ly/vonk/plugins/terminology
.
When you configure Remote Terminology Services it is your responsibility to check whether you are licensed to use those services.
Custom Search Parameters
Configure Search Parameters
You can control which search parameters are known to Firely Server. This is managed in the same way as all the conformance resources, see Conformance Resources.
Re-indexing for new or changed SearchParameters
Firely Server extracts values from resources based on the available search parameters upon create or update. This means that if you already had resources in your database before adding a custom search parameter, those resources will not be indexed for that parameter. If you on the other hand removed a previously used search parameter, the index will contain superfluous data.
To fix that, you should re-index (repeat the extraction) for these parameters.
In short, both reindex operations below will:
Return an Operation Outcome stating that the reindex procedure was started successfully.
Run the actual reindex asynchronously, using a configured number of threads, thereby using most of the hardware resources available to Firely Server.
Block any other requests for the duration of the reindex.
Log progress in the log.
Caution
This is a possibly lengthy operation, so use it with care.
Always try the reindex on a representative (sub)set of your data in a test environment to assess how long the operation may take in the production environment.
Always make a backup of your data before performing a reindex.
Warning
During the re-index operation, all other operations are blocked and responded to with response code ‘423 - Locked’.
Reindexing and FHIR versions
Reindexing is also controlled by the fhirVersion parameter (see Multiple versions of FHIR) in the Accept header or the version-mapped endpoint. It will then reindex only for SearchParameters and resources in that FHIR version. So for a full reindex of everything you may need to issue the command twice, once for each fhirVersion.
Rebuild the whole search index
This is only needed if we changed something very significant to the way Firely Server searches, like
The way values are extracted for all or many searchparameters.
The structure in which Firely Server stores the search index.
To re-index all resources for all search parameters, use:
POST http(s)://<firely-server-endpoint>/administration/reindex/all Accept=application/fhir+json (or xml); fhirVersion=3.0 (or 4.0)
This will delete any previously indexed data and extract it again from the resources.
Rebuild the search index for specific searchparameters
This is needed if:
The definition (usually the
expression
) of a searchparameter has changed.A searchparameter was added.
A searchparameter was removed and you want the search index to be tidy and not have this parameter in it anymore.
To re-index all resources for certain search parameters, use:
POST http(s)://<firely-server-endpoint>administration/reindex/searchparameters Accept=application/fhir+json (or xml); fhirVersion=3.0 (or 4.0)
In the body of the POST, you put the name of the search parameters to actually re-index as form parameters:
include=Patient.name,Observation.code exclude=Organization.name
include
means that resources will be re-indexed only for those search parameters.
You use this if you added or changed one or few search parameters.
exclude
means that any existing index data for those search parameters will be erased.
You use this when you removed a search parameter.
Remember to adjust the Content-Type header: application/x-www-form-urlencoded
.
If you are not permitted to perform the reindex, Firely Server will return statuscode 403.
Re-index Configuration
Firely Server will not re-index the resources in the database all at once, but in batches. The re-index operation will process all batches until all resources are re-indexed. You can control the size of the batches in the Firely Server settings. Besides that you can also control how many threads run in parallel to speed up the reindex process. The configured value is a maximum, since Firely Server will also be limited by the available computing resources.
"ReindexOptions": {
"BatchSize": 100,
"MaxDegreeOfParallelism": 10
},
Use any integer value >= 1.
Warning
CosmosDB in its default configuration (and on the CosmosDB emulator) is fairly limited in its throughput. If you encounter errors stating ‘Request rate is large’, you will have to:
lower the MaxDegreeOfParallelism,
restart Firely Server
and start a the reindex operation again.
Limitations
Every search parameter has to have either:
a valid FhirPath in it’s Expression property, or
be a Composite search parameter and specify at least one component.
Errata to the specification
The FHIR Specification is good, but not perfect. Some of the SearchParameters have errors. If we find these errors, we report them in the issue tracking system of HL7. But it takes time until the fix is applied to the specification. In the meantime Firely Server provides you with updated versions of the resources that have errors, so you can use them already while we await the fixes in the specification.
These corrections come with the Firely Server installation, in the files:
errataFhir3.0.zip
, with corrections to the STU3 version of the SpecificationerrataFhir4.0.zip
, with corrections to the R4 version of the Specification
These files are imported automatically during startup, as are other conformance resources, see Conformance Resources.
Currently the errata.zip file contains the following corrections:
- clinical-patient
This parameter incorrectly specified that both Patient and Group were target resource types for the patient search parameter.
For DeviceUseStatement-patient this was correct, so we created a separate file for this parameter, still listing the Group as a valid target type.- search parameters with FhirPath expression .as(DateTime)
Several search parameters had an incorrect FhirPath expression using .as(DateTime) instead of .as(dateTime). As a result, Firely Server could not index the fields correctly and searches on the dates would not work. The search parameters that were corrected are: clinical-date, DeviceRequest-event-date, Observation-code-value-date, Observation-value-date and patient-death-date.
- Resource.<xyz> expressions
The FhirPath library did not support polymorphism yet, so all the search parameters defined with an expression of Resource.<xyz> – for example Resource.meta.lastUpdated – did not work correctly. We have changed the expression to have just the <xyz> part – for example meta.lastUpdated.
- StructureDefinition.ext-context (R4 only)
The FhirPath expression ended on a BackboneElement that cannot be indexed. Changed to the expression
StructureDefinition.context.where(type='element').expression
.
Subscriptions
Subscriptions can be managed in the Firely Server Administration API, on the /administration/Subscription
endpoint. If you post a Subscription
to the regular FHIR endpoint, it will be stored but not evaluated. Subscriptions posted to the
/administration
endpoint will be processed and evaluated for each POST/PUT to the server.
Firely Server currently only supports STU3/R4-style Subscriptions with a Channel of type rest-hook.
If you are not permitted to access the /Subscription endpoint, Firely Server will return statuscode 403.
See Subscriptions in the specification for more background on Subscriptions.
FHIR versions
You POST a Subscription with a fhirVersion parameter (see Multiple versions of FHIR) or to a version specific endpoint. It will then respond to changes on resources in that FHIR version. So if you need a Subscription on both STU3 and R4 resources, POST that Subscription for both FHIR versions.
Configuration
Firely Server evaluates the active Subscriptions periodically, and in batches (x at a time, until all the active Subscriptions have been evaluated). You can control the period and the batchsize. If an evaluation of a Subscription fails, Firely Server will retry the evaluation periodically for a maximum amount of tries. You can control the retry period and the maximum number of retries.
"SubscriptionEvaluatorOptions": {
"Enabled" : true
"RepeatPeriod": 20000,
"SubscriptionBatchSize" : 1,
"RetryPeriod": 60000,
"MaximumRetries": 3,
"SendRestHookAsCreate": false
},
Enabled
allows you to quickly enable or disable the evaluation of Subscriptions. Default value is ‘false’, which implies that Subscription evaluation is also off if this section is left out of the settings.RepeatPeriod
is expressed in milliseconds. In the example above the period is set to 20 seconds, meaning that after a change a subscriber will be notified in at most 20 seconds.SubscriptionBatchSize
is expressed in number of Subscriptions that is retrieved and evaluated at once. Default is 1, but you can set it higher if you have a lot of Subscriptions.RetryPeriod
is expressed in milliseconds. In the example above the period is set to 60 seconds, meaning that Firely Server will retry to send the resources after a minimum of 60 seconds. Retry is included in the normal evaluation process, so the RetryPeriod cannot be smaller than RepeatPeriod.MaximumRetries
is the maximum amount of times Firely Server will retry to send the resources.SendRestHookAsCreate
: in versions < 3.9.3, Vonk sent RestHook notifications as a create operation using a POST. That was not compliant with the specification that requires an update operation using a PUT. The default value offalse
provides compliant behaviour. Only set it totrue
if you need Firely Servder to keep sending create operations as it did previously.
Auditing
Firely Server can log access through the RESTful API for auditing purposes. It has 3 features:
Write requests and responses to a separate audit logfile.
Include user id and name from the JWT token (if present) in the audit log lines.
Write the audit information to AuditEvent resources in the Firely Server Data database.
All features can be enabled by including Vonk.Plugins.Audit
in the pipeline. See Configure the pipeline for details on how to do that.
You can enable specific features by narrowing the namespace that you include in the pipeline, see the available plugins listed under Auditing.
Audit log file configuration
Configure where to put the audit log file and the format of its lines in the appsettings (see Firely Server settings):
"Audit": {
"PathFormat": "./audit/AuditLog-{Date}.log"
"OutputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Application}] [Audit] [Machine: {MachineName}] [ReqId: {RequestId}] [IP-Address: {Ip}] [Connection: {ConnectionId}] [UserId: {UserId}] [UserName: {UserName}] [Path: {Path}] [Action: {Action}] [Resource: {Resource} Key:{ResourceKey}] [StatusCode: {StatusCode}] {NewLine}"
},
The OutputTemplate listed here contains all the properties that can be logged:
RequestId: unique id of this request, use this to correlate request and response
Ip: IP Address of the client
ConnectionId: use this to correlate requests from the same client
UserId: user id from the JWT token (if present)
UserName: user name from the JWT token (if present)
Path: request url
Action: interaction that was requested (like instance_read or type_search)
Resource: resourcetype involved
ResourceKey: ‘key’ of the resource involved (if any), consisting of the resourcetype and the id, formatted as “resourcetype/id”
StatusCode: statuscode of the response at the time of logging (by default ‘-1’ when the request is not handled yet)
For transactions and batches, the audit plugin will write a line for the transaction/batch as a whole and one for every entry in the transaction/batch.
AuditEvent logging
There is no further configuration for AuditEvent logging. If you include it in the pipeline, it will start generating AuditEvent resources.
For transactions and batches the audit plugin will create an AuditEvent for the transaction/batch as a whole and one for every entry in the transaction/batch.
Firely Server does not allow you to update or delete the AuditEvent resources through the RESTful API so the Audit log cannot be tampered with. You can of course still manipulate these resources directly on the database, for instance to offload a surplus of old AuditEvent resources elsewhere. Please Contact us us for details if you want to do this.
Reset the database
If you have set up Firely Server as a reference server in a testing environment, it can be useful to reset the database. You would usually do this in combination with Preloading resources.
To reset the database, execute:
POST http(s)://<firely-server-endpoint>/administration/reset
Firely Server will return statuscode 200 if the operation succeeded.
If you are not permitted to perform the reset, Firely Server will return statuscode 403.
Note
On a large database this operation may take a while.
An alternative, if you have direct access to the database server, is to delete the database altogether and have Firely Server recreate it again.
If you run on SQL Server, see Using SQL server for the
AutoUpdateDatabase
feature.If you run on MongoDB, Firely Server will recreate the collection by default if it is not present.
Although the operation requires no further arguments, it requires a POST, since it is certainly not side-effect free.
Preloading resources
Caution
Preload in Firely Server (Vonk) 3.0.0 is only available for STU3.
If you have set up Firely Server as a reference server in a testing environment, it can be useful to load it with an ‘iron test set’ of examples. You can do that with the preload feature. Usually you will want to Reset the database first.
To preload a set of resources, execute:
POST http(s)://<firely-server-endpoint>/administration/preload
Content-Type: application/octet-stream
Body: a zip file with resources, each resource in a separate file (xml or json).
Firely Server will return statuscode 200 if the operation succeeded.
If you are not permitted to preload resources into the database, Firely Server will return statuscode 403.
Note
The operation can take quite long if the zip contains many resources.
E.g. when uploading the examples-json.zip from the specification, it took about a minute on MongoDb and about 7 minutes on SQL Server on a simple test server.
Attention
This feature is not meant for bulk uploading really large sets of resources. Firely Server currently has no special operation for bulk operations.
Multiple versions of FHIR
Since version 3.0.0 Firely Server can run multiple versions of FHIR side-by-side in the same server. This page explains how this works and what the consequences are.
Requests
The FHIR Specification explains the mimetype parameter that distinguishes one FHIR version from another in the paragraph on the FHIR Version parameter. Firely Server uses this approach to let you choose the model for your request. Below are examples on how to use the fhirVersion parameter and how in influences the behaviour of Firely Server. Accepted values for the parameter are:
fhirVersion=3.0, for FHIR STU3
fhirVersion=4.0, for FHIR R4
You can add the fhirVersion to the Accept and/or the Content-Type header. If you specify it on both, the fhirVersion parameters have to be the same.
The examples below explain the behaviour with STU3, but if you replace fhirVersion with 4.0, it works exactly the same on R4.
Note
If you do not specify a fhirVersion parameter, Firely Server will use fhirVersion=3.0 (STU3) as a default. This way the behaviour is compatible with previous versions of Firely Server. If you like, you can change the Default
in Information model
Note
If you use both an Accept header and a Content-Type header, the fhirVersion parameter for both must be the same. So this would be invalid:
POST <base>/Patient
Accept=application/fhir+json; fhirVersion=3.0
Content-Type=application/fhir+json; fhirVersion=4.0
Search for all Patients in STU3. In Firely Server this means Patient resources that were also stored as STU3. There is no automatic conversion of resources that were stored as R4 to the STU3 format (or vice versa).
GET <base>/Patient
Accept=application/fhir+json; fhirVersion=3.0
Search for Patients with the name ‘Fred’ in STU3. The search parameters used in the query must be valid in STU3.
GET <base>/Patient?name=Fred
Accept=application/fhir+json; fhirVersion=3.0
Create a Patient resource in STU3. This will only be retrievable when accessed with STU3:
POST <base>/Patient
Content-Type=application/fhir+json; fhirVersion=3.0
Accept=application/fhir+json; fhirVersion=3.0
{<valid Patient JSON body>}
Update a Patient resource in STU3.:
PUT <base>/Patient/123
Content-Type=application/fhir+json; fhirVersion=3.0
Accept=application/fhir+json; fhirVersion=3.0
{<valid Patient JSON body with id: 123>}
If no resource with this id existed before: it will be created with this id. (This was already always the behaviour of Firely Server.)
If a resource with this id existed before, in STU3: update it.
If a resource with this id already exists in R4: you will get an error with an OperationOutcome saying that a resource with this id already exists with a different informationmodel.
Note
Id’s still have to be unique within a resourcetype, regardless of the FHIR version.
Delete a Patient resource.:
DELETE <base>/Patient/123
Accept=application/fhir+json; fhirVersion=3.0
This will delete Patient/123, regardless of its FHIR version. The Accept header is needed for Firely Server to know how to format an OperationOutcome if there is an error.
Conformance resources
Conformance resources like StructureDefinition and SearchParameter are registered per FHIR version. This implies:
Conformance resources will be imported during Import of Conformance Resources for both STU3 and R4. To avoid id clashes (see note above), the id’s in R4 are appended with ‘-Fhir4.0’
So the StructureDefinition for Patient will be available for STU3 and R4 respectively like this:
GET <base>/StructureDefinition/Patient Accept=application/fhir+json; fhirVersion=3.0 GET <base>/StructureDefinition/Patient-Fhir4.0 Accept=application/fhir+json; fhirVersion=4.0
If you add a StructureDefinition or SearchParameter via the Administration API, you can decide for yourself whether to append the FHIR version to the id or not. Just note that you cannot use the same id for different FHIR versions.
Depending on the fhirVersion parameter Firely Server evaluates whether a resourcetype or searchparameter is valid in that FHIR version. E.g. ‘VerificationResult’ is only valid in R4, but ‘DataElement’ is only valid in R3.
For validation, the StructureDefinitions and terminology resources needed are only searched for in the FHIR version of the resource that is being validated.
When you Manage Conformance Resources with the Administration API, a StructureDefinition can only be posted to the Administration API in the context of a FHIR Version that matches the StructureDefinition.fhirVersion. So this works:
POST <base>/administration/StructureDefinition Accept=application/fhir+json; fhirVersion=4.0 Content-Type=application/fhir+json; fhirVersion=4.0 { "resourcetype": "StructureDefinition" ... "fhirVersion": "4.0.0" //Note the FHIR version matching the Content-Type }
But it would not work if
"fhirVersion"="3.0.1"
If you Load Conformance Resources on demand, this will be done for all the importfiles described above, regardless of the fhirVersion in the Accept header.
Running a single version
To use only a single version you set the Default
information model in Information model to the version you want to use. In addition, you can exclude the namespace of the version you don’t need (Vonk.Fhir.R3
or Vonk.Fhir.R4
) from the PipelineOptions to disable its use. If you exclude a namespace, make sure to exclude it from all branches.
Running different versions on different endpoints
To assign endpoints to different versions, create a mapping in Information model. Use the Mode
switch to select either a path or a subdomain mapping, assigning your endpoints in the Map
array. Mapped endpoints will only accept the version you have specified. The web service root (‘/’ and ‘/administration/’) will still accept all supported versions.
Assigning an endpoint to a FHIR version is exactly equivalent to adding that particular fhirVersion
MIME parameter to every single request sent to that endpoint. So using these settings:
"InformationModel": {
"Default": "Fhir4.0",
"Mapping": {
"Mode": "Path",
"Map": {
"/R3": "Fhir3.0",
"/R4": "Fhir4.0"
}
}
}
The call
GET http://myserver.org/Patient
Accept=application/fhir+json; fhirVersion=3.0
is equivalent to
GET http://myserver.org/R3/Patient
and the call
GET http://myserver.org/Patient (defaults to R4)
is equivalent to
GET http://myserver.org/R4/Patient
and the administration call
GET http://myserver.org/administration/StructureDefinition (defaults to R4)
is equivalent to
GET http://myserver.org/administration/R4/StructureDefinition (/R4 is a postfix to '/administration')
As you can see, on a mapped endpoint it is never necessary to use a FHIR _format
parameter or a fhirVersion
MIME parameter in a Content-Type
or Accept
header.
Support for R5 (experimental!)
By default the binaries for supporting R5 are included in the Firely Server distribution (since Firely Server (Vonk) 3.3.0). But also by default these binaries are not loaded. See the PipelineOptions in appsettings.default, where Vonk.Fhir.R5
is commented out.
Re-enable these in your appsettings.instance and you are good to go.
Note that there is not yet an errata_Fhir5.0.zip
and Firely Server will complain about that in the log. You can ignore that message.
HIPAA compliance
Firely Server is a well-tested, secure HL7 FHIR® server that enables you to comply with the Technical Safeguards of the HIPAA Security Rule.
On this page we will detail how you can achieve compliance for your Firely Server deployment. To ensure your organisation’s specific usecase, environment, and deployment are compliant, feel free to contact us: we’d be happy to help.
164.312(a)(1) Standard: Access control
Implement technical policies and procedures for electronic information systems that maintain electronic protected health information to allow access only to those persons or software programs that have been granted access rights as specified in 164.308(a)(4).
There are several ways to approach this:
ensure Firely Server is deployed in a secure environment where only those with correct permissions are able to access it,
use SMART on FHIR as a means of controlling access,
or add custom authentication based on a plugin.
Deploying in a secure environment (1) would mean access to Firely Server is controlled by third-party software or policy, placing this scenario outside the scope of this guide.
For scenario (2), Firely Server implements support for Smart on FHIR, a sibling specification to FHIR for securely connecting third-party applications to Electronic Health Record data. See Access control and SMART on how to configure Firely Server with it.
You may also wish to setup custom authentication (3). Given how Firely Server is based on a pipeline architecture, you can insert a plugin at the start of the pipeline to call out to your authentication service(s) prior to handling the request. See this gist as an example.
164.312(c)(1) Standard: Integrity
Implement policies and procedures to protect electronic protected health information from improper alteration or destruction.
The same solutions apply to this point as 164.312(a)(1) Standard: Access control and 164.312(b) Standard: Audit control.
164.312(d) Standard: Person or entity authentication
Implement procedures to verify that a person or entity seeking access to electronic protected health information is the one claimed.
The same solutions apply to this point as 164.312(a)(1) Standard: Access control.
164.312(a)(2)(i) Unique user identification
Assign a unique name and/or number for identifying and tracking user identity.
The same solution applies to this point as 164.312(b) Standard: Audit control. For Firely Server to be able to log the identity of the user, this identity must be present in or derivable from the authentication token, and it must be added to the log properties. If you use the SMART on FHIR plugin, that is automatically configured. If you want to do this from within a custom authentication plugin, feel free to contact us for details.
164.312(b) Standard: Audit control
Implement hardware, software, and/or procedural mechanisms that record and examine activity in information systems that contain or use electronic protected health information.
With the use of the Audit Event log plugin, Firely Server will thoroughly log every interaction as a note in a log file and/or in an AuditEvent resource. Logged information will be a trace record of all system activity: viewing, modification, deletion and creation of all Eletronic Protected Health Information (ePHI).
The audit trail can track the source IP, event type, date/time, and more. If a JWT token is provided (for SMART on FHIR), the user/patient identity can be logged as well.
164.312(e)(1, 2) Standard: Transmission security
Implement technical security measures to guard against unauthorized access to electronic protected health information that is being transmitted over an electronic communications network.
Implement a mechanism to encrypt electronic protected health information whenever deemed appropriate.
Transmission security in Firely Server can be achieved by encrypting the communications with TLS/SSL. Standard industry practice is to use a reverse proxy (e.g. nginx or IIS) for this purpose. If you’d like, you can also enable secure connections in Firely Server directly without a proxy as well.
Firely Server is regularly updated with the latest versions of ASP.NET to ensure that the latest cryptographic algorithms are available for use.
164.312(e)(2)(ii) Encryption
Implement a mechanism to encrypt electronic protected health information whenever deemed appropriate.
The recommended way to ensure that e-PHI is encrypted as necessary is to use disk encryption, and there are several solutions for this depending on your deployment environment. If you’re deploying in the cloud - see your vendors options for disk encryption, as most have options for encrypted disks already. If you’re deploying locally, look into BitLocker on Windows or dm-crypt/LUKS for Linux.
Disk encryption is preferred over individual database field encryption as the latter would severely impact the search performance.
164.312(a)(2)(ii) Emergency access procedure
Establish (and implement as needed) procedures for obtaining necessary electronic protected health information during an emergency.
This depends on the solution you went with for 164.312(a)(1) Standard: Access control.
In case you went with SMART on FHIR, add an authorization workflow that grants emergency access rights - essentially, a “super” access token. The application can then use this token with Firely Server, just like any other token.
If you went with a custom authentication scheme, add a special measure to handle this scenario.
164.312(a)(c) Implementation specification: Mechanism to authenticate electronic protected health information
Implement electronic mechanisms to corroborate that electronic protected health information has not been altered or destroyed in an unauthorized manner.
Firely Server does not allow you to delete resources through its RESTful API. Old versions of resources are retained by default. The only way to alter or destroy resources is through direct database access.
Therefore database-level safety mechanisms must ensure that information is not altered or destroyed unless it’s desired.
Firely Server Administration API
Besides the regular FHIR endpoint, Firely Server also exposes an Administration API. The endpoint for this is:
http(s)://<firely-server-endpoint>/administration
Functions
The following functions are available in the Administration API.
Configuration
You can Configure the Administration API, including restricting access to functions of the Administration API to specific ip addresses.
Database
The Administration API uses a database separately from the main ‘Firely Server Data’ database. Historically, SQL Server, MongoDB and Memory are supported as databases for the Administration API.
As of Firely Server (Vonk) version 0.7.1, SQLite is advised for this, and we have made that the default configuration. See Using SQLite on how to configure for this.
Firely Server Plugins
Firely Server Plugins is the means to adjust a Firely Server to your own special needs, beyond the configuration. Please have a look at the Overview of Firely Server, Plugins and Facades to see how plugins fit in the Firely Server. Architecture provides more detail on that.
Configure the pipeline
Configuration of the pipeline in Firely Server is done with PipelineOptions
in combination with SupportedInteractions
. A default setup is installed with Firely Server in appsettings.default.json, and it looks like this:
"PipelineOptions": {
"PluginDirectory": "./plugins",
"Branches": [
{
"Path": "/",
"Include": [
"Vonk.Core",
"Vonk.Fhir.R3",
"Vonk.Fhir.R4",
"Vonk.Repository.Sql.SqlVonkConfiguration",
"Vonk.Repository.Sqlite.SqliteVonkConfiguration",
"Vonk.Repository.MongoDb.MongoDbVonkConfiguration",
"Vonk.Repository.CosmosDb.CosmosDbVonkConfiguration",
"Vonk.Repository.Memory.MemoryVonkConfiguration",
"Vonk.Subscriptions",
"Vonk.Smart",
"Vonk.UI.Demo",
"Vonk.Plugin.DocumentOperation.DocumentOperationConfiguration",
"Vonk.Plugin.ConvertOperation.ConvertOperationConfiguration",
"Vonk.Plugin.BinaryWrapper.BinaryWrapperConfiguration",
"Vonk.Plugin.MappingToStructureMap.MappingToStructureMapConfiguration",
"Vonk.Plugin.TransformOperation.TransformOperationConfiguration"
],
"Exclude": [
"Vonk.Subscriptions.Administration"
]
},
{
"Path": "/administration",
"Include": [
"Vonk.Core",
"Vonk.Fhir.R3",
"Vonk.Fhir.R4",
"Vonk.Repository.Sql.SqlAdministrationConfiguration",
"Vonk.Repository.Sqlite.SqliteAdministrationConfiguration",
"Vonk.Repository.MongoDb.MongoDbAdminConfiguration",
"Vonk.Repository.Memory.MemoryAdministrationConfiguration",
"Vonk.Subscriptions.Administration",
"Vonk.Plugins.Terminology",
"Vonk.Administration"
],
"Exclude": [
"Vonk.Core.Operations",
"Vonk.Core.Licensing.LicenseRequestJobConfiguration"
]
}
]
},
"SupportedInteractions": {
"InstanceLevelInteractions": "read, vread, update, delete, history, conditional_delete, conditional_update, $validate, $validate-code, $expand, $compose, $meta, $meta-add, $document",
"TypeLevelInteractions": "create, search, history, conditional_create, compartment_type_search, $validate, $snapshot, $validate-code, $expand, $lookup, $compose, $document",
"WholeSystemInteractions": "capabilities, batch, transaction, history, search, compartment_system_search, $validate, $convert"
},
- PluginDirectory:
You can put plugins of your own (or third party) into this directory for Firely Server to pick them up, without polluting the Firely Server binaries directory itself. For a list of available plugins in Firely Server, see Plugins available for Firely Server. The directory in the default setting of
./plugins
is not created upon install, you may do this yourself if you want to add a plugin.- PluginDirectory.Branches:
A web application can branch into different paths, and Firely Server has two by default:
/
: the root branch, where the main FHIR RESTful API is hosted;/administration
: where the Firely Server Administration API is hosted.
Branches
contains a subdocument for each of the defined paths:- Path
The path for this branch. This is the part after the base URL that Firely Server is hosted on.
- Include
(Prefixes of) Configuration classes that add services and middleware to Firely Server.
- Exclude
(Prefixes of) Configuration classes that may not be executed.
Exclude
overridesInclude
and is useful if you want to use all but one configuration class from a namespace.
- SupportedInteractions:
A comma-separated list of all interactions Firely Server should enable on
[base]/[type]/[id]
(InstanceLevelInteractions),[base]/[type]
(TypeLevelInteractions), and[base]
(WholeSystemInteractions) levels. Firely Server will use this list to enable/disable supported interactions and reflect it in/metadata
accordingly.If you’d like to limit what operations your Firely Server supports, remove them from this list.
If you’ve added a custom plugin that enables a new interaction, make sure to load the plugin (see
PluginDirectory
above) and enable the interaction in this list. For example, if you’ve added theVonk.Plugin.ConvertOperation
$convert plugin inPipelineOptions.Branches.Include
, make sure to enable the operation$convert
as well:"WholeSystemInteractions": "$convert, capabilities, batch, transaction, history, search, compartment_system_search, $validate"
Configuration classes
A configuration class is a static class with two public static methods having the signature as below, that can add services to the Firely Server dependency injection system, and add middleware to the pipeline.
[VonkConfiguration (order: xyz)] //xyz is an integer
public static class MyVonkConfiguration
{
public static void ConfigureServices(IServiceCollection services)
{
//add services here to the DI system of ASP.NET Core
}
public static void Configure(IApplicationBuilder builder)
{
//add middleware to the pipeline being built with the builder
}
}
As you may have noticed, the methods resemble those in an ASP.NET Core Startup class. That is exactly where they are ultimately called from. We’ll explain each of the parts in more detail.
- VonkConfiguration
This is an attribute defined by Firely Server (package Vonk.Core, namespace Vonk.Core.Pluggability). It tells Firely Server to execute the methods in this configuration class. The
order
property determines where in the pipeline the middleware will be added. You can see the order of the plugins in the log at startup.- MyVonkConfiguration
You can give the class any name you want, it will be recognized by Firely Server through the attribute, not the classname. We do advise you to choose a name that actually describes what is configured. It is also better to have multiple smaller configuration classes than one monolith adding all your plugins, so you allow yourself to configure your plugins individually afterwards.
- ConfigureServices
The main requirements for this method are:
It is public static;
It has a first formal argument of type
Microsoft.Extensions.DependencyInjection.IServiceCollection
;It is the only method in this class matching the first two requirements.
This also means that you can give it a different name. Beyond that, you may add formal arguments for services that you need during configuration. You can only use services that are available from the ASP.NET Core hosting process, not any services you have added yourself earlier. Usual services to request are:
IConfiguration
IHostingEnvironment
These services will be injected automatically by Firely Server.
- Configure
The main requirements for this method are:
It is public static;
It has a first formal argument of type
Microsoft.AspNetCore.Buider.IApplicationBuilder
;It is the only method in this class matching the first two requirements.
This also means that you can give it a different name. Beyond that, you may add formal arguments for services that you may need during configuration. Here you can use services that are available from the ASP.NET Core hosting process and any services you have added yourself earlier. For services in request scope please note that this method is not run in request scope. These services will be injected automatically by Firely Server.
We provided an example of this: creating your own landing page.
Detailed logging of loading plugins
If your plugin or any of the Firely Server plugins appears not to be loaded correctly, you may inspect what happens in more detail in the log. See Log settings for where you can find the log file.
You can vary the log level for Vonk.Core.Pluggability.VonkConfigurer
to hide or reveal details.
On the Information
level, Firely Server will tell you which assemblies are loaded and searched for VonkConfiguration
attributes:
Looking for Configuration in these assemblies:
C:\data\dd18\vonk_preview\Vonk.Administration.Api.dll
C:\data\dd18\vonk_preview\Vonk.Core.dll
C:\data\dd18\vonk_preview\Vonk.Fhir.R3.dll
C:\data\dd18\vonk_preview\Vonk.Fhir.R4.dll
C:\data\dd18\vonk_preview\Vonk.Repository.Generic.dll
C:\data\dd18\vonk_preview\Vonk.Repository.Memory.dll
C:\data\dd18\vonk_preview\Vonk.Repository.MongoDb.dll
C:\data\dd18\vonk_preview\Vonk.Repository.Sql.dll
C:\data\dd18\vonk_preview\vonk.server.dll
C:\data\dd18\vonk_preview\Vonk.Server.PrecompiledViews.dll
C:\data\dd18\vonk_preview\Vonk.Smart.dll
C:\data\dd18\vonk_preview\Vonk.Subscriptions.dll
C:\data\dd18\vonk_preview\Vonk.UI.Demo.dll
C:\data\dd18\vonk_preview\plugins\Visi.Repository.dll
C:\data\dd18\vonk_preview\plugins\Vonk.Facade.Relational.dll
Also on the Information
level, Firely Server will show the services and middleware as it has loaded, in order.
The list below is also the default pipeline as it is configured for Firely Server.
Configuration:
/
FhirR3Configuration [100] | Services: V | Pipeline: X
FhirR4Configuration [101] | Services: V | Pipeline: X
MetadataConfiguration [110] | Services: V | Pipeline: X
LicenseConfiguration [120] | Services: V | Pipeline: X
SerializationConfiguration [130] | Services: V | Pipeline: X
RepositorySearchSupportConfiguration [140] | Services: V | Pipeline: X
RepositoryIndexSupportConfiguration [141] | Services: V | Pipeline: X
PluggabilityConfiguration [150] | Services: V | Pipeline: X
ViSiConfiguration [240] | Services: V | Pipeline: X
DemoUIConfiguration [800] | Services: V | Pipeline: V
VonkToHttpConfiguration [1110] | Services: V | Pipeline: V
VonkFeaturesExtensions [1120] | Services: X | Pipeline: V
FormatConfiguration [1130] | Services: V | Pipeline: V
LongRunningConfiguration [1170] | Services: V | Pipeline: V
VonkCompartmentsExtensions [1210] | Services: X | Pipeline: V
SupportedInteractionConfiguration [1220] | Services: V | Pipeline: V
UrlMappingConfiguration [1230] | Services: V | Pipeline: V
ElementsConfiguration [1240] | Services: V | Pipeline: V
FhirBatchConfiguration [3110] | Services: V | Pipeline: V
FhirTransactionConfiguration [3120] | Services: V | Pipeline: V
SubscriptionConfiguration [3200] | Services: V | Pipeline: V
ValidationConfiguration [4000] | Services: V | Pipeline: V
DefaultShapesConfiguration [4110] | Services: V | Pipeline: V
CapabilityConfiguration [4120] | Services: V | Pipeline: V
IncludeConfiguration [4210] | Services: V | Pipeline: X
SearchConfiguration [4220] | Services: V | Pipeline: V
ProfileFilterConfiguration [4310] | Services: V | Pipeline: V
PrevalidationConfiguration [4320] | Services: V | Pipeline: V
ReadConfiguration [4410] | Services: V | Pipeline: V
CreateConfiguration [4420] | Services: V | Pipeline: V
UpdateConfiguration [4430] | Services: V | Pipeline: V
DeleteConfiguration [4440] | Services: V | Pipeline: V
ConditionalCreateConfiguration [4510] | Services: V | Pipeline: V
ConditionalUpdateConfiguration [4520] | Services: V | Pipeline: V
ConditionalDeleteConfiguration [4530] | Services: V | Pipeline: V
HistoryConfiguration [4610] | Services: V | Pipeline: V
VersionReadConfiguration [4620] | Services: V | Pipeline: V
InstanceValidationConfiguration [4840] | Services: V | Pipeline: V
SnapshotGenerationConfiguration [4850] | Services: V | Pipeline: V
/administration
SqlVonkConfiguration [220] | Services: V | Pipeline: X
SqlAdministrationConfiguration [221] | Services: V | Pipeline: X
DatabasePluggabilityConfiguration [300] | Services: V | Pipeline: X
VonkToHttpConfiguration [1110] | Services: V | Pipeline: V
VonkFeaturesExtensions [1120] | Services: X | Pipeline: V
FormatConfiguration [1130] | Services: V | Pipeline: V
SecurityConfiguration [1150] | Services: V | Pipeline: V
AdministrationOperationConfiguration [1160] | Services: V | Pipeline: V
LongRunningConfiguration [1170] | Services: V | Pipeline: V
VonkCompartmentsExtensions [1210] | Services: X | Pipeline: V
SupportedInteractionConfiguration [1220] | Services: V | Pipeline: V
UrlMappingConfiguration [1230] | Services: V | Pipeline: V
ElementsConfiguration [1240] | Services: V | Pipeline: V
DefaultShapesConfiguration [4110] | Services: V | Pipeline: V
AdministrationSearchConfiguration [4221] | Services: V | Pipeline: V
ValidationConfiguration [4310] | Services: V | Pipeline: X
SubscriptionValidationConfiguration [4330] | Services: V | Pipeline: V
ChangeInterceptionConfiguration [4390] | Services: X | Pipeline: V
AdministrationReadConfiguration [4411] | Services: V | Pipeline: V
AdministrationCreateConfiguration [4421] | Services: V | Pipeline: V
AdministrationUpdateConfiguration [4431] | Services: V | Pipeline: V
AdministrationDeleteConfiguration [4441] | Services: V | Pipeline: V
AdministrationImportConfiguration [5000] | Services: V | Pipeline: V
CodeSystemLookupConfiguration [5110] | Services: V | Pipeline: V
ValueSetValidateCodeInstanceConfiguration [5120] | Services: V | Pipeline: V
ValueSetValidateCodeTypeConfiguration [5130] | Services: V | Pipeline: V
ValueSetExpandInstanceConfiguration [5140] | Services: V | Pipeline: V
ValueSetExpandTypeConfiguration [5150] | Services: V | Pipeline: V
CodeSystemComposeInstanceConfiguration [5160] | Services: V | Pipeline: V
CodeSystemComposeTypeConfiguration [5170] | Services: V | Pipeline: V
It shows all the configuration classes it found, and whether a ConfigureServices and / or a Configure method was found and executed.
It also displays the value of the order
property of the VonkConfiguration
attribute for each configuration class.
This allows you to determine an appropriate order for your own configuration class.
On the Verbose
level, Firely Server will also tell you why each configuration class that is found is being included or excluded. An example:
2018-07-02 12:58:10.586 +02:00 [Firely Server] [Verbose] [Machine: XYZ] [ReqId: ] Searching for configurations in assembly "Vonk.Core, Version=0.7.0.0, Culture=neutral, PublicKeyToken=null"
2018-07-02 12:58:10.625 +02:00 [Firely Server] [Verbose] [Machine: XYZ] [ReqId: ] "Vonk.Core.Serialization.SerializationConfiguration" was included on "/" because it matches the include "Vonk.Core"
2018-07-02 12:58:10.625 +02:00 [Firely Server] [Verbose] [Machine: XYZ] [ReqId: ] "Vonk.Core.Serialization.SerializationConfiguration" was not included on "/administration" because it did not match any include
The order of plugins
Firely Server is organized as a pipeline of components - called Middleware. Every request travels through all the components until one of the components provides the response to the request. After that, the response travels back through all the components, in reverse order. Components that come after the responding component in the pipeline are not visited at all.
So let’s say you issue a FHIR read interaction, GET <base-url>/Patient/example
. The component implementing this interaction sits in the pipeline after search but before create. So the request will visit the search middleware (that will ignore it and just pass it on) but will never visit the create middleware.
So many components implement an interaction and provide a response to that interaction. In Firely Server those are called Handlers. Some components may not provide responses directly, but read or alter the request on the way in. Such a component is called a PreHandler. Reversely, a component may read or alter the response on the way back. Such a component is called a PostHandler.
A plugin can configure its own component in this pipeline but as you may understand by now it makes a difference where in the pipeline you put that component. Especially if it is a Pre- or PostHandler. To control the position in the pipeline, Firely Server uses the concept of ‘Order’.
The VonkConfiguration attribute lets you define an Order
for your component. This page explains how to choose a suitable number for that order.
Inspect order numbers in use
When you start Firely Server, the log lists all the loaded plugins, with their order. You can see an example here. Also the list of Plugins available for Firely Server includes the order number chosen for each of those plugins.
Minimum order
Registering services
The order is mainly relevant for middleware that you register in the pipeline, in the Configure(IApplicationBuilder app)
method. Some plugins, like e.g. a Facade implementation, only need to register services for the dependency injection framework, in the ConfigureServices(IServiceCollection services)
method.
If that is the case, the order is only relevant if you need to override a registration done by Firely Server. There are two ways:
Choose an order before Firely Server’s default registration. Firely Server in general uses
TryAddSingleton
orTryAddScoped
to register an implementation of an interface. This means that if an implementation is already registered, the TryAdd… will not register a second implementation.As an example: if you want to override the registration of
IReadAuthorizer
: that is registered from the RepositorySearchSupport plugin, with order 140. So you would choose an order lower than 140.Choose a high order (e.g. > 10000) and make sure your registration overwrites any existing registration.
services.AddOrReplace<IReadAuthorizer, MyReadAuthorizer>(ServiceLifetime.Scoped);
The latter method is the least error prone and therefore recommended.
If you only need to register interfaces and/or classes defined by your plugin, the order is not relevant, so pick any number. All service registrations are done before the pipeline itself is configured.
Registering middleware
For middleware it is more important where exactly it ends up in the pipeline. This depends mostly on what type of handler it is, see below at Handlers and pre- and posthandlers.
No matter what handler you have, it probably wants to act on the IVonkContext. Then it is important to be in the pipeline after the HttpToVonk plugin (order: 1110), since this plugin translates information from the HttpContext
to an IVonkContext
and adds the latter as a feature to the HttpContext.Features
collection.
Also, you probably want to set your response on the IVonkContext.Response
and not directly on the HttpContext.Response
. Then, you will need the VonkToHttp plugin (order: 1120) to translate the IVonkContext
back to the HttpContext
.
So in general, the minimum order you need for your plugin will be higher than 1120.
If you want your middleware to act on all the entries in a Batch or Transaction, you need to choose an order higher than that of the Transaction plugin, which is 3120.
Order collisions
If two plugins have the same order number, it is not defined in what order the plugins will be put in the pipeline. As long as those plugins act on disjoint sets of requests that may not be a problem. But it is recommended to avoid this by checking the orders already in use.
Handlers and pre- and posthandlers
In Firely Server you can define different types of middleware:
Handler - acts on requests of a certain type, provides the response to it and ends the pipeline.
Prehandler - acts on requests of certain type(s), may modify the request and sends the request further down the pipeline.
Posthandler - lets the request pass by to be handled further down the pipeline. When the response passes on the way back, it acts on requests or responses of certain type(s), and may modify the response.
This is explained in the session on Plugins from DevDays 2018.
What type of middleware you want your service to be is defined by your use of one of the *Handle...
methods from the VonkAppBuilder extension methods or the IApplicationBuilder extension methods.
Prehandler
A Prehandler needs to act before the actual handler will provide a response. So the order of it must be lower than any Handler that may handle the requests that this Prehandler is interested in.
So if you want a Prehandler to intercept all create interactions, you should choose an order lower than that of the Create plugin, which is 4420.
An example of this is the Prevalidation plugin. It needs to validate all resources that get handled by the Create, Update, Conditional Create and Conditional Update plugins. Of these, Create has the lowest order: 4420. So it must be below 4420. But it also needs to act on each resource in a Batch or Transaction, so it must be higher than these two, which means higher than 3120. So this is why we have chosen 4320 as order for Prevalidation.
Posthandler
A Posthandler needs to act after the actual handler provided a response. But due to the nature of the processing pipeline that means it must have an order lower than that of the handler(s) it wants to post-process. The idea is that the posthandler sits in the pipeline and lets the request pass through. Then one of the handlers provides the response and sends it back through the pipeline. It will pass through the posthandler again (now ‘backwards’), and then the posthandler will do its processing.
So if you want a Posthandler to process the responses of all create interactions (e.g. for logging purposes), you should choose an order lower than that of the Create plugin.
An example for this is the Include plugin. This must act on the response of the Search plugin. So the Include has order 4210, right before Search which has 4220.
Important classes and interfaces
If you want to develop a plugin for Firely Server, there are a couple of classes that you will probably interact with. These classes are listed under Reference - Programming API.
Template for a plugin
Attention
A complete template for a Firely Server plugin can be found on Github. It covers all details on how to create a custom operation and how to use Firely Server services internally.
A regular Firely Server plugin acts on the IVonkContext and its IVonkRequest and IVonkResponse properties.
You don’t have to create ASP.NET Core middleware yourself. You just need to create a service acting on IVonkContext. In the configuration you can specify when the service should be called, and in which position in the pipeline it should be put. See Interaction Handling for details on that.
You can use the following code as a template for a plugin:
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using System.Threading.Tasks;
using Vonk.Core.Context;
using Vonk.Core.Context.Features;
using Vonk.Core.Pluggability;
using Vonk.Fhir.R3;
using F = Hl7.Fhir.Model;
namespace com.mycompany.vonk.myplugin
{
public class MyPluginService
{
public async Task Act(IVonkContext vonkContext)
{
var (request, args, response) = vonkContext.Parts();
//do something with the request
//write something to the response
response.Payload = new F.Patient{Id = "pat1"}.ToIResource();
response.HttpResult = 200;
}
}
[VonkConfiguration(order: 5000)]
public static class MyPluginConfiguration
{
public static IServiceCollection AddMyPluginServices(IServiceCollection services)
{
services.TryAddScoped<MyPluginService>();
return services;
}
public static IApplicationBuilder UseMyPlugin(IApplicationBuilder app)
{
app.OnCustomInteraction(VonkInteraction.system_custom, "myOperation").HandleAsyncWith<MyPluginService>((svc, context) => svc.Act(context));
return app;
}
}
}
Returning non-FHIR content from a plugin
Some plugins may need to return content that is not a FHIR Resource. You currently cannot do that through the IVonkResponse
. But there is another way: write directly to the HttpContext.Response.
The plugin with order 1110 makes the IVonkContext
accessible. That means if you pick an order higher than 1110 you can read the IVonkContext.Request
and .Arguments
, and write directly to the HttpContext.Response
.
If you write the response yourself, you also need to set the StatusCode and Content-Type on the HttpContext.Response.
Note
If you write to the HttpContext.Response directly, the payload of the IVonkContext.Response is ignored.
The steps to take are:
Configure your plugin with an order higher than 1110
Write the
HttpContext.Response.Body
directlySet other properties of the
HttpContext.Response
(likeStatusCode
) yourself.
An example of such a plugin would look like this. Note that this is now regular ASP.NET Core Middleware, not a service like in Template for a plugin.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using System.Text;
using System.Threading.Tasks;
using Vonk.Core.Context;
using Vonk.Core.Context.Features;
using Vonk.Core.Pluggability;
namespace com.mycompany.vonk.myplugin
{
public class MyPluginMiddleware
{
private readonly RequestDelegate _next;
public MyPluginMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
var vonkContext = httpContext.Vonk();
var (request, args, _) = vonkContext.Parts();
if (VonkInteraction.system_custom.HasFlag(request.Interaction))
{
//write something directly to the HttpContext.Response. Now you also have to set the Content-Type header and the Content-Length yourself.
var message = "This is a response that is not a FHIR resource";
string contentLength = Encoding.UTF8.GetByteCount(message).ToString();
httpContext.Response.Headers.Add("Content-Type", "text/plain; charset=utf-8");
httpContext.Response.Headers.Add("Content-Length", contentLength);
httpContext.Response.StatusCode = 200;
await httpContext.Response.WriteAsync("This is a response that is not a FHIR resource");
}
else
{
await _next(httpContext);
}
}
}
[VonkConfiguration(order: 1115)] //note the order: higher than 1110
public static class MyPluginConfiguration
{
public static IServiceCollection AddMyPluginServices(IServiceCollection services)
{
//No services to register in this example, but if you create services to do the actual work - register them here.
return services;
}
public static IApplicationBuilder UseMyPlugin(IApplicationBuilder app)
{
app.UseMiddleware<MyPluginMiddleware>(); //You cannot use the extension methods that allow you to filter the requests.
return app;
}
}
}
Firely Server Plugin example - Create a new landing page
As a minimal example of how to use Firely Server Plugins we will show you how to create a library with your own landing page, and use it to replace the landing page that is provided by Firely Server. The landing page is the webpage you see when you access Firely Server’s endpoint with a browser. By default it looks like this:

Create a new ASP.NET Core web application
In Visual Studio create a new project of type ASP .NET Core Web Application:

Press OK to continue.

Choose a name for your project and solution. Click Create to continue.

Choose ASP.NET Core 3.1 and select Web Application (Model-View-Controller). Press OK.
Add Firely Server Package
Add Vonk.Core via the Nuget Package Manager:

This will give you access to all the core components of Firely Server, including the Vonk.Core.Pluggability.VonkConfiguration
attribute.
Adjust project file
Add wwwroot and Views as an Embedded resource in the project file (that is neccessary for Firely Server to pick them up from a library dll). To edit the project file, right click on the project file and select Edit <projectname>.cproj:
<ItemGroup>
<EmbeddedResource Include="wwwroot\**\*" />
<EmbeddedResource Include="Views\**" />
</ItemGroup>
The project file will look like this:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Vonk.Core" Version="3.4.0" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="wwwroot\**\*" />
<EmbeddedResource Include="Views\**" />
</ItemGroup>
</Project>
Save the project file.
Create the configuration class
Next, add a new file for the configuration class, as described in Configuration classes. Annotate it with [VonkConfiguration(order: 802)]
. In the example below the class is named UIConfiguration.
Then add the static methods as prescribed:
public static IServiceCollection AddUIServices(IServiceCollection services)
{
var thisAssembly = typeof(UIConfiguration).GetTypeInfo().Assembly;
services
.AddMvc(option => option.EnableEndpointRouting = false)
.AddRazorRuntimeCompilation()
.AddApplicationPart(thisAssembly)
.AddControllersAsServices();
var embeddedFileProvider = new EmbeddedFileProvider(
thisAssembly,
thisAssembly.GetName().Name
);
services.Configure<MvcRazorRuntimeCompilationOptions>(options =>
{
options.FileProviders.Clear();
options.FileProviders.Add(embeddedFileProvider);
});
return services;
}
public static IApplicationBuilder UseUI(IApplicationBuilder app)
{
var thisAssembly = typeof(UIConfiguration).GetTypeInfo().Assembly;
var embeddedStaticFileProvider = new EmbeddedFileProvider(
thisAssembly,
thisAssembly.GetName().Name + ".wwwroot"
);
app.UseStaticFiles(new StaticFileOptions() { FileProvider = embeddedStaticFileProvider });
return app.MapWhen(ctx => ctx.IsBrowserRequest(), ab => ab.UseMvcWithDefaultRoute());
}
The source file will then look like this:

Deploy and Configure
Build this project in Release mode and copy the produced dll (located in <src>\bin\Release\netcoreapp3.1) to the plugin directory of Firely Server, as configured in the PipelineOptions:PluginDirectory.
Go to the Firely Server settings of Firely Server, and replace the namespace of the landingpage (Vonk.UI.Demo
) in the include of the PipelineOptions:
"PipelineOptions": {
"PluginDirectory": "./plugins",
"Branches": [
{
"Path": "/",
"Include": [
"Vonk.Core",
"Vonk.Fhir.R3",
"Vonk.Fhir.R4",
//"Vonk.Fhir.R5"
"Vonk.Repository.SqlVonkConfiguration",
"Vonk.Repository.SqliteVonkConfiguration",
"Vonk.Repository.MongoDbVonkConfiguration",
"Vonk.Repository.MemoryVonkConfiguration",
"Vonk.Subscriptions",
"Vonk.Smart",
"WebApplication2" //This is the adjustment you make.
"Vonk.Plugin.DocumentOperation.DocumentOperationConfiguration",
"Vonk.Plugin.ConvertOperation.ConvertOperationConfiguration",
"Vonk.Plugin.BinaryWrapper",
"Vonk.Plugin.MappingToStructureMap.MappingToStructureMapConfiguration",
"Vonk.Plugin.TransformOperation.TransformOperationConfiguration",
"Vonk.Plugin.Audit"
],
"Exclude": [
"Vonk.Subscriptions.Administration"
]
},
{
"Path": "/administration",
"Include": [
"Vonk.Fhir.R3",
"Vonk.Fhir.R4",
//"Vonk.Fhir.R5"
"Vonk.Repository.SqlAdministrationVonkConfiguration",
"Vonk.Repository.SqliteAdministrationVonkConfiguration",
"Vonk.Repository.MongoDbAdministrationVonkConfiguration",
"Vonk.Repository.MemoryAdministrationVonkConfiguration",
"Vonk.Subscriptions.Administration",
"Vonk.Plugins.Terminology",
"Vonk.Plugin.Audit",
"Vonk.Administration"
],
"Exclude": [
"Vonk.Core.Operations",
"Vonk.Core.Licensing.LicenseRequestJobConfiguration"
]
}
]
}
Run and admire
Now run Firely Server from the commandline or Powershell window with
> dotnet .\Vonk.Server.dll
Open a browser and visit the homepage of Firely Server (http://localhost:4080) to admire your own landingpage.
Firely Server Plugin example - $document operation
As a more elaborate example we implemented the $document operation from the FHIR Specification as a plugin.
You can find the code on GitHub.
BinaryWrapper plugin
Description
Enables you to send binary content to Firely Server and have it stored as a Binary resource, as well as the reverse: get a Binary resource and have it returned in its original binary format. The contents are Base64 encoded and stored inside the resource in the Firely Server database. Therefore this plugin is only suitable for small binary files.
Sending binary content example request:
POST <base-url>/Binary
Content-Type = application/pdf
Accept = application/fhir+json; fhirVersion=4.0
Body: enclose a file with the actual contents
This can also be done with a PUT:
PUT <base-url>/Binary/example
The Content-Type must be the mediatype of the actual contents. It will only be accepted by Firely Server if it is one of the mediatypes listed in the
RestrictToMimeTypes
below.The Accept header can be either a fhir mediatype, with any of the supported FHIR versions. You could also set it equal to the Content-Type in which case you will be returned the same contents again.
Getting binary content example request:
GET <base-url>/Binary/example
Accept = application/pdf; fhirVersion=4.0
The Accept header should match the mediatype of the actual contents. If you don’t know the mediatype, you could request the binary resource in FHIR format first and examine its
contentType
element.
Configuration
"Vonk.Plugin.BinaryWrapper":{
"RestrictToMimeTypes": ["application/pdf", "text/plain", "image/png", "image/jpeg"]
},
"SizeLimits": {
"MaxResourceSize": "1MiB", // b/kB/KiB/Mb/MiB, no spaces
},
"PipelineOptions": {
"Branches": [
{
"Path": "/",
"Include": [
...
"Vonk.Plugin.BinaryWrapper"
]
},
...
]
}
RestrictToMimeTypes
protects Firely Server from arbitrary content.This plugin honours the Firely Server setting for maximum resource size. This protects Firely Server from binary contents that are too large to store in the database.
The namespace
Vonk.Plugin.BinaryWrapper
configures both encoding and decoding of binary contents. You can configure them separately as well:"PipelineOptions": { "Branches": [ { "Path": "/", "Include": [ ... "Vonk.Plugin.BinaryWrapper.BinaryEncodeConfiguration", "Vonk.Plugin.BinaryWrapper.BinaryDecodeConfiguration" ] }, ... ] }
Relationships
The TransformOperation plugin relies on the BinaryWrapper to encode the contents to be mapped, so the IVonkContext then contains a proper Binary resource in its payload to work with.
Release notes
Version 0.3.0
Built against Firely Server (Vonk) 3.2.0
Compatible with Firely Server (Vonk) 3.2.0, 3.2.1, 3.3.0
Introduces the decoding of Binary resources, so you can GET a Binary resource in its original binary format.
Version 0.2.0
Build against Firely Server (Vonk) 3.0.0
Compatible with Firely Server (Vonk) 3.0.0
Functionally equivalent to version 0.1.0
Version 0.1.0
Build against Firely Server (Vonk) 2.1.0
Compatible with Firely Server (Vonk) 2.1.0
Introduces the encoding of Binary resources, so you can POST binary contents and have it stored as a Binary resource.
Convert plugin
Description
Enables you to convert between json and xml representation of a resource using the $convert operation. It can not convert between different FHIR versions.
Convert example request:
POST <base-url>/$convert
Content-Type = application/fhir+json; fhirVersion=4.0
Accept = application/fhir+xml; fhirVersion=4.0
Body: a resource in JSON format
This can also be done reversely, with a body in XML format
Configuration
"PipelineOptions": {
"Branches": [
{
"Path": "/",
"Include": [
...
"Vonk.Plugin.ConvertOperation"
]
},
...
]
}
Release notes
Version 0.2.0
Build against Firely Server (Vonk) 3.2.0
Compatible with Firely Server (Vonk) 3.2.0, 3.3.0
Functionally equivalent to version 0.1.0
Version 0.1.0
Build against Firely Server (Vonk) 3.0.0
Compatible with Firely Server (Vonk) 3.0.0, 3.1.0
Introduces the implementation of $convert for conversion between json and xml.
Firely Server Facade
Firely Server Facade is a means to use the Firely Server implementation of the FHIR RESTful API on top of an existing repository. This repository may be a relational database, a nosql database or even another web api.
This chapter details the two options for setting up a Firely Server Facade, and provides you with an exercise to get some hands-on experience with a Facade solution.
Facade setup configuration
To set up a Firely Server Facade, you will need to create a library with your own implementation of the interfaces for reading and/or writing FHIR resources and provide that as a plugin to Firely Server.
Provide a plugin to Firely Server
This leverages the capabilities of Firely Server Plugins. With this setup you:
create a new ASP.NET Core library
include Firely Server NuGet packages
implement your own repository backend to interface with your data store (can be SQL server or any other medium)
configure the PipelineOptions to use your library instead of Firely Server’s own repository implementation
configure the PipelineOptions to limit the plugins to those that are supported by your repository implementation.
The benefit of using this approach is that you automatically get to use all of Firely Server’s configuration, logging, Application Insights integration, the Administration API, etc. described in the other sections of this documentation.
The exercise below uses this setup.
Note
Although we take care to try and avoid breaking changes, please be prepared to retest and update your plugins when you choose to update Firely Server.
Exercise: Build your first Facade
The best way to get experience in developing a Firely Server Facade is by following the exercise - build your first facade. This exercise builds a facade on a simple relational database, by creating a plugin and inserting that into the Firely Server pipeline.
Using a Firely Server Facade allows you to open up legacy systems to the FHIR ecosystem, or add a whole new database backend.
In this exercise you will use Firely Server Facade libraries to build an ASP.NET Core library implementing a FHIR RESTful API on top of an existing database.
The existing database contains two simple tables ‘Patient’ and ‘BloodPressure’. In the exercise we refer to it as the ‘ViSi’ system, short for ‘VitalSigns’.
Git repository Vonk.Facade.Starter
This repository contains the completed exercise. You can find the repository at Github. Of course we do recommend to try and do the exercise yourself first, before looking at the final result.
Prerequisites and Preparations
Experience with programming ASP.NET (Core) libraries.
Basic understanding of the FHIR RESTful API and FHIR servers.
Visual Studio 2017 or newer
get a free community edition at https://www.visualstudio.com/downloads/
be sure to select the components for C# ASP.NET Core web development
.NET Core 2.0 SDK, from https://www.microsoft.com/net/download/windows
this is probably installed along with the latest Visual Studio, but needed if your VS is not up-to-date.
SQL Server 2012 or newer:
get a free developer or express edition at https://www.microsoft.com/en-us/sql-server/sql-server-downloads
add SQL Server Management Studio from https://docs.microsoft.com/en-us/sql/ssms/download-sql-server-management-studio-ssms
Postman, Fiddler or a similar tool to issue http requests and inspect the responses.
Installing the Firely Server
Before you can start implementing your facade, you need to have the Firely Server installed. See Getting Started on how to download the binaries and license key.
Preparing the database
Download the CreateDatabase script
, and create a SQL Server database with it.
It creates a database ‘ViSi’ with two tables: Patient and BloodPressure. You can familiarize yourself with the table structure and contents to prepare for the mapping to FHIR later on.
Proceed to the next step to start your facade project.
Starting your project
In this step, you will create and set up your project to start building the facade.
Create new project
Open Visual Studio 2017
File | New | Project
Choose Class Library (.NET Core)
Project name and directory at your liking; Click OK
Add Firely Server Packages
Tools > NuGet Package Manager > Package Manager Console
Run
Install-Package Vonk.Core
Run
Install-Package Vonk.Fhir.R3
(if you want to use R3)Run
Install-Package Vonk.Fhir.R4
(if you want to use R4)
Note
You can install the latest beta release of the Firely Server packages by adding -IncludePrerelease
to the install command.
Mapping the database
In this step you will start mapping the existing database model to FHIR resources.
Reverse engineer the database model
To use EF Core, install the package for the database provider(s) you want to target. This walkthrough uses SQL Server. For a list of available providers see Database Providers.
Tools ➡️ NuGet Package Manager ➡️ Package Manager Console
Run
Install-Package Microsoft.EntityFrameworkCore.SqlServer
We will be using some Entity Framework Tools to create a model from the database. So we will install the tools package as well:
Run
Install-Package Microsoft.EntityFrameworkCore.Tools
Note
The current version of Firely Server uses the latest EF Core libraries. If you are developing for an older Firely Server version, please check the version of
Microsoft.EntityFrameworkCore.SqlServer.dll
in your Firely Server distribution folder. Add -Version <version>
to the commands above to use
the same version in your Facade implementation.
Now it’s time to create the EF model based on your existing database.
Tools ➡️ NuGet Package Manager ➡️ Package Manager Console
Run the following command to create a model from the existing database. Adjust the Data Source to your instance of SQL Server. If you receive an error stating The term ‘Scaffold-DbContext’ is not recognized as the name of a cmdlet, then close and reopen Visual Studio.:
Scaffold-DbContext "MultipleActiveResultSets=true;Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=ViSi;Data Source=localhost" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models //For localdb: Scaffold-DbContext "Server=(localdb)\mssqllocaldb;Database=ViSi;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models //For SQLEXPRESS: Scaffold-DBContext "Data Source=(local)\SQLEXPRESS;Initial Catalog=ViSi;Integrated Security=True" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models
You can also generate the scaffolding using the EF CLI tools which are crossplatform:
dotnet ef dbcontext scaffold "User ID=SA;Password=<enter your password here>;MultipleActiveResultSets=true;Server=tcp:.;Connect Timeout=5;Integrated Security=false;Persist Security Info=False;Initial Catalog=ViSi;Data Source=localhost" Microsoft.EntityFrameworkCore.SqlServer --output-dir Models
The reverse engineer process creates entity classes (Patient.cs & BloodPressure.cs) and a derived context (ViSiContext.cs) based on the schema of the existing database.
The entity classes are simple C# objects that represent the data you will be querying and saving. Later on you will use these classes to define your queries on and to map the resources from.
Clean up generated code
To avoid naming confusion with the FHIR Resourcetype Patient, rename both files and classes:
Patient ➡️ ViSiPatient
BloodPressure ➡️ ViSiBloodPressure
In
ViSiContext.cs
, ensure that the EF objects mapping our class to the database table are correct and without prefixes (since it’s just our local classes that have them):public virtual DbSet<ViSiBloodPressure> BloodPressure { get; set; } public virtual DbSet<ViSiPatient> Patient { get; set; }
The Scaffold command puts your connectionstring in the ViSiContext class. That is not very configurable. Later in the exercise, we will add it as ‘DbOptions’ to the appsettings.instance.json file in 3. Configure your Firely Server Facade.
Rename the default Class1 class to DbOptions, and add this to interpret the setting:
public class DbOptions { public string ConnectionString { get; set; } }
Remove the empty constructors from the ViSiContext class
Use the options in your ViSiContext class, by adding:
private readonly IOptions<DbOptions> _dbOptionsAccessor; public ViSiContext(IOptions<DbOptions> dbOptionsAccessor) { _dbOptionsAccessor = dbOptionsAccessor; }
Change the existing
OnConfiguring
method that contains the connectionstring to:protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { if (!optionsBuilder.IsConfigured) { optionsBuilder.UseSqlServer(_dbOptionsAccessor.Value.ConnectionString); } }
Create your first mapping
Add a new
public
classResourceMapper
to the projectAdd usings for
Vonk.Core.Common
, forHl7.Fhir.Model
, forHl7.Fhir.Support
and for<your project>.Models
Add a method to the class
public IResource MapPatient(ViSiPatient source)
In this method, put code to create a FHIR Patient object, and fill its elements with data from the ViSiPatient:
var patient = new Patient { Id = source.Id.ToString(), BirthDate = source.DateOfBirth.ToFhirDate() }; patient.Identifier.Add(new Identifier("http://mycompany.org/patientnumber", source.PatientNumber)); // etc.
For more examples of filling the elements, see the FHIR API documentation: FHIR-model.
Then return the created Patient object as an IResource with
patient.ToIResource()
.IResource
is an abstraction from actual Resource objects as they are known to specific versions of the Hl7.Fhir.Net API. See IResource.
Enable Search
Enabling search involves two major steps:
Creating a query to the database based on the bits and pieces in the search url
Getting a count and actual data from the database with that query, and map it to a SearchResult
The next paragraphs will walk you through these steps.
1. Create a query
Firely Server Facade is meant to be used across all kinds of database paradigms and schemas. Or even against underlying web services or stored procedures. This means Firely Server cannot prescribe the way your query should be expressed. After all, it could be an http call to a webservice, or a json command to MongoDB.
In our case we will build a LINQ query against our ViSi model, that is translated by Entity Framework to a SQL query.
Because this is a quite common case, Firely Server provides a basis for it in the package Vonk.Facade.Relational
.
Go back to the NuGet Package Manager Console and run
Install-Package Vonk.Facade.Relational
Note
If you did this previously for the other Firely Server packages, you can install the latest beta release of this package as well by adding
-IncludePrerelease
to the install command.
Adding classes for the query
You usually create a query class per ResourceType. The Query object is used to capture the elements of the search that are provided to the QueryFactory.
In this exercise we start with resource type Patient, and will create a PatientQuery
and PatientQueryFactory
class.
Because PatientQuery has no specific content of its own, we will include both in one file.
Add a new class
PatientQueryFactory
to the root of the projectAdd using statements for
Vonk.Facade.Relational
,Microsoft.EntityFrameworkCore
, and<your project>.Models
Above the actual
PatientQueryFactory
class insert thePatientQuery
class:public class PatientQuery: RelationalQuery<ViSiPatient> {}
Now flesh out the
PatientQueryFactory
:public class PatientQueryFactory: RelationalQueryFactory<ViSiPatient, PatientQuery> {}
Adding a constructor
You have to provide a constructor for the factory class. With this you tell Firely Server for which resource type this QueryFactory is valid. The DbContext is used for retrieving DbSets for related entities, as we will see later:
public PatientQueryFactory(DbContext onContext) : base("Patient", onContext) { }
Deciding on a FHIR version
You need to explicitly tell Firely Server for which FHIR version(s) you wish to return resources. If you don’t override EntryInformationModel
, any search will fail with a 501 Not Implemented
. The following override will allow searches for any possible FHIR version to be handled by your facade:
public override PatientQuery EntryInformationModel(string informationModel)
{
return default(PatientQuery);
}
If you wish to implement search only for a single FHIR version or for a limited set of versions you can override the method like this:
public override PatientQuery EntryInformationModel(string informationModel)
{
if (informationModel == VonkConstants.Model.FhirR4)
{
return default(PatientQuery);
}
throw new NotImplementedException($"FHIR version {informationModel} is not supported");
}
Handling the search request
Each of the searchparameters in the search request triggers a call to the Filter
method. This method takes a
parameterName
and IFilterValue
as its arguments.
The parameterName
is the searchparameter as it was used in the search url. This name corresponds with the code field in a SearchParameter resource.
The IFilterValue value
is one of 10 possible implementations, one for each type of SearchParameter. See IFilterValue implementations
for a short description of these possibilities.
By default the Filter
method dispatches the call to a suitable overload of AddValueFilter
, based on the actual type of the value
parameter.
It is up to you to override the ones you support any parameters for.
Override the method
PatientQuery AddValueFilter(string parameterName, TokenValue value)
in thePatientQueryFactory
class to implement support for the_id
parameter, which is a token type parameter.The
_id
parameter must be matched against the ViSiPatient.Id property. So we have to:Parse the Token.Code to an integer (ViSiPatient.Id is of type int)
Create a query with a predicate on ViSiPatient.Id.
This is how:
if (parameterName == "_id") { if (!int.TryParse(value.Code, out int patientId)) { throw new ArgumentException("Patient Id must be an integer value."); } else { return PredicateQuery(vp => vp.Id == patientId); } } return base.AddValueFilter(parameterName, value);
Note
The ArgumentException
in this code will automatically result in setting the argument status to error, so the Firely Server
will send a response with an error code and OperationOutcome. See the information about the IArgumentCollection
and IArgument
classes in IVonkContext.
That’s it for now, we will add support for another parameter later.
IFilterValue implementations
There are 10 possible implementations you can use as value for the IFilterValue parameter in the Query. The first 7 are the general search parameter types: StringValue, DateTimeValue, TokenValue, NumberValue, QuantityValue, UriValue and ReferenceValue.
Besides that there are two special values for chaining and reverse chaining: ReferenceToValue and ReferenceFromValue.
And finally there is a special value for when Firely Server does not know the SearchParameter and hence not the type of it: RawValue.
This is the second step for enabling search.
2. Get the data and map to FHIR
Getting the data happens in the implementation of the ISearchRepository
. It has only one method, Search
.
The Vonk.Facade.Relational package has an abstract implementation of it that you can use as a starting point.
This implementation assumes that you can support searching for exactly one ResourceType at a time.
The gist of the implementation is to switch the querying based on the ResourceType. The querying itself then looks pretty much the same for every type of resource.
Add the new class ViSiRepository to the root of the project:
public class ViSiRepository : SearchRepository
You have to provide a constructor that gets a QueryContext. We’ll get to that later. Apart from that you will need your DbContext (ViSiContext) to query on, and the ResourceMapper to perform the mapping of the results. So put all of that in the constructor:
private readonly ViSiContext _visiContext; private readonly ResourceMapper _resourceMapper; public ViSiRepository(QueryContext queryContext, ViSiContext visiContext, ResourceMapper resourceMapper) : base(queryContext) { _visiContext = visiContext; _resourceMapper = resourceMapper; }
You will have to implement the abstract method
Task<SearchResult> Search(string resourceType, IArgumentCollection arguments, SearchOptions options)
.First, let’s inspect the parameters:
- resourceType
The ResourceType that is being searched for, e.g. Patient in
<firely-server-endpoint>/Patient?...
- arguments
All the arguments provided in the search, whether they come from the path (like ‘Patient’), the querystring (after the ‘?’), the headers or the body. Usually you don’t have to inspect these yourself.
- options
A few hints on how the query should be executed: are deleted or contained resources allowed etc. Usually you just pass these on as well.
The pattern of the implementation is:
switch on the resourceType
dispatch to a method for querying for that resourceType
Naturally we do this async, since in a web application you should never block a thread while waiting for the database.
To implement this, add this to the class:
protected override async Task<SearchResult> Search(string resourceType, IArgumentCollection arguments, SearchOptions options) { switch (resourceType) { case "Patient": return await SearchPatient(arguments, options); default: throw new NotImplementedException($"ResourceType {resourceType} is not supported."); } }
Now we moved the problem to
SearchPatient
, so this method needs to be implemented. The pattern here is:Create a query - in this case, a PatientQuery via PatientQueryFactory.
Execute the query against the DbContext (our _visiContext) to get a count of matches.
Execute the query against the DbContext to get the current page of results.
Map the results using the _resourceMapper
The implementation of this looks like:
private async Task<SearchResult> SearchPatient(IArgumentCollection arguments, SearchOptions options) { var query = _queryContext.CreateQuery(new PatientQueryFactory(_visiContext), arguments, options); var count = await query.ExecuteCount(_visiContext); var patientResources = new List<IResource>(); if (count > 0) { var visiPatients = await query.Execute(_visiContext).ToListAsync(); foreach (var visiPatient in visiPatients) { patientResources.Add(_resourceMapper.MapPatient(visiPatient)); } } return new SearchResult(patientResources, query.GetPageSize(), count); }
What happens behind the scenes is that the QueryBuilderContext creates a QueryBuilder that analyzes all the arguments and options, and translates that into calls into your PatientQueryFactory. This pattern offers maximum assistance in processing the search, but also gives you full control over the raw arguments in case you need that for anything. Any argument that is reported as in Error, or not handled will automatically show up in the OperationOutcome of the Firely Server response.
In the next paragraph you will configure your Firely Server to use your Facade, and can – finally – try out some searches. The paragraph after that expands the project to support ViSiBloodPressure Observations, and details how to add custom search parameters.
Finalizing search
In the previous steps you have created search support for the _id parameter on a Patient resource type. In order to test if your Facade implementation works correctly, you will need to perform a couple of steps:
Create a configuration class for the ASP .Net Core pipeline
Plug the Facade into the Firely Server
Configure the Firely Server to use your repository
1. Add configuration class
To add your repository service to the Firely Server pipeline, you will need to add a configuration class that sets the order of inclusion, and adds to the services. For background information, see Configuration classes.
Add a static class to your project called
ViSiConfiguration
Add the following code to it:
[VonkConfiguration(order: 240)] public static class ViSiConfiguration { public static IServiceCollection AddViSiServices(this IServiceCollection services, IConfiguration configuration) { services.AddDbContext<ViSiContext>(); services.TryAddSingleton<ResourceMapper>(); services.TryAddScoped<ISearchRepository, ViSiRepository>(); services.Configure<DbOptions>(configuration.GetSection(nameof(DbOptions))); return services; } }
2. Create your Facade plugin
First, build your project
Find the resulting dll and copy that to the
plugins
folder in the working directory of your Firely Server
Note
If your Firely Server working directory does not contain a plugins folder yet, you can create one. Within it, you can create subfolders, which can be useful if you work with multiple plugins.
You can also configure the name and location of this folder with the PipelineOptions.PluginDirectory
setting
in the appsettings file.
3. Configure your Firely Server Facade
Create an appsettings.instance.json file in your Firely Server working directory.
Tip
See Firely Server settings for more information about the hierarchy of the
appsettings(.*).json
files and the settings that can be configured.Add a setting for the connectionstring to the appsettings.instance.json file:
"DbOptions" : { "ConnectionString" : "<paste the connection string to your ViSi database here>" },
Add the
SupportedInteractions
section. You can look at Enable or disable interactions to check what this section should contain. For now you only need"WholeSystemInteractions": "capabilities"
,"InstanceLevelInteractions": "read"
and"TypeLevelInteractions": "search"
:"SupportedInteractions": { "InstanceLevelInteractions": "read", "TypeLevelInteractions": "search", "WholeSystemInteractions": "capabilities" },
Add the
SupportedModel
section to indicate which resource types and search parameters you support in your Facade implementation:"SupportedModel": { "RestrictToResources": [ "Patient" ], "RestrictToSearchParameters": ["Resource._id", "StructureDefinition.url"] },
You will need to add your repository to the Firely Server pipeline, and remove the existing repository implementations. The standard settings for the pipeline configuration can be found in the appsettings.default.json file, or see Configure the pipeline for an example.
Copy the whole PipelineOptions section to your appsettings.instance.json file (both
/
and/administration
)To the
Include
part of the branch with"Path":"/"
add your namespace, and remove the Vonk.Repository.* lines from it:{ "Path": "/", "Include": [ "Vonk.Core", "Vonk.Fhir.R3", "Vonk.Subscriptions", "Vonk.Smart", "Vonk.UI.Demo", "ViSiProject" // fill in (a prefix of) the namespace of your project here ] },
Remove the PipelineOptions from appsettings.default.json, because of the warning mentioned on the Hierarchy of settings.
Test your work
Proceed to the next section to test your Facade, and for some helpful tips about debugging your code.
Debugging the Facade
Start your Firely Server
Note
If this is your first startup of Firely Server, it will take a while to load in all of the specification files.
You can inspect the console log to see if the pipeline is configured to include your repository. See Detailed logging of loading plugins for more details.
To test your Facade, open Postman, or Fiddler, or use curl to request
GET http://localhost:4080/metadata
The resulting CapabilityStatement should list only the Patient resource type in its .rest.resource field, and – among others – the _id search parameter in the .rest.searchParam field.
Now you can test that searching patients by
_id
works:GET http://localhost:4080/Patient?_id=1
Requesting the resource ‘normally’ should automatically work as well:GET http://localhost:4080/Patient/1
Important
If it works, congratulations! You now have a Firely Server Facade running!
Testing during implementation
Follow these steps if you want to test your work during the implementation phase without having to build, copy and start Firely Server each time, or with the ability to set break points in your code and debugging it:
In the project properties, click on the
Build
tab.Set the
Output path
to your Firely Server plugins directory.Go to the
Debug
tab and setLaunch
toExecutable
.Point the
Executable
field to your dotnet.exe.Set the
Application arguments
to<your-Firely-Server-working-directory>/Firely.Server.dll
.Set the
Working directory
to your Firely Server working directory.
Now, whenever you click to start debugging, Firely Server will start from your project and your project dll will be automatically built to the Firely Server plugins directory.
Next part of the exercise
You can proceed to the next section to add support for Observations as well.
Finalizing your project
Add support for the ViSiBloodPressure Observations
First, follow similar steps as above to support ViSiBloodPressure:
Add a mapping method in the ResourceMapper class to map from a ViSiBloodPressure to a FHIR Observation resource, and return that as an IResource.
Create a BloodPressureQuery query class.
Add a BPQueryFactory extending
RelationalQueryFactory<ViSiBloodPressure, BloodPressureQuery>
.Implement support for the
_id
parameter by overridingpublic virtual BloodPressureQuery AddValueFilter(string parameterName, TokenValue value)
.Add the Observation type to the
SupportedModel
section in Firely Server’s appsettings.instance.json:"RestrictToResources": [ "Patient", "Observation" ]
When you have completed these steps, build your project again and copy the dll to your Firely Server plugins folder.
After you (re)start Firely Server, you will be able to request an Observation through your Facade:
GET http://localhost:4080/Observation?_id=1
or GET http://localhost:4080/Observation/1
.
Since you do not always want to request Observations by their technical id, but more often might want to request Observations from a specific patient, the next part will describe implementing support that. The Patient resource is referenced by the Observation in its subject field. The corresponding search parameter is either subject or patient.
Add support for chaining
To add support for searching on Observation?subject:Patient._id
we need to override the AddValueFilter
overload receiving a ReferenceToValue
parameter in the query factory for BloodPressure (BPQueryFactory).
The ReferenceToValue
type contains the possible Targets
for the chain search parameter as parsed from the query string.
We are currently interested only on the Patient type so we can restrict the implementation to that target.
The ReferenceToValue
type also has an extension method CreateQuery
that expects an implementation of the RelationalQueryFactory
of the referenced target. This will generate the query to obtain the resources referenced by it.
Searching on chained parameters involves the following steps:
Retrieve all patient ids based on the chained parameter. You can use the
ReferenceToValue.CreateQuery
extension method to get the query and run the query with itsExecute
method.Create a
PredicateQuery
with the condition thatViSiBloodPressure.PatientId
is included in the ids retrieved at the previous step.The final code should look similar to this:
public override BloodPressureQuery AddValueFilter(string parameterName, ReferenceToValue value) { if (parameterName == "subject" && value.Targets.Contains("Patient")) { var patientQuery = value.CreateQuery(new PatientQueryFactory(OnContext)); var patIds = patientQuery.Execute(OnContext).Select(p => p.Id); return PredicateQuery(bp => patIds.Contains(bp.PatientId)); } return base.AddValueFilter(parameterName, value); }Note
patIds is of type IQueryable, so the resulting BloodPressureQuery will still be executed as a single command to the database.
Add support for the
Observation.subject
search parameter in the Firely Server appsettings similar to how we did it for_id
.
At this point you should be able to search for GET http://localhost:4080/Observation?subject:Patient._id=1
Add support for reverse chaining
Adding support for Patient?_has:Observation:subject:_id=1
is similar. You just need to use the AddValueFilter
overload receiving a ReferenceFromValue
.
The ReferenceFromValue
type has a Source
property filled in with the source of the search parameter. It also has an extension method CreateQuery
that given the corresponding RelationalQueryFactory
implementation can generate
the query to obtain resources referenced by the reverse chaining.
So you can add reverse chaining with the following code:
public class PatientQueryFactory
{
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);
}
}
Note
The reverse chaining example above uses the Patient resource type as its base, so you will need to implement this in your PatientQueryFactory.
Now you can test if reverse chaining works: http://localhost:4080/Patient?_has:Observation:subject:_id=1
Get the goodies
At this point you get out of the box support for _include
and _revinclude
(:iterate
as well), and combinations of search parameters.
You can test the following scenarios:
_include
:http://localhost:4080/Observation?_include=Observation:subject
_revinclude
:http://localhost:4080/Patient?_revinclude=Observation:subject
combinations of the above
Adding a custom SearchParameter
Your Firely Server will load the standard parameters from the
specification on first startup, so the _id
SearchParameter from the exercise is already known to Firely Server, as well as any of
the other standard search parameters for the resource types.
If you want to implement support for a custom search parameter, you will need to have the definition of that in the form of a SearchParameter resource, and add it to your Firely Server. The Configure Search Parameters section describes how to do that.
Of course you will also need to implement the correct AddValueFilter method in your <resourcetype>QueryFactory
to handle
the parameter correctly, as is done for the _id parameter in the exercise.
The end?
This concludes the exercise. An example Github repository contains the completed exercise.
Please feel free to try out more options, and ask for help if you get stuck!
The next topic will show you how to enable Create, Update and Delete interactions.
Postscript
If your resource is split across multiple tables in the database, you’ll need to make use of .Include()
to have EF load the dependent table. To do so in Firely Server, override the GetEntitySet() method in your RelationalQuery class, for example
protected override IQueryable<ViSiPatient> GetEntitySet(DbContext dbContext)
{
// load the dependent Address table
return dbContext.Set<ViSiPatient>().Include(p => p.Address).AsNoTracking();
}
Enable changes to the repository
In Exercise: Build your first Facade you have created read and search support for a Firely Server Facade on top of an existing database. The next part will walk you through enabling create, update and delete support. This will be done in three steps:
Map the FHIR resource to the database model;
Implement the IResourceChangeRepository;
Indicate support in the appsettings file
1. Map the FHIR data to the model
For both the Create and Update interactions, we need to map our incoming FHIR data to the ViSi model. To do that, we will add new mapping methods to our ResourceMapper class.
Add a method called
MapVisiPatient
to the ResourceMapper class, that takes a FHIRPatient
and returns aViSiPatient
.Implement the mapping from the FHIR object to the ViSiPatient model:
public ViSiPatient MapViSiPatient(IResource source) { var fhirPatient = source.ToPoco<Patient>(); var visiPatient = new ViSiPatient(); if (source.Id != null) { if (!int.TryParse(source.Id, out int id)) throw new VonkRepositoryException("Id needs to be integer to map resource"); visiPatient.Id = id; } visiPatient.PatientNumber = fhirPatient.Identifier.Find(i => (i.System == "http://mycompany.org/patientnumber")).Value; // etc. return visiPatient; }
Where it says ‘etc.’, fill in the rest of the code to map the data to required fields of the database, and any other fields you have data for.
2. Implement the IResourceChangeRepository
You are going to implement a repository that handles changes to your database. The interface for this is called IResourceChangeRepository
, which
can be found in Vonk.Core.Repository
.
Add a new class
ViSiChangeRepository
to the project, that implements the IResourceChangeRepository:public class ViSiChangeRepository : IResourceChangeRepository
Choose to implement the interface, so the required methods are added to the class.
Just like with the search repository, you will need your DbContext to query on, and the ResourceMapper to perform the mapping of the incoming data to your proprietary model.
So put all of that in the constructor:
private readonly ViSiContext _visiContext; private readonly ResourceMapper _resourceMapper; public ViSiChangeRepository(ViSiContext visiContext, ResourceMapper resourceMapper) { _visiContext = visiContext; _resourceMapper = resourceMapper; }
Implementing Create
Now implement the Create method with a switch on resource type, so you can add other resource types later:
public async Task<IResource> Create(IResource input) { switch (input.Type) { case "Patient": return await CreatePatient(input); default: throw new NotImplementedException($"ResourceType {input.Type} is not supported."); } }
As you can see, we have deferred the work to a CreatePatient method, which we also need to implement. This method will add the new resource to the collection, and save the changes to the database:
private async Task<IResource> CreatePatient(IResource input) { var visiPatient = _resourceMapper.MapViSiPatient(input); await _visiContext.Patient.AddAsync(visiPatient); await _visiContext.SaveChangesAsync(); // return the new resource as it was stored by this server return _resourceMapper.MapPatient(_visiContext.Patient.Last()); }
For the
Create
andUpdate
methods, you will also need to implement theNewId
andNewVersion
methods, because Firely Server will call them. For theNewId
method, we will return null, since our ViSi database does not allow us to create our own index value. Since our ViSi repository does not handle versions, we will let theNewVersion
method return null as well:public string NewId(string resourceType) { return null; } public string NewVersion(string resourceType, string resourceId) { return null; }
Note
For the ViSi repository we’re using a null value, but you can implement this method any way that’s useful for your own repository. The public Firely Server for example generates a GUID in these methods.
At this point you can skip ahead to 3. Configure the service and Firely Server, if you want to try and create a new patient in the ViSi database.
Tip
This is easiest to test if you retrieve an existing resource from the database first with your HTTP tool. Then change some of the data in the resulting JSON or XML, and send that back to your Facade.
Implementing Update
Implementing the Update
method can be done like the Create
, with a switch on resource type, and instead of adding
a resource to the collection, you will update the collection:
private async Task<IResource> UpdatePatient(ResourceKey original, IResource update)
{
try
{
var visiPatient = _resourceMapper.MapViSiPatient(update);
var result = _visiContext.Patient.Update(visiPatient);
await _visiContext.SaveChangesAsync();
return _resourceMapper.MapPatient(result.Entity);
}
catch (Exception ex)
{
throw new VonkRepositoryException($"Error on update of {original} to {update.Key()}", ex);
}
}
Implementing Delete
Deleting a resource from the collection is done by first looking up the corresponding resource, and then removing it from the collection. Note that the database used for this exercise cannot process the deletion of the Patient when there are still related Observations in the BloodPressure table, so we need to remove them as well or choose to throw an error.
First, create a switch on resource type in the main
Delete
method again.Implement the
DeletePatient
:private async Task<IResource> DeletePatient(ResourceKey toDelete) { int toDelete_id = int.Parse(toDelete.ResourceId); var visiPatient = _visiContext.Patient.Find(toDelete_id); var bpEntries = _visiContext.BloodPressure.Where(bp => bp.PatientId == toDelete_id); var result = _resourceMapper.MapPatient(visiPatient); try { _visiContext.BloodPressure.RemoveRange(bpEntries); _visiContext.Patient.Remove(visiPatient); await _visiContext.SaveChangesAsync(); } catch (Exception ex) { throw new VonkRepositoryException($"Error on deleting Patient with Id {toDelete_id}", ex); } return result; }
3. Configure the service and Firely Server
Just like with the search repository, you will need to add your change repository as service to the pipeline. Also, you will need to indicate support for the CRUD interactions in your Firely Server appsettings.
In your project, go to the ViSiConfiguration class, and add this line to add an IResourceChangeRepository to the pipeline:
services.TryAddScoped<IResourceChangeRepository, ViSiChangeRepository>();
Add support for the interactions to the SupportedModel section of the Firely Server appsettings:
"SupportedInteractions": { "InstanceLevelInteractions": "read, update, delete", "TypeLevelInteractions": "search, create", "WholeSystemInteractions": "capabilities, search" },
Adjust
PipelineOptions.Branches.Include
fromVonk.Core.Operations.Crud.ReadConfiguration
to``Vonk.Core.Operations
include all operations, includingCreate
.
You can now build your project, copy the dll to the Firely Server plugins folder and run Firely Server to test the new interactions on your Facade.
The end?
This concludes the second exercise. Please feel free to try out more options, and ask for help if you get stuck!
The next topic will show you how to integrate Access Control.
Firely Server Reference Documentation
The reference documentation lists the available plugins for configuring the pipeline, and the public programming API of Firely Server for building Plugins and Facades.
Plugins available for Firely Server
Infrastructural plugins
- Name
Scheduler
- Configuration
Vonk.Core.Quartz.QuartzConfiguration
- License token
- Order
10
- Description
Registers a scheduler that can run jobs periodically. You can use this yourself, but with care (you don’t want jobs slowing down the server):
Implement a
Quartz.IJob
, let’s say with class MyJob {…}Have the
Quartz.IScheduler
injectedCall
IScheduler.StartJob<MyJob>(TimeSpan runInterval, CancellationToken cancellationToken)
- Name
Maintenance
- Configuration
Vonk.Core.Infra.MaintenanceConfiguration
- License token
- Order
20
- Description
Periodically cleans the indexed values for deleted or superceded resources from the database.
- Name
License
- Configuration
Vonk.Core.Licensing.LicenseConfiguration
- License token
- Order
120
- Description
Registers the LicenseService that checks for a valid license. Without this plugin Firely Server does not work.
- Name
Serialization
- Configuration
Vonk.Core.Serialization.SerializationConfiguration
- License token
- Order
130
- Description
Registers an implementation for the
ISerializationService
andISerializationSupport
interfaces and actual serializers and parsers for JSON and XML.
- Name
Pluggability
- Configuration
Vonk.Core.Pluggability.PluggabilityConfiguration
- License token
- Order
150
- Description
Registers services to dynamically build the
IModelService
using registeredIModelContributor
implementations.
- Name
Http to Vonk
- Configuration
Vonk.Core.Context.Http.HttpToVonkConfiguration
- License token
- Order
1110
- Description
Builds an IVonkContext out of the HttpContext. You can only access the IVonkContext in the pipeline from plugins that have a higher order.
- Name
Vonk to Http
- Configuration
Vonk.Core.Context.Http.VonkToHttpConfiguration
- License token
- Order
1120
- Description
Translates the response in the IVonkContext to a response on the HttpContext. It honors the value of the prefer header if present. It also adds the VonkExceptionMiddleware to the pipeline as a last resort for catching exceptions.
- Name
Formatter
- Configuration
Vonk.Core.Context.Format.FormatConfiguration
- License token
- Order
1130
- Description
Registers an implementation of IFormatter that can write the
IVonkContext.Response.Payload
to the response body in the requested format. Does not add a processor to the pipeline.
- Name
Long running tasks
- Configuration
Vonk.Core.Infra.LongRunning.LongRunningConfiguration
- License token
- Order
1170
- Description
If Vonk processes a task that could lead to inconsistent output, all other requests are rejected by this plugin. Long running tasks are e.g. the Import of Conformance Resources and Re-indexing for new or changed SearchParameters.
- Name
Compartments
- Configuration
Vonk.Core.Context.Features.CompartmentsConfiguration
- License token
- Order
1210
- Description
Recognizes a compartment in a compartment search on system or type level (see Search). It is added as a feature of type
ICompartment
to theIVonkContext.Features
collection, to be used by Search later on. This ICompartment feature will limit all queries to within the specified compartment.
- Name
Supported Interactions
- Configuration
Vonk.Core.Context.Guards.SupportedInteractionsConfiguration
- License token
- Order
1220
- Description
Blocks interactions that are not listed as supported.
- Options
SupportedInteractions
, see Enable or disable interactions.
- Name
Size Limits
- Configuration
Vonk.Core.Context.Guards.SizeLimitsConfiguration
- License token
- Order
1225
- Description
Rejects bodies that are too large and bundles with too many entries.
- Options
SizeLimits
, see Protect against large input
- Name
Url mapping
- Configuration
Vonk.Core.Context.UrlMapping.UrlMappingConfiguration
- License token
- Order
1235
- Description
In a resource in the request, urls pointing to this instance of Firely Server are made relative. In a resource in the response, relative urls are made absolute, by adding the base url of the server. This way the server can be addressed in multiple ways (e.g. http://intranet.acme.com/fhir and https://fhir.acme.com) and still provide correct absolute urls.
- Name
Default Shapes
- Configuration
Vonk.Core.Context.Guards.DefaultShapesConfiguration
- License token
- Order
4110
- Description
If no sort order is given for a search,
_lastUpdated:asc
is added. If no count is given for a search,_count=<default count>
is added.- Options
BundleOptions.DefaultCount
, see Search and History.
Support for different FHIR versions
- Name
FHIR R3
- Configuration
Vonk.Fhir.R3.FhirR3Configuration
- License token
- Order
100
- Description
Registers services to support FHIR STU3 (or R3).
- Name
FHIR R3 Specification
- Configuration
Vonk.Fhir.R3.FhirR3SpecificationConfiguration
- License token
- Order
112
- Description
Registers an
Hl7.Fhir.Specification.IStructureDefinitionSummaryProvider
for FHIR STU3 (or R3).
- Name
FHIR R4
- Configuration
Vonk.Fhir.R4.FhirR4Configuration
- License token
- Order
101
- Description
Registers services to support FHIR R4.
- Name
FHIR R4 Specification
- Configuration
Vonk.Fhir.R4.FhirR4SpecificationConfiguration
- License token
- Order
112
- Description
Registers an
Hl7.Fhir.Specification.IStructureDefinitionSummaryProvider
for FHIR R4.
FHIR RESTful interactions
- Name
Read
- Configuration
Vonk.Core.Operations.Crud.ReadConfiguration
- License token
- Order
4410
- Description
Implements FHIR instance read. It will return the Resource that matches the id and the FHIR version. If a Resource with matching id is found with another FHIR version you are notified.
- Name
Create
- Configuration
Vonk.Core.Operations.Crud.CreateConfiguration
- License token
- Order
4420
- Description
Implements FHIR type create.
- Name
Update
- Configuration
Vonk.Core.Operations.Crud.UpdateConfiguration
- License token
- Order
4430
- Description
Implements FHIR instance update, with support for ‘upsert’: creating a Resource with a pre-assigned id. Note that id’s must be unique across FHIR versions.
- Name
Patch
- Configuration
Vonk.Core.Operations.Crud.FhirPatchConfiguration
- License token
- Order
4433
- Description
Implements FHIR instance patch, as specified by FHIR Patch.
- Name
Delete
- Configuration
Vonk.Core.Operations.Crud.DeleteConfiguration
- License token
- Order
4440
- Description
Implements FHIR instance delete. Since id’s in Firely Server must be unique across FHIR versions, the delete is issued on the provided id, regardless of the FHIR version.
- Name
Search
- Configuration
Vonk.Core.Operations.Search.SearchConfiguration
- License token
- Description
Implements FHIR Search on system and type level. For data access it uses the registered implementation of ISearchRepository, which can be any of the implementations provided by Firely Server or an implementation provided by a Facade plugin. The implementations provided by Firely Server also require the Index plugin to extract searchparameter values from the resources.
- Order
4220
- Options
AdministrationImportOptions
, see SearchParameters and other Conformance Resources, for available SearchparametersSupportedModel.RestrictToSearchParameters
, see Restrict supported resources and SearchParameters for available SearchparametersBundleOptions
, see Search and History, for number of returned results
See ISearchRepository and Firely Server Facade.
- Name
Search support
- Configuration
Vonk.Core.Repository.RepositorySearchSupportConfiguration
- License token
- Order
140
- Description
Registers services required for Search. It is automatically registered by Search.
- Name
Index
- Configuration
Vonk.Core.Repository.RepositoryIndexSupportConfiguration
- License token
- Order
141
- Description
Extracts values matching Searchparameters from resources, so they can be searched on.
- Name
Include
- Configuration
Vonk.Core.Operations.Search.IncludeConfiguration
- License token
- Order
4210
- Description
Implements
_include
and_revinclude
. This acts on the result bundle of a search. Therefore it also works out of the box for Facade implementations, provided that the Facade implements support for the reference Searchparameters that are used in the _(rev)include.
- Name
Elements
- Configuration
Vonk.Core.Context.Elements.ElementsConfiguration
- License token
- Order
1240
- Description
Applies the
_elements
parameter to the Resource that is in the response (single resource or bundle).
- Name
Summary
- Configuration
Vonk.Core.Context.Elements.SummaryConfiguration
- License token
- Order
1240
- Description
Applies the
_summary
parameter to the Resource that is in the response (single resource or bundle).
- Name
History
- Configuration
Vonk.Core.Operations.History.HistoryConfiguration
- License token
- Order
4610
- Description
Implements
_history
on system, type and instance level.- Options
BundleOptions
, see Search and History
- Name
Version Read
- Configuration
Vonk.Core.Operations.History.VersionReadConfiguration
- License token
- Order
4620
- Description
Implements reading a specific version of a resource (
<base>/Patient/123/_history/v3
).
- Name
Capability
- Configuration
Vonk.Core.Operations.Capability.CapabilityConfiguration
- License token
- Order
4120
- Description
Provides the CapabilityStatement on the
<base>/metadata
endpoint. The CapabilityStatement is tailored to the FHIR version of the request. The CapabilityStatement is built dynamically by visiting all the registered implementations of ICapabilityStatementContributor, see Capabilities.
- Name
Conditional Create
- Configuration
Vonk.Core.Operations.ConditionalCrud.ConditionalCreateConfiguration
- License token
- Order
4510
- Description
Implements FHIR conditional create.
- Name
Conditional Update
- Configuration
Vonk.Core.Operations.ConditionalCrud.ConditionalUpdateConfiguration
- License token
- Order
4520
- Description
Implements FHIR conditional update.
- Name
Conditional Delete
- Configuration
Vonk.Core.Operations.ConditionalCrud.ConditionalDeleteConfiguration
- License token
- Order
4530
- Description
Implements FHIR conditional delete.
- Options
FhirCapabilities.ConditionalDeleteOptions
, see FHIR Capabilities
- Name
Validation
- Configuration
Vonk.Core.Operations.Validation.ValidationConfiguration
- License token
- Order
4000
- Description
Implements FHIR $validate on type and instance level for POST:
POST <base>/Patient/$validate
orPOST <base>/Patient/123/$validate
.
- Name
Instance Validation
- Configuration
Vonk.Core.Operations.Validation.InstanceValidationConfiguration
- License token
- Order
4840
- Description
Implements FHIR $validate on instance level for GET:
GET <base>/Patient/123/$validate
- Name
Structural Validation
- Configuration
Vonk.Core.Operations.Validation.StructuralValidationConfiguration
- License token
- Order
1227
- Description
Validates the structure of resources sent to Firely Server (is it valid FHIR JSON or XML?).
- Name
Prevalidation
- Configuration
Vonk.Core.Operations.Validation.PreValidationConfiguration
- License token
- Order
1228
- Description
Validates resources sent to Firely Server against their stated profile compliance (in Resource.meta.profile). The strictness of the validation is controlled by the options.
- Options
Validation
, see Validation
- Name
Profile filter
- Configuration
Vonk.Core.Operations.Validation.ProfileFilterConfiguration
- License token
- Order
4310
- Description
Blocks resources that do not conform to a list of profiles.
- Options
Validation.AllowedProfiles
, see Validation
- Name
Meta
- Configuration
Vonk.Core.Operations.MetaOperation.MetaConfiguration
- License token
- Order
5180
- Description
Implements FHIR $meta on instance level.
- Name
Meta Add
- Configuration
Vonk.Core.Operations.MetaOperation.MetaAddConfiguration
- License token
- Order
5190
- Description
Implements FHIR $meta-add on instance level.
- Name
Meta Delete
- Configuration
Vonk.Core.Operations.MetaOperation.MetaDeleteConfiguration
- License token
- Order
5195
- Description
Implements FHIR $meta-delete on instance level.
- Name
Snapshot Generation
- Configuration
Vonk.Core.Operations.SnapshotGeneration.SnapshotGenerationConfiguration
- License token
- Order
4850
- Description
Implements FHIR $snapshot on a type level:
POST <base>/administration/StructureDefinition/$snapshot
.
- Name
Batch
- Configuration
Vonk.Core.Operations.Transaction.FhirBatchConfiguration
- License token
- Order
3110
- Description
Processes a batch Bundle by sending each entry through the rest of the processing pipeline and gathering the results.
- Options
SizeLimits
, see Protect against large input
- Name
Transaction
- Configuration
Vonk.Core.Operations.Transaction.FhirTransactionConfiguration
- License token
- Order
3120
- Description
Process a transaction Bundle by sending each entry through the rest of the processing pipeline and gathering the results. Different from Batch, Transaction succeeds or fails as a whole. Transaction requires an implementation of
Vonk.Core.Repository.IRepoTransactionService
for transaction support by the underlying repository. The SQL Server and SQLite implementations provides a real one, whereas the MongoDb provides a simulated implementation, to allow you to experiment with transactions on MongoDb.- Options
SizeLimits
, see ValidationRepository
, see Repository
- Name
LastN
- Configuration
Vonk.Plugin.LastN.LastNConfiguration
- License token
- Order
5007
- Description
Implements FHIR $lastn on Observation resources.
Terminology
- Name
CodeSystem Lookup
- Configuration
Vonk.Plugins.Terminology.[R3|R4|R5].CodeSystemLookupConfiguration
- License token
- Order
5110
- Description
Implements FHIR $lookup on type level requests:
POST <base>/administration/CodeSystem/$lookup
orGET <base>/administration/CodeSystem/$lookup?...
- Name
CodeSystem FindMatches / Compose
- Configuration
Vonk.Plugins.Terminology.CodeSystemFindMatchesConfiguration
- License token
- Order
5220
- Description
Implements FHIR $compose on type level requests:
POST <base>/administration/CodeSystem/$find-matches``and on instance level requests: ``POST <base>/administration/CodeSystem/[id]/$find-matches
orGET <base>/administration/CodeSystem/[id]/$find-matches?...
- Name
ValueSet Validate Code
- Configuration
Vonk.Plugins.Terminology.ValueSetValidateCodeConfiguration
- License token
- Order
5120
- Description
Implements FHIR $validate-code on type level requests:
POST <base>/administration/ValueSet/$validate-code
and instance level requests:GET <base>/administration/ValueSet/[id]/$validate-code?...
andPOST <base>/administration/ValueSet/[id]/$validate-code
- Name
ValueSet Expand
- Configuration
Vonk.Plugins.Terminology.ValueSetExpandConfiguration
- License token
- Order
5140
- Description
Implements FHIR $expand on instance level requests:
GET <base>/administration/ValueSet/[id]/$expand?...
andPOST <base>/administration/ValueSet/[id]/$expand
and on type level requests:POST <base>/administration/ValueSet/$expand
.
- Name
ConceptMap Translate
- Configuration
Vonk.Plugins.Terminology.ConceptMapTranslateConfiguration
- License token
- Order
5260
- Description
Implements FHIR $translate on instance level requests:
GET <base>/administration/ConceptMap/[id]/$translate?...
andPOST <base>/administration/ValueSet/[id]/$translate
and on type level requests:POST <base>/administration/ConceptMap/$translate
.
- Name
CodeSystem Subsumes
- Configuration
Vonk.Plugins.Terminology.CodeSystemSubsumesConfiguration
- License token
- Order
5280
- Description
Implements FHIR $subsumes on instance level requests:
GET <base>/administration/CodeSystem/[id]/$subsumes?...
and on type level requests:POST <base>/administration/CodeSystem/$subsumes
orGET <base>/administration/CodeSystem/$subsumes?...
- Name
CodeSystem Closure
- Configuration
Vonk.Plugins.Terminology.CodeSystemClosureConfiguration
- License token
- Order
5300
- Description
Implements FHIR $closure on system level requests:
POST <base>/administration/$closure
SMART on FHIR
- Name
SMART on FHIR
- Configuration
Vonk.Smart.SmartConfiguration.SmartConfiguration
- License token
- Order
2000
- Description
Implements SMART on FHIR authentication and authorization, see Access control and SMART.
Subscriptions
- Name
Subscriptions
- Configuration
Vonk.Subscriptions.SubscriptionConfiguration.SubscriptionConfiguration
- License token
- Order
3200
- Description
Implements the FHIR Subscriptions framework, see Subscriptions.
Auditing
- Name
Username log
- Configuration
Vonk.Plugin.Audit.UsernameLoggingConfiguration
- License token
- Order
2010
- Description
Makes the user id and name from the JWT token (if present) available for logging. See Auditing for more info.
- Name
Audit logging for transactions
- Configuration
Vonk.Plugin.Audit.AuditTransactionConfiguration
- License token
- Order
3100
- Description
Logs requests and responses for transactions to a file. See Auditing for more info.
- Name
Audit log
- Configuration
Vonk.Plugin.Audit.AuditConfiguration
- License token
- Order
3150
- Description
Logs requests and responses to a file. See Auditing for more info.
- Name
AuditEvent logging for transactions
- Configuration
Vonk.Plugin.Audit.AuditEventTransactionConfiguration
- License token
- Order
3160
- Description
Logs requests and responses for transactions to a file. See Auditing for more info.
- Name
AuditEvent logging
- Configuration
Vonk.Plugin.Audit.AuditEventConfiguration
- License token
- Order
3170
- Description
Logs requests and responses to a file. See Auditing for more info.
Demo UI
- Name
Demo UI
- Configuration
Vonk.UI.Demo.DemoUIConfiguration.DemoUIConfiguration
- License token
- Order
800
- Description
Provides the landing page that you see when you request the base url from a browser. If you want to provide your own landing page, replace this plugin with your own. There is an example of that, see Firely Server Plugin example - Create a new landing page.
Documents
- Name
Document generation
- Configuration
Vonk.Plugins.DocumentOperation.DocumentOperationConfiguration
- License token
- Order
4900
- Description
Implements FHIR $document :
POST <base>/Composition/$document
orGET <base>/Composition/[id]/$document
- Code
- Name
Document signing
- Configuration
Vonk.Plugins.SignatureService.SignatureConfiguration
- License token
- Order
4899
- Description
Signs a document generated by $document.
Conversion
- Name
Format conversion
- Configuration
Vonk.Plugins.ConvertOperation.ConvertOperationConfiguration
- License token
- Order
4600
- Description
Implements FHIR $convert :
POST <base>/$convert
to convert between JSON and XML representation.
Binary
- Name
Binary wrapper (Encode)
- Configuration
Vonk.Plugins.BinaryWrapper.BinaryEncodeConfiguration
- License token
- Order
1112
- Description
Wraps an incoming binary format in a Binary resource for further processing by the pipeline.
- Settings
"Vonk.Plugin.BinaryWrapper":{ "RestrictToMimeTypes": ["application/pdf", "text/plain", "image/png", "image/jpeg"] },
- Name
Binary wrapper (Decode)
- Configuration
Vonk.Plugins.BinaryWrapper.BinaryDecodeConfiguration
- License token
- Order
1122
- Description
Implements
GET <base>/Binary/<id>
, retrieve back the Binary resource in its native format.
Transformation and mapping
- Name
FHIR Mapper (Transform)
- Configuration
Vonk.Plugins.Transform.TransformConfiguration
- License token
- Order
4560
- Description
Implements FHIR $transform :
POST <base>/administration/StructureMap/[id]/$transform
. See fhir_mapper_docs:mappingengine_index.- Name
FHIR Mapper (Convert)
- Configuration
Vonk.Plugin.MappingToStructureMap.MappingToStructureMapConfiguration
- License token
- Order
4550
- Description
Implements FHIR $convert :
POST <base>/$convert
to convert between FHIR Mapping Language and its StructureMap representation.
Repository implementations
- Name
Memory Repository
- Configuration
Vonk.Repository.MemoryConfiguration
- license token
- Order
210
- Description
Implements a repository in working memory that fully supports all of the capabilities of Firely Server. This implementation is mainly used for unittesting.
- Name
Memory Administration Repository
- Configuration
Vonk.Repository.MemoryAdministrationConfiguration
- license token
- Order
211
- Description
Implements a repository in working memory for the Administration API. This implementation is mainly used for unittesting.
- Name
MongoDb Repository
- Configuration
Vonk.Repository.MongoDbConfiguration
- license token
- Order
230
- Description
Implements a repository in MongoDb that fully supports all of the capabilities of Firely Server, except Transactions.
- Name
MongoDb Administration Repository
- Configuration
Vonk.Repository.MemoryAdministrationConfiguration
- license token
- Order
231
- Description
Implements a repository in MongoDb for the Administration API.
- Name
SQLite Repository
- Configuration
Vonk.Repository.SqliteConfiguration
- license token
- Order
240
- Description
Implements a repository in SQLite that fully supports all of the capabilities of Firely Server.
- Name
SQLite Administration Repository
- Configuration
Vonk.Repository.SqliteAdministrationConfiguration
- license token
- Order
241
- Description
Implements a repository in SQLite for the Administration API.
- Name
SQL Server Repository
- Configuration
Vonk.Repository.SqlConfiguration
- license token
- Order
220
- Description
Implements a repository in SQL Server that fully supports all of the capabilities of Firely Server.
- Name
SQL Server Administration Repository
- Configuration
Vonk.Repository.SqlAdministrationConfiguration
- license token
- Order
221
- Description
Implements a repository in SQL Server for the Administration API.
Administration API
- Name
Administration API
- Configuration
Vonk.Administration.Api.AdministrationOperationConfiguration
- license token
- Order
1160
- Description
Sets up a sequence of plugins for the Administration API. Administration API is different from general plugins since it branches off of the regular processing pipeline and sets up a second pipeline for the /administration endpoint.
- Name
Fhir STU3 Administration services
- Configuration
Vonk.Administration.FhirR3.RepositoryConfigurationR3
- license token
- Order
4310
- Description
Implements support services to work with FHIR STU3 conformance resources in the Administration API.
- Name
Fhir R4 Administration services
- Configuration
Vonk.Administration.FhirR4.RepositoryConfigurationR4
- license token
- Order
4310
- Description
Implements support services to work with FHIR R4 conformance resources in the Administration API.
Bulk Data
- Name
Bulk Data Export
- Configuration
Vonk.Plugin.BulkDataExport
- license token
http://fire.ly/vonk/plugins/bulk-data-export
- Order
5005
- Description
Request an export of bulk data sets. See Bulk Data Export.
- Name
Patient everything
- Configuration
Vonk.Plugin.PatientEverything
- license token
http://fire.ly/vonk/plugins/patient-everything
- Order
5006
- Description
Request a Patient record. See Patient $everything.
Important classes and interfaces
If you want to develop a plugin for Firely Server, there are a couple of classes that you will probably interact with. This page lists those classes, with an explanation of each.
IResource
- namespace
Vonk.Core.Common
- purpose
IResource is the abstraction for all FHIR resources in Firely Server. It is used in the request and the response, and thereby all through the pipeline. It allows you to program against resources in different Fhir.NET API (the Resource class is defined in each version separately), as well as against resources that do not even have a POCO implementation.
/// <summary>
/// Abstraction of a resource in some format. Specifies the properties of a Resource that Firely Server needs to read and maintain.
/// <para>Future: may be extended with Tags and Labels.</para>
/// </summary>
public interface IResource : ISourceNode
{
/// <summary>
/// Type of resource, e.g. Patient or AllergyIntolerance.
/// </summary>
string Type { get; }
/// <summary>
/// Logical identity of resource, e.g. 'example' or 'e3f5b0b8-4570-4e4c-b597-e6523aff3a19'. Does not contain the resourcetype.
/// Refers to Resource.id
/// IResource is immutable, so to update this, use resourceWithNewId = this.SetId(), from IResourceExtensions.
/// In the context of a repository, consider IResourceChangeRepository.EnsureMeta().
/// </summary>
string Id { get; }
/// <summary>
/// Version of resource. Refers to Resource.meta.versionId.
/// IResource is immutable, so to update this, use resourceWithNewVersion = this.SetVersion(), from IResourceExtensions.
/// In the context of a repository, consider IResourceChangeRepository.EnsureMeta().
/// </summary>
string Version { get; }
/// <summary>
/// Model that the resource was defined in.
/// Common models are the different versions of FHIR, defined in <see cref="VonkConstants.Model"/>
/// </summary>
string InformationModel { get; }
/// <summary>
/// When was the resource last updated?
/// Refers to Resource.meta.lastUpdated.
/// IResource is immutable, so to update this, use resourceWithNewLastUpdated = this.SetLastUpdated(DateTimeOffset) from IResourceExtensions.
/// In the context of a repository, consider IResourceChangeRepository.EnsureMeta().
/// </summary>
DateTimeOffset? LastUpdated { get; }
/// <summary>
/// Is this a contained resource, or a container resource?
/// A resource is a container resource if it is not contained. Even if it has no contained resources embedded.
/// </summary>
ResourceContained Contained { get; }
/// <summary>
/// Direct access to contained resources, if any. Prefer to return an empty list otherwise.
/// Refers to DomainResource.contained.
/// </summary>
IEnumerable<IResource> ContainedResources { get; }
}
If you work with a POCO, you can use the extension method ToIResource() from Vonk.Fhir.R3 to adapt it to an IResource:
var patientPoco = new Patient(); //Requires Hl7.Fhir.Model
var resource = patientPoco.ToIResource();
IResource is immutable, so changes will always result in a new instance. Changes can usually be applied with extension methods on ISourceNode, found in Vonk.Core.ElementModel.ISourceNodeExtensions. There are also several extension methods specifically for IResource in Vonk.Core.Common.IResourceExtensions:
var updatedResource = oldResource.Add(SourceNode.Valued("someElement", "someValue");
//Continue with updatedResource, since oldResource will not have the change.
IResource extension methods
IResource has a whole list of extension methods for manipulating them, conversion between ISourceNode and IResource and caching objects within it. All these methods are in the namespace Vonk.Core.Common.IResourceExtensions
. Please check the ///-comments on the methods for more information.
ElementModel, manipulating IResource and ISourceNode
As stated in IResource, IResource
is immutable, and so is ISourceNode
that it is based on.
Still, there are cases where you would want to manipulate a resource, e.g. add, change or remove elements. The namespace Vonk.Core.ElementModel
has methods to do so. All of these methods do NOT change the original structure (the input IResource or ISourceNode), but instead return an updated structure.
ISourceNode manipulation
All the ISourceNode
extension methods can be used on IResource
as well. If you need an IResource
as result, just turn the resulting ISourceNode
to an IResource
again. So if you added an element to an ISourceNode:
IResource result = input.AddIfNotExists(SourceNode.Valued("active", "false")).ToIResource(input.InformationModel);
For some of the more common extension methods we provide an overload on IResource that does this for you, like IResource.Patch(...)
All the methods below are in the namespace Vonk.Core.ElementModel.ISourceNodeExtensions
:
- Add(this ISourceNode original, ISourceNode newChild) ISourceNode
Add the
newChild
as a child node to theoriginal
. It will be added at the end of the Children.
- Add(this ISourceNode original, ITypedElement newChild) ISourceNode
Overload of Add(ISourceNode newChild) that lets you add an ITypedElement as new child.
- AddIf(this ISourceNode original, ISourceNode newChild, Func<ISourceNode, bool> addIf) ISourceNode
Add the
newChild
as a child node to theoriginal
if theaddIf
predicate onoriginal
is met. It will be added at the end of the Children.
- Add(this ISourceNode original, TypedElement newChild, Func<ISourceNode, bool> addIf) ISourceNode
Similar to AddIf(ISourceNode newChild, Func<ISourceNode, bool> addIf) that lets you add an ITypedElement as new child.
- AddIfNotExists(this ISourceNode original, ISourceNode newChild) ISourceNode
Add the
newChild
as a child node to theoriginal
if there is no child with the same name yet. It will be added at the end of the Children.
- AddIfNotExists(this ISourceNode original, ISourceNode newChild, Func<ISourceNode, bool> exists) ISourceNode
Add the
newChild
as a child node to theoriginal
if theexists
predicate onoriginal
is not satisfied. This is likeAddIfNotExist(ISourceNode newChild)
, but here you get to specify what ‘exists’ means. It will be added at the end of the Children.
- AddIfNotExists(this ISourceNode original, string location, ISourceNode newChild) ISourceNode
Navigate to
lcoation
. Then add thenewChild
as a child node to theoriginal
if there is no child with the same name yet.
- AddIfNotExists(this ISourceNode original, ISourceNode newChild, string location, Func<ISourceNode, bool> exists) ISourceNode
Navigate to
location
. Then add thenewChild
as a child node if theexists
predicate on the current node is not satisfied.
- AnnotateWith<T>(this ISourceNode original, T annotation, bool hideExisting = false) -> ISourceNode
Add an annotation of type T to the
original
. When hideExisting == true, any existing annotations of type T are not visible anymore on the returned ISourceNode.
- GetBoundAnnotation<T>(this ISourceNode original, ) where T : class, IBoundAnnotation -> T
Retrieve an annotation that is bound directly to
original
, not to any of the nodes it may decorate. (ISourceNode is immutable, to changes are usually a pile of wrappers around theoriginal
SourceNode, and each of the wrappers can add / replace annotations.)
- RemoveEmptyNodes(this ISourceNode original, ISourceNode newChild) ISourceNode
Remove any nodes that have no value or children. This happens recursively: if a node has only children with empty values, it will be removed as well. This way the returned ISourceNode conforms to the invariant in the FHIR specification that an element either has a value or children.
- RemoveEmptyNodes(this ISourceNode original, ISourceNode newChild, string location) ISourceNode
Remove any nodes that have no value or children, from the specified
location
downwards. This happens recursively: if a node has only children with empty values, it will be removed as well.
- Child(this ISourceNode original, string name, int arrayIndex = 0) ISourceNode
Convenience method to get the child with name
name
at positionarrayIndex
. Usually used to get a child of which you know there is only one:patientNode.Child("active")
- ChildString(this ISourceNode original, string name, int arrayIndex = 0) ISourceNode
Convenience method to get the value of the child with name
name
at positionarrayIndex
. Usually used to get a child of which you know there is only one:patientNode.ChildString("id")
- ForceAdd(this ISourceNode original, string addAt, ISourceNode newChild) ISourceNode
Add the
newChild
at locationaddAt
. Create the intermediate nodes if neccessary.
- AddOrReplace(this ISourceNode original, Func<ISourceNode, bool> match, ISourceNode toAdd, Func<ISourceNode, ISourceNode> replace) ISourceNode
Find any child nodes of
original
that match thematch
predicate. Applyreplace
to them. If none are found, addtoAdd
as new child.
- AddOrReplace(this ISourceNode original, ISourceNode toAdd, Func<ISourceNode, ISourceNode> replace) ISourceNode
Optimized overload of the previous method for matching on the node name. It will perform
replace
on any child node oforiginal
with the same name astoAdd
. If none are found it will addtoAdd
as new child node.
- Remove(this ISourceNode original, string location) ISourceNode
Remove the node at
location
, if any. If that results in parent nodes becoming empty (no Text, no Children), those are removed as well.
- SelectNodes(this ISourceNode original, string fhirPath) IEnumerable<ISourceNode>
Run
fhirPath
over theoriginal
, but with the limitations of untyped nodes. It will return the matching nodes. Use valueDateTime/valueBoolean instead of just ‘value’ for choice types. Only use this method if you are familiar with the differences in the naming of nodes between ISourceNode and ITypedElement.
- SelectText(this ISourceNode original, string fhirPath) string
Run
fhirPath
over theoriginal
, but with the limitations of untyped nodes. Returns theText
of the first matching node. Use valueDateTime/valueBoolean instead of just ‘value’ for choice types. Only use this method if you are familiar with the differences in the naming of nodes between ISourceNode and ITypedElement.
- Patch(this ISourceNode original, string location, Func<ISourceNode, ISourceNode> patch) ISourceNode
Find any nodes at
location
and applypatch
to them. Forpatch
you can use other methods listed here likeRename
,Add
orRevalue
.location
is evaluated as a fhirpath statement, with the limitations of untyped nodes.
- Patch(this ISourceNode original, string[] locations, Func<ISourceNode, ISourceNode> patch) ISourceNode
Find any nodes having one of the
locations
as their Location and applypatch
to them. If you don’t know exact locations, useoriginal.Patch(location, patch)
, see above.
- ForcePatch(this ISourceNode original, string forcePath, Func<ISourceNode, ISourceNode> patch) ISourceNode
Enforce that
forcePath
exists. Then patch the resulting node(s) withpatch
.
- ForcePatchAt(this ISourceNode original, string fromLocation, string forcePath, Func<ISourceNode, ISourceNode> patch) ISourceNode
For each node matching the
fromLocation
: enforce thatfromLocation.forcePath
exists, then patch the resulting node(s) withpatch
. E.g. someBundle.ForcePatchAt(“entry”, “request”, node => node.Add(SourceNode.Valued(“url”, “someUrl”)) will add request.url with value “someUrl” to every entry.
- Relocate(this ISourceNode original, string newLocation) ISourceNode
Set
original.Location
to the newLocation, and update all its descendants’Location
properties recursively.
- Rename(this ISourceNode original, string newName) ISourceNode
Set
original.Name
to thenewName
.
- Revalue(this ISourceNode original, string newValue) ISourceNode
Set
original.Text
tonewValue
.
- Revalue(this ISourceNode original, Dictionary<string, string> replacements) ISourceNode
replacements
is a dictionary of location + newValue. On each matching location underoriginal
, the value will be set to the according newValue fromreplacements
.
- AnnotateWithSourceNode(this ISourceNode original) ISourceNode
Add
original
as annotation to itself. Very specific use case.
ITypedElement manipulation
All the methods below are in the namespace Vonk.Core.ElementModel.ITypedElementExtensions
:
- Add(this ITypedElement original, ITypedElement newChild, Func<ITypedElement, bool> addIf) ISourceNode
Add
newChild
as child tooriginal
ifaddIf
onoriginal
evaluates to true. Convenience overload ofISourceNodeExtensions.Add(ISourceNode, ITypedElement, Func<ISourceNode, bool>)
- Add(this ITypedElement original, ITypedElement newChild) ISourceNode
Add
newChild
as child tooriginal
. Convenience overload ofISourceNodeExtensions.Add(ISourceNode, ITypedElement)
- AddIfNotExists(this ITypedElement original, ITypedElement newChild) ISourceNode
Add
newChild
as child tooriginal
if no child with the same name exists yet. Convenience overload ofISourceNodeExtensions.AddIfNotExists(ISourceNode, ITypedElement)
- AddIf(this ITypedElement original, ISourceNode newChild, Func<ITypedElement, bool> addIf) ISourceNode
Add
newChild
as child tooriginal
ifaddIf
onoriginal
evaluates to true. Convenience overload ofISourceNodeExtensions.AddIf(ISourceNode, ISourceNode, Func<ISourceNode, bool>)
- method
Add(this ITypedElement original, ISourceNode newChild) Add
newChild
as child tooriginal
.- method
AddIfNotExists(this ITypedElement original, ISourceNode newChild) Add
newChild
as child tooriginal
if no child with the same name exists yet. Convenience overload ofAddIfNotExists(ITypedElement, ITypedElement)
- Cache(this ITypedElement original) ITypedElement
Prevent recalculation of the Children upon every access.
- Child(this ITypedElement element, string name, int arrayIndex = 0) ITypedElement
Returns n-th child with the specified
name
, if any.
- ChildString(this ITypedElement element, string name, int arrayIndex = 0) string
Returns the value of the n-th child with the specified
name
as string, if any.
- DefinitionSummary(this ITypedElement element, IStructureDefinitionSummaryProvider provider) IStructureDefinitionSummary
Returns the summary for the actual type of the element. Especially useful if the element is of a choicetype.
- AddParent(this ITypedElement element) ITypedElement
Add
Vonk.Core.ElementModel.IParentProvider
annotations toelement
and its descendants.
- GetParent(this ITypedElement element) ITypedElement
Get the parent of this element, through the
Vonk.Core.ElementModel.IParentProvider
annotation (if present).
- AddTreePath(this ITypedElement element) ITypedElement
Add the
Vonk.Core.ElementModel.ITreePathGenerator
annotation. TreePath is the Location without any indexes (no [n] at the end).
- GetTreePath(this ITypedElement element) string
Get the value of the
Vonk.Core.ElementModel.ITreePathGenerator
annotation, if present. TreePath is the Location without any indexes (no [n] at the end).
IVonkContext
- namespace
Vonk.Core.Context
- purpose
IVonkContext is the Vonk-specific counterpart to HttpContext from ASP.NET Core. It contains an IVonkRequest and IVonkResponse object that allow you to get information from the request and set results in the response, both in Firely Server terms.
Have IVonkContext
injected in the method where you need it. Use a configuration class to call this method from the pipeline and have the actual context injected. A more complete template is found at Template for a plugin.
public class SomeService
{
public async Task DoMyOperation(IVonkContext vonkContext)
{
//...
}
}
public static class SomeServiceConfiguration
{
public static IApplicationBuilder UseMyOperation(this IApplicationBuilder app)
{
return app.UseVonkInteractionAsync<SomeService>((svc, context) => svc.DoMyOperation(context));
}
}
If you also need access to the raw HttpContext
, you have two options:
The
IVonkContext.HttpContext()
extension method gives you the original HttpContext. Be aware though:Situations may arise where there is no HttpContext, so be prepared for that.
When handling batches or transactions, an IVonkContext is created for each entry in the bundle. But they all refer to the same original HttpContext.
Create a normal ASP.NET Core Middleware class and access the IVonkContext with the extension method
Vonk()
onHttpRequest
. A more complete template is found at Returning non-FHIR content from a plugin.public class SomeMiddleware { public SomeMiddleware(RequestDelegate next) { //... } public async Task Invoke(HttpContext httpContext) { var vonkContext = httpContext.Vonk(); //... } } public static class SomeMiddlewareConfiguration { public static IApplicationBuilder UseSomeMiddleware(this IApplicationBuilder app) { return app.UseMiddleware<SomeMiddleware>(); //Just plain ASP.NET Core, nothing Firely Server specific here. } }
IVonkContext has three major parts, that are explained below. The InformationModel
tells you the FHIR version for which the request was made.
public interface IVonkContext
{
IVonkRequest Request {get;}
IArgumentCollection: Arguments{get;}
IVonkResponse Response {get;}
string InformationModel {get;}
}
And because you frequently need the parts instead of the context itself, there is an extension method on IVonkContext
:
public (IVonkRequest request, IArgumentCollection args, IVonkResponse respons) Parts(this IVonkContext vonkContext)
IVonkRequest
- namespace
Vonk.Core.Context
- purpose
Get information about the request made, in Firely Server / FHIR terms.
You can access the current IVonkRequest
through the IVonkContext. Its properties are:
public interface IVonkRequest
{
string Path { get; }
string Method { get; }
string CustomOperation { get; }
VonkInteraction Interaction { get; }
RequestPayload Payload { get; set; }
}
Path
and Method
relate directly to the equivalents on HttpContext. Interaction
tells you which of the FHIR RESTful interactions was called. CustomOperation
is only filled if one of the custom operations was invoked, like e.g. $validate
. All of these can be filtered by the InteractionHandlerAttribute, so you typically don’t need to inspect them manually.
Payload indirectly contains the resource that was sent in the body of the request. You are advised to only use the extension methods to access it:
public static bool TryGetPayload(this IVonkRequest request, out IResource resource)
TryGetPayload is useful if your code wants to act on the payload if it is present, but does not care if it is not.
public void ThisMethodActsOnThePayloadIfPresent(IVonkContext vonkContext)
{
var (request, args, response) = vonkContext.Parts();
if (request.TryGetPayload(response, out var resource))
{
// do something with the resource.
}
}
public static bool GetRequiredPayload(this IVonkRequest request, IVonkResponse response, out IResource resource)
GetRequiredPayload is useful if your code expects the payload to be present. It will set the appropriate response code and OperationOutcome on the provided response if it is not present or could not be parsed. Then you can choose to end the pipeline and thus return the error to the user.
public void ThisMethodNeedsAPayload(IVonkContext vonkContext)
{
var (request, args, response) = vonkContext.Parts();
if (!request.GetRequiredPayload(response, out var resource))
{
return; //If you return with an error code in the response, Firely Server will end the pipeline
}
// do something with the resource.
}
If you want to change the payload, assign a whole new one. Generally you would want to change something to the old payload. But IResource is immutable, so changes to it yield a new instance. That leads to this pattern
if (request.TryGetPayload(response, out var resource)
{
//Explicit typing of variables for clarity, normally you would use 'var'.
ISourceNode updatedNode = resource.Add(SourceNode.Valued("someElement", "someValue");
IResource updatedResource = updatedNode.ToIResource();
request.Payload = updatedResource.ToPayload();
}
IArgumentCollection, IArgument
- namespace
Vonk.Core.Context
- purpose
Access arguments provided in the request.
The IVonkContext.Arguments
property contains all the arguments from the request, from the various places:
The path segments: /Patient/123/_history/v1 will translate to three arguments, _type, _id and _version.
The query parameters: ?name=Fred&active=true will translate to two arguments, name and active.
The headers:
If-None-Exists = identifier=abc&active=true will translate to two arguments, identifier and active.
If-Modified-Since, If-None-Match, If-Match: will each translate to one argument
An individual argument will tell you its name (ArgumentName
), raw value (ArgumentValue
) and where it came from (Source
).
Handling arguments
An argument by default has a Status
of Unhandled
.
If an argument is of interest to the operation you implement in your plugin, you can handle the argument. It is important to mark arguments handled if:
you handled them
or the handling is not relevant anymore because of some error you encountered
In both cases you simply set the Status
to Handled
.
If an argument is incorrect, you can set its status to Error
and set the Issue
to report to the client what the problem was. These issues will be accumulated in the response by Firely Server automatically.
Any argument that is not handled will automatically be reported as such in an OperationOutcome.
Useful extension methods:
IArgument.Handled()
IArgument.Warning(string message, Issue issue)
IArgument.Error(string message, Issue issue)
Firely Server has a lot of issues predefined in Vonk.Core.Support.VonkIssues
.
IVonkResponse
- namespace
Vonk.Core.Context
- purpose
Inspect response values set by other middleware, or set it yourself.
public interface IVonkResponse
{
Dictionary<VonkResultHeader, string> Headers { get; }
int HttpResult { get; set; }
OperationOutcome Outcome { get; }
IResource Payload { get; set; }
}
If your operation provides a response, you should:
Set the response code
HttpResult
.Provide a resource in the
Payload
, if applicable.Add an issue if something is wrong.
If you just listen in on the pipeline, you can check the values of the response. Besides that, the InteractionHandlerAttribute allows you to filter on the HttpStatus
of the response.
IFormatter
- namespace
Vonk.Core.Context.Format
- purpose
Serialize response resource in requested format to the body of the HttpContext.Response. Although this interface is public, you should never need it yourself, since the VonkToHttp plugin takes care of this for you.
Interaction Handling
In the configuration of a plugin you specify on which interaction(s) the plugin should act. That can be done with an attribute on the main method of the service in the plugin, or with a fluent interface on IApplicationBuilder.
InteractionHandlerAttribute
- namespace
Vonk.Core.Pluggability
- purpose
Add an
[InteractionHandler]
attribute to a method to specify when the method has to be called. You specify this by providing values that the IVonkContext should match.
Without any arguments, the method will be called for every possible interaction.
[InteractionHandler()]
public async Task DoMyOperation(IVonkContext vonkContext)
You can specify different filters, and combine them at will:
Specific interaction(s):
[InteractionHandler(Interaction = VonkInteraction.type_create | VonkInteraction.instance_update)]
Specific FHIR version(s) of the request:
[InteractionHandler(InformationModel = VonkConstants.Model.FhirR4)]
Specific resource type(s):
[InteractionHandler(AcceptedTypes = new["Patient", "Observation"])]
Specific custom operation:
[InteractionHandler(Interaction = VonkInteraction.all_custom, CustomOperation = "myCustomOperation")]
. Note that the$
that is used on the url is not included in the name of the custom operation here.Specific http method:
[InteractionHandler(Method = "POST")]
Specific statuscode(s) on the response:
[InteractionHandler(StatusCode = new[]{200, 201})]
Now to configure your service to be a processor in the Firely Server pipeline, you use UseVonkInteraction[Async]()
:
public static class MyOperationConfiguration
{
public static IApplicationBuilder UseMyOperation(this IApplicationBuilder app)
{
return app.UseVonkInteractionAsync<MyService>((svc, ctx) => svc.DoMyOperation(ctx));
}
}
InteractionHandler fluent interface
Because InteractionHandler
is an attribute, you can only use constant values. If that is not what you want, you can use the fluent interface in the configuration class instead. The code below shows the same filters as above, although you typically would not use all of them together (e.g. the PUT
excludes type_create
).
public static class MyOperationConfiguration
{
public static IApplicationBuilder UseMyOperation(this IApplicationBuilder app)
{
return app
.OnInteraction(VonkInteraction.type_create | VonkInteraction.instance_update)
.AndInformationModel(VonkConstants.Model.FhirR4)
.AndResourceTypes(new[] {"Patient", "Observation"})
.AndStatusCodes(new[] {200, 201})
.AndMethod("PUT")
.HandleAsyncWith<MyService>((svc, ctx) => svc.DoMyOperation(ctx));
}
}
Other Handle...
methods allow you to define a pre-handler (that checks or alters the request before the actual operation) or a post-handler (that checks or alters the response after the actual operation), either synchronously or asynchronously.
If you have a very specific filter that is not covered by these methods, you can specify it directly with a function on the IVonkContext
that returns a boolean whether or not to call your operation.
app
.On(ctx => MyVerySpecificFilter(ctx))
.Handle...
Attention
The filter you specify is called for every request. So make sure you don’t do any heavy calculations or I/O.
IApplicationBuilder extension methods
- UseVonkInteraction<TService>(this IApplicationBuilder app, Expression<Action<<TService, IVonkContext>> handler, OperationType operationType = OperationType.Handler) -> IApplicationBuilder
Handle the request with the
handler
method when the request matches theInteractionHandler
attribute on thehandler
method. TheOperationType
may also specifyPreHandler
orPostHandler
. If you need to do anything lengthy (I/O, computation), use the Async variant of this method.
- UseVonkInteractionAsync<TService>(this IApplicationBuilder app, Expression<Func<TService, IVonkContext, T.Task>> handler, OperationType operationType = OperationType.Handler) -> IApplicationBuilder
Handle the request with the asynchronous
handler
method when the request matches theInteractionHandler
attribute on thehandler
method. TheOperationType
may also specifyPreHandler
orPostHandler
.
- OnInteraction(this IApplicationBuilder app, VonkInteraction interaction) VonkAppBuilder
Used for fluent configuration of middleware. This is one of two methods to enter the
VonkAppBuilder
, see VonkAppBuilder extension methods. It requires you to choose an interaction to act on. If you need your services to act on every interaction, chooseVonkInteraction.all
.
- OnCustomInteraction(this IApplicationBuilder app, VonkInteraction interaction, string custom) VonkAppBuilder
Used for fluent configuration of middleware. This is one of two methods to enter the
VonkAppBuilder
, see VonkAppBuilder extension methods. It requires you to choose an interaction to act on. This should be one of theVonkInteraction.all_custom
interactions.custom
is the name of the custom interaction to act on, without the preceding ‘$’.
VonkAppBuilder extension methods
VonkAppBuilder
is used to fluently configure your middleware. It has methods to filter the requests that your middleware should respond to. Then it has a couple of *Handle...
methods to transform your service into middleware for the pipeline, and return to the IApplicationBuilder interface.
- AndInteraction(this VonkAppBuilder app, VonkInteraction interaction) VonkAppBuilder
Specify an interaction to act on.
- AndResourceTypes(this VonkAppBuilder app, params string[] resourceTypes) VonkAppBuilder
Specify the resourcetypes to act on.
- AndStatusCodes(this VonkAppBuilder app, params int[] statusCodes) VonkAppBuilder
Specify the statuscode(s) of the response to act on. This is mainly useful for posthandlers.
- AndMethod(this VonkAppBuilder app, string method) VonkAppBuilder
Specify the http method (GET, PUT, etc) to act on.
- AndInformationModel(this VonkAppBuilder app, string model) VonkAppBuilder
If your service can only act on one FHIR version, specify it with this method. Common values for
model
areVonkConstants.Model.FhirR3
andVonkConstants.Model.FhirR4
.
- PreHandleAsyncWith<TService>(this VonkAppBuilder app, Expression<Func<TService, IVonkContext, T.Task>> preHandler) -> IApplicationBuilder
Mark the
preHandler
method as a prehandler, so it will act on the IVonkContext and send it further down the pipeline.
- PreHandleWith<TService>(this VonkAppBuilder app, Expression<Action<TService, IVonkContext>> preHandler) -> IApplicationBuilder
Synchronous version of
PreHandleAsyncWith
for synchronouspreHandler
methods.
- HandleAsyncWith<TService>(this VonkAppBuilder app, Expression<Func<TService, IVonkContext, T.Task>> handler) -> IApplicationBuilder
Mark the
handler
method as a hanlder, so it will act on the IVonkContext, provide a response and end the pipeline for the request.
- HandleWith<TService>(this VonkAppBuilder app, Expression<Action<TService, IVonkContext>> handler)
Synchronous version of
HandleAsyncWith
for synchronoushandler
methods.
- PostHandleAsyncWith<TService>(this VonkAppBuilder app, Expression<Func<TService, IVonkContext, T.Task>> postHandler) -> IApplicationBuilder
Mark the
postHandler
method as a posthandler, so it will pass on the IVonkContext to the rest of the pipeline, and on the way back through the pipeline inspect or modify the response. Make sure that theVonkConfiguration
order you have for this is lower than whatever action you need to post-handle.
- PostHandleWith<TService>(this VonkAppBuilder app, Expression<Action<TService, IVonkContext>> postHandler) -> IApplicationBuilder
Synchronous version of
PostHandleAsyncWith
for synchronouspostHandler
methods.
Pipeline configuration
VonkConfigurationAttribute
- namespace
Vonk.Core.Pluggability
- purpose
This attribute is used on a static class to make Firely Server recognize it as part of the configuration of the processing pipeline. See Configuration classes.
- properties
Order
: Determines the place in the pipeline. See The order of plugins for background.
Repository interfaces
ISearchRepository
Vonk.Core.Repository.ISearchRepository
is central in the functioning of Firely Server. It defines all read-access to the underlying repository, being one of Firely Server’s own database implementations or a Facade implementation.
It has a single method:
Task<SearchResult> Search(IArgumentCollection arguments, SearchOptions options);
Using ISearchRepository
Have it injected by the dependency injection into the class where you need it:
public MyService(ISearchRepository searchRepository) { _searchRepository = searchRepository; }
Note
Implementations of ISearchRepository have a Scoped lifetime, so MyService should also be registered as Scoped:
public IServiceCollection AddMyServices(this IServiceCollection services) { services.TryAddScoped<MyService>(); return services; }
Prepare search arguments that express what you are looking for. Search arguments are effectively key-value pairs as if they came from the querystring on the request. So the key must be the code of a supported Searchparameter.
var args = new ArgumentCollection { new Argument(ArgumentSource.Internal, ArgumentNames.resourceType, "Patient") { MustHandle = true //optional }, new Argument(ArgumentSource.Internal, "name", "Fred") { MustHandle = true //optional }, } .AddCount(20);
Note
The Search implementation will in general update the arguments, especially their
Status
property and theIssue
if something went wrong. So be careful with reuse of arguments. UseIArgumentCollection.Clone()
if neccessary.Prepare search options that guide the search. Usually you can use one of the predefined options on the
SearchOptions
class.var options = SearchOptions.Latest(vonkContext.ServerBase, vonkContext.Request.Interaction, vonkContext.InformationModel);
Execute the search.
var searchResult = await _searchRepository.Search(args, options);
Check the status of the arguments, expecially if they could not be ignored (MustHandle = true). Because this is a common pattern, there is an extension method
CheckHandled
that throws a VonkParameterException if MustHandle arguments are not handled.try { args.CheckHandled("Arguments must all be handled in MyService"); } catch (VonkParameterException vpe) { //report it in the vonkContext.Response.Outcome }
Inspect the number of the results to check whether anything was found. If so, you can enumerate the results or process the set as a whole, since
SearchResult
implementsIEnumerable<IResource>
.if (searchResult.TotalCount > 0) { foreach(var resource in searchResult) { ... } }
Implement ISearchRepository
Implementing ISearchRepository is only needed in a Facade.
The general pattern for implementing ISearchRepository is:
For each of the IArguments in the IArgumentCollection:
If you support the argument, translate it to a ‘where’ clause on your repository. If your backend is another Web API, this could have the form of a piece of a querystring.
Call IArgument.Handled() to update its status. There is also .Warning() and .Error() when something is wrong with the argument. If you simply don’t support the argument, you can leave the status to ‘Unhandled’.
Pay special attention to the
_count
and_skip
arguments for proper paging.
‘AND’ all the arguments together, e.g. forming a database query or complete querystring.
Issue the query to your repository and await the results (await used intentionally: this should be done asynchronously).
For each of the resulting records or objects: map them to matching FHIR resources, either by:
Creating POCO’s:
var result = new Patient() { /*fill in from the source object*/ }; return result.ToIResource(); //InformationModel implied by the assembly of class Patient
or by crafting SourceNodes:
var result = SourceNode.Resource("Patient", SourceNode.Valued("id", /* id from source */), SourceNode.Node("meta", SourceNode.Valued("versionId", "v1"), ....), ...)) return result.ToIResource(VonkConstants.Model.FhirR3 /* or FhirR4 */);
Combine the mapped resources into a SearchResult:
return new SearchResult(resources, pagesize, totalCount, skip);
pagesize
: should be the value of the _count argument, unless you changed it for some reason.totalCount
: total number of results, if there are more than you are returning right now.skip
: number of results skipped in this set (if you are serving page x of y).
For a Facade on a relational database we provide a starting point with Vonk.Facade.Relational.SearchRepository
. Follow the exercise in Exercise: Build your first Facade to see how that is done.
IResourceChangeRepository
IResourceChangeRepository
defines methods to change resources in the repository:
public interface IResourceChangeRepository
{
Task<IResource> Create(IResource input);
Task<IResource> Update(ResourceKey original, IResource update);
Task<IResource> Delete(ResourceKey toDelete, string informationModel);
string NewId(string resourceType);
string NewVersion(string resourceType, string resourceId);
}
ResourceKey
is a simple struct to identify a resource by Type, Id and optionally VersionId.
Using IResourceChangeRepository
You should hardly ever need to use the IResourceChangeRepository
. It is used by the Create, Update, Delete and the conditional variations thereof.
Should you need to use it, the methods are fairly straightforward.
- method
Create
- description
Provide an IResource having an id and versionId. These can be obtained by calling NewId and NewVersion. The return value will contain a possibly updated IResource, if the implementation changed or added elements. To fill in all the metadata there is a convenient extension method on
IResourceChangeRepository
:var withMetaInfo = changeRepository.EnsureMeta(resource); //Will keep existing id, version and lastUpdated and fill in if missing. //EnsureMeta also exists as extension method on IResource - that uses Guids for id and version. var createdResource = await changeRepository.Create(withMetaInfo);
- method
Update
- description
Assert that a resource exists that can be updated. If not, use
Create
, otherwise go for Update.var existingKey = new ResourceKey(resourceType, resourceId); var args = existingKey.ToArguments(true); var args = args.AddCount(0); //We don't need the actual result - just want to know whether it is there. var options = SearchOptions.Latest(vonkContext.ServerBase, VonkInteraction.type_search, InformationModel: null); //search across informationmodels, we expect ids to be unique. var exists = (await searchRepository.Search(args, options)).TotalCount = 1; //Take care of < 1 or > 1 matches var withMetaInfo = changeRepository.EnsureMeta(resource, KeepExisting.Id); //Will keep existing id and provide fresh version and lastUpdated. var updatedResource = await changeRepository.Update(existingKey, withMetaInfo);
- method
Delete
- description
Delete the resource that matches the provided key and informationModel. Returns the resource that was deleted.
var existingKey = new ResourceKey(resourceType, resourceId); var deletedResource = await changeRepository(existingKey, vonkContext.InformationModel);
- method
NewId
- description
Get a new Id value generated by the repository (e.g. when the repository wants to use a sequence generator or ids in a specific format). Generally used through the extension method IResourceChangeRepository.EnsureMeta(IResource resource, KeepExisting keepExisting), see
Create
above.- method
NewVersion
- description
Get a new Version value generated by the repository (e.g. when the repository wants to use a sequence generator or ids in a specific format). The repository may want to base the version on the id, therefore the Id is passed as an argument. Generally used through the extension method IResourceChangeRepository.EnsureMeta(IResource resource, KeepExisting keepExisting), see
Create
above.
Implement IResourceChangeRepository
Implementing IResourceChangeRepository is only needed in a Facade that wants to provide write-access to the underlying repository.
For all three methods, you will have to map data from FHIR resources to your internal data structures and back.
Note that you also need to implement ISearchRepository to support the Create and Update plugins and of course the conditional variants of those.
Constructing bundles
In a Plugin or Facade you may need to construct a bundle from a set of resources, e.g. a SearchResult (see here). There are two ways of doing this: with the Bundle POCO or with SourceNodes.
Bundle POCO
This is fairly straightforward. Create a new Bundle object, and fill its properties, iterating over the set of resources that you have. The code looks like this:
using Hl7.Fhir.Model; //Either from Hl7.Fhir.Core.Stu3 or Hl7.Fhir.Core.R4
...
var searchResult = await searchRepository.Search(args, options)
var bundle = new Bundle() { Type = Bundle.BundleType.Searchset }; // Type is required
foreach (var resource in searchResult)
{
bundle.Entry.Add
{
new BundleEntryComponent
{
Resource = resource.ToPoco<Resource>();
// fill in any other details like Request or Response.
}
}
}
//fill in details of the bundle as a whole, like Meta or Identifier.
The limitation of this is that you are bound to either STU3 or R4, and that also implies that you cannot include Custom Resources in the bundle.
Bundle from SourceNodes
ISourceNode is from Hl7.Fhir.ElementModel and not tied to a specific FHIR version, and Firely Server can serialize ISourceNode provided that the right StructureDefinition is available in the Administration API - which is the case for Bundle by default.
You start by creating the Bundle itself using the class SourceNode that allows for construction of ISourceNode nodes.
using Hl7.Fhir.ElementModel;
...
var bundleNode = SourceNode.Resource("Bundle", "Bundle", SourceNode.Valued("type", "document"));
//choose type as one of the bundle types from the spec, see http://hl7.org/fhir/R4/bundle-definitions.html#Bundle.type
Then you can add elements to the bundle itself that are not in the entries of the bundle. Like an identifier:
var identifier = SourceNode.Node("identifier");
identifier.Add(SourceNode.Valued("system", "urn:ietf:rfc:3986"));
identifier.Add(SourceNode.Valued("value", Guid.NewGuid().ToString()));
bundleNode.Add(identifier);
Then you can turn this into the helper class GenericBundle
that provides several helper methods on an ISourceNode that is known to be a Bundle.
var documentBundle = GenericBundle.FromBundle(bundleNode);
documentBundle = documentBundle.Meta(Guid.NewGuid().ToString(), DateTimeOffset.Now);
Maybe you already saw an alternative way of adding the identifier in the intellisense by now:
documentBundle = documentBundle.Identifier("urn:ietf:rfc:3986", Guid.NewGuid().ToString());
Note that you always have to continue with the result of the modifying function. All these functions act on ISourceNode and that is immutable, so you get a new instance with the changes applied as a return value.
Now you have the skeleton of the Bundle, it is ready to add entries with resources to it.
IResource resourceForDocument = ... ; //Get or construct a resource that is one of the entries of the Bundle.
documentBundle = documentBundle.AddEntry(resourceForDocument, resourceForDocument.Key().ToRelativeUri());
Other extensions methods available on GenericBundle
:
public static GenericBundle Total(this GenericBundle bundle, int total)
public static GenericBundle AddLink(this GenericBundle bundle, string relation, string uri)
public static GenericBundle Links(this GenericBundle bundle, Dictionary<string, string> links)
Search result bundles
Usually you don’t need to construct a searchset bundle yourself, since the SearchService takes care of that when a search is issued on the FHIR endpoint. But should you want to do it in a custom operation, then the methods for doing so are at your disposal.
To help construct a bundle of type ‘searchset’, there is a special kind of bundle class SearchBundle
. Create the sourcenode for the bundle as above. Then instead of creating a GenericBundle
, turn it into a SearchBundle
:
var searchBundle = bundleNode.ToSearchBundle();
Now you can use various methods to add entries for matches, includes or an OperationOutcome:
//SearchBundle methods
public SearchBundle AddMatch(ISourceNode resource, string fullUrl, string score = null)
public SearchBundle AddInclude(ISourceNode resource, string fullUrl, string score = null)
public SearchBundle AddOutcome(ISourceNode outcome, string fullUrl, string score = null)
//Extension methods
public static SearchBundle ToSearchBundle(this IEnumerable<SearchInfo> searchInfos, string informationModel)
public static SearchBundle ToSearchBundle(this IEnumerable<ISourceNode> resources, string searchMode, string informationModel)
public static SearchBundle ToSearchBundle(this IEnumerable<ITypedElement> resources, string searchMode, string informationModel)
The SearchInfo
struct essentially captures all the information that goes into an entry of a searchset bundle:
public struct SearchInfo
{
public SearchInfo(ISourceNode resource, string mode = SearchMode.match, string fullUrl = null, string score = null)
public string Mode { get; }
public ISourceNode Resource { get; }
public string FullUrl { get; }
public string Score { get; }
}
Using all this to turn the SearchResult
returned from the ISearchRepository.Search()
method into a bundle looks like this (using the second extension method above):
var bundle = searchResult
.ToSearchBundle(SearchMode.match, vonkContext.InformationModel)
//informationModel is needed because bundle has slight differences between STU3 and R4
.Total(searchResult.Page.TotalCount)
//Total is defined on GenericBundle
.Links(searchResult.Page.PagingLinks(vonkContext));
//Links is defined on GenericBundle
return bundle.ToIResource(vonkContext.InformationModel).EnsureMeta();
Access Control in Plugins and Facades
The Access Control feature is also available to users of a Firely Server Facade or Firely Server Plugins. You can use the default implementation based on SMART on FHIR, or provide an implementation of your own.
Access control implementation
The access control engine is programmed using interfaces for which you can provide your own implementation. Because we think the model behind SMART on FHIR covers many cases, these interfaces are loosely modelled after it. The important interfaces and class are:
Interface / Class |
Description |
---|---|
IAuthorization |
Defines whether your are allowed to read or write a type of resource. |
ICompartment |
Confines the user to a compartment, expressed as a combination of a |
IReadAuthorizer |
Calculates access control for a type of resource given an instance of IAuthorization |
IWriteAuthorizer |
Calculates access control for writing a new (version of a) resource given an instance |
AuthorizationResult |
Return value of IReadAuthorizer and IWriteAuthorizer methods. |
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.
Search
The FHIR RESTful Search is the most complex part of the FHIR RESTful API. Firely Server is capable of interpreting the search and translating it to small query-bits irrespective of the actual repository implementation.
When implementing the ISearchRepository
you get full control over which parameters you support and how you support them.
On the method ISearchRepository.Search()
, you just get the list of arguments that make up the search, as an IArgumentCollection
. If you decide to act on these raw arguments directly, you can.
But if you want Firely Server to interpret the search, you can use the QueryBuilderContext.CreateQuery
method that will decompose the IArgumentCollection, interpret and validate every argument in it and then call into the
IRepoQueryFactory.Filter
or IRepoQueryFactory.ResultShape
method for each of the arguments.
So the Filter
method is called with interpreted search arguments. E.g. identifier=abc
will be provided as (in pseudocode) Filter("identifier", TokenValue{code = "abc", noSystem = true})
.
If no search parameter is defined for an argument, you still get the change to handle it. E.g. myspecialparameter=qyz
will be provided as Filter("myspecialparameter", RawValue{ value = "qyz" })
.
This allows for easy extensibility without defining your own SearchParameter resources, and is suitable for adding parameters that map to non-FHIR structures in your backend system.
Note however that Firely Server also supports Custom Search Parameters.
The ResultShape
method is called when an argument is recognized as a ‘Search result parameter’, like _sort or _include.
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.
Dependencies of Firely Server and their licenses
Firely Server is mainly built using libraries from Microsoft .Net Core and ASP.NET Core, along with a limited list of other libraries. This is the full list of direct depencies that Firely Server has on other libraries, along with their licenses.
This list uses the NuGet package names (or prefixes of them) so you can easily lookup further details of those packages on NuGet.org if needed.
Microsoft.AspNetCore.* - Apache 2.0
Microsoft.ApplicationInsights.* - MIT
Microsoft.Bcl.AsyncInterfaces - MIT
Microsoft.EntityFrameworkCore.* - Apache 2.0
Microsoft.Extensions.* - MIT
Microsoft.AspNet.WebApi.Client - MS-.NET-Library License
System.Interactive.Async - MIT
Microsoft.Data.SqlClient - MIT
Microsoft.CSharp - MIT
System.Interactive.Async - MIT
System.Text.Json - MIT
System.* - MS-.NET-Library License
NETStandard.Library - MIT
NewtonSoft.Json - MIT
IdentityModel.* - Apache 2.0
IdentityServer4.AccessTokenValidation - Apache 2.0
IPNetwork2 - BSD 2-Clause “Simplified” License
Quartz - Apache 2.0
Serilog(.*) - Apache-2.0
LinqKit.Microsoft.EntityFrameworkCore - MIT
Hl7.Fhir.* - Firely OSS license (see below)
Fhir.Metrics - as Hl7.Fhir
Simplifier.Licensing - as Hl7.Fhir
Dapper - Apache 2.0
SqlKata.* - MIT
MongoDB / CosmosDB:
MongoDB.* - Apache 2.0
For unittesting:
XUnit - Apache 2.0
Moq - BSD 3
FluentAssertions - Apache 2.0
Microsoft.NETCore.Platforms - MIT
Microsoft.NET.Test.Sdk - MS-.NET-Library License
System.Reactive - MIT
coverlet.collector - MIT
WireMock.Net - Apache 2.0
Firely OSS License
Firely Server relies on the reference .NET FHIR library: Hl7.Fhir.*, also created and maintained by Firely. The license is this (as stated in the LICENSE file:
Copyright (c) 2013-2020, HL7, Firely (info@fire.ly), Microsoft Open Technologies and contributors. See the file CONTRIBUTORS for details
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
Neither the name of Firely nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Tools
This section contains documentation about tools that can improve your Firely Server experience.
Firely Server Import (FSI)
Firely Server Import (FSI) is a CLI application designed to optimize massive resource ingestion into a Firely Server instance. In contrast to resource ingestion by sending HTTP requests to a running Firely Server instance, this tool writes data directly to the underlying FS database which increases the throughput significantly.
Note
Currently, this tool only supports import into SQL Server database, however, we are working on support for MongoDB.
The tool is in beta stage and can be tested only after consulting us.
General usage
Attention
Firely Server instances targeting the same database will be impacted severely by the workload that FSI puts on the database. We advise to stop the Firely Server instances while the import is performed.
Only one instance of FSI per database should be run at a time. FSI can utilize all the cores on the machine it is run on, and insert data over several connections to the database in parallel. Multiple instances would probably cause congestion in the database.
Prerequisites
The tool requires that the target SQL Server database already exists and contains all required tables and indexes. If you don’t have a database with the schema yet, you first need to run the Firely Server at least once as described in article Using SQL server.
Input files formats
FSI supports the following input file formats:
FHIR collection bundles stored in
*.json
files, and*.ndjson
files where each line contains a separate FHIR resource in JSON format.
After the import
After ingesting massive amount of data, it is important to make sure the SQL Server indexes are in good shape. You can read more on this topic here: Index Maintenance.
Arguments
The execution of FSI can be configured using input parameters. These parameters can be supplied either as CLI arguments or specified in the file appsettings.instance.json
which must be created in the same directory as the fsi.exe
.
If you want to specify input parameters in the file, you can use the snippet below as a base for your appsettings.instance.json
. In this case, you need to update the values that you want to set yourself and delete all other records.
{
"source": "./fsi-source", //valid directory
"limit": -1,
"fhirVersion": "R4",
"license": "C:/some/path/to/your/license/license.json", // See the Licensing section below
"updateExistingResources": "true",
"sqlserver": {
"connectionString": "<connectionstring to the Firely Server SQL Server database>",
"saveParallel": 2,
"queryExistenceParallel": 4,
"saveBatchSize": 500,
"commandTimeOut": 60, //seconds
"liftIndexes": false
},
"workflow": { //-1 = unbounded
"readParallel": 3,
"readBufferSize": 200,
"metaParallel": 1,
"metaBufferSize": 50,
"typeParallel": 4,
"typeBufferSize": 50,
"indexParallel": -1, //this is usually the most time consuming process - give it as much CPU time as possible.
"indexBufferSize": 50
}
}
Supported arguments
CLI argument |
Appsettings parameter name |
Required |
Description |
---|---|---|---|
|
fhirVersion |
FHIR version of the input |
|
|
source |
yes |
Input directory for work (this directory is visited recursively including all the subdirectories) |
|
limit |
Limit the number of resources to import. Use this for testing your setup. |
|
|
license |
yes |
Firely Server license file |
|
updateExistingResources |
When true, a resource is updated in the database if it already exists and a history record is created. Otherwise, an existing resource gets skipped. |
|
|
sqlServer/connectionString |
yes |
Connection string to Firely Server SQL Server database |
|
sqlServer/saveParallel |
The # of batches to save in parallel. Depends on your bandwidth to SQL Server and its processing power. |
|
|
sqlServer/saveBatchSize |
The # of resources to save in each batch. SQL Server must be able to process it within the CommandTimeout. It is recommended to set this value to at least 500 for optimal performance. |
|
|
sqlServer/commandTimeOut |
The time SQL Server is allowed to process a batch of resources. |
|
|
sqlserver/queryExistenceParallel |
The number of parallel threads querying the DB to check whether a resource exists (only when |
|
|
sqlServer/liftIndexes |
Experimental! Removes all the indexes before the import and re-applies them afterwards. |
|
|
workflow/readParallel |
Number of threads to read from the source. Reading is quite fast so it need not be high. |
|
|
workflow/readBufferSize |
Number of resources to buffer after reading. |
|
|
workflow/metaParallel |
Number of threads to assign metadata. Should be higher than ReadParallel. |
|
|
workflow/metaBufferSize |
Number of resources to buffer for assigning metadata. |
|
|
workflow/typeParallel |
Number of threads to add type information. Should be higher than ReadParallel. |
|
|
workflow/typeBufferSize |
Number of resources to buffer for adding type information. |
|
|
workflow/indexParallel |
Number of threads to index the search parameters. This is typically the most resource intensive step and should have the most threads. |
|
|
workflow/indexBufferSize |
Number of resources to buffer for indexing the search parameters. |
|
|
Show version information |
||
|
Show help and usage information |
Examples
Runs the import for files located in directory /path/to/your/input/files and its subdirectories using license file /path/to/your/license/fsi-license.json targeting the database defined by the connection string. In case if a resource being imported already exists in the target database, it gets skipped.
dotnet fsi.exe \
-s ./path/to/your/input/files \
--license /path/to/your/license/fsi-license.json \
-c 'Initial Catalog=VonkData;Data Source=server.hostname,1433;User ID=username;Password=PaSSSword!' \
--update-existing-resources false
Same as above but if a resource being imported already exists in the target database, it gets updated. The old resource gets preserved as a historical record.
dotnet fsi.exe \
-s ./path/to/your/input/files \
--license /path/to/your/license/fsi-license.json \
-c 'Initial Catalog=VonkData;Data Source=server.hostname,1433;User ID=username;Password=PaSSSword!'
Monitoring
Logs
When importing the data, it is handy to have the logging enabled, as it would capture any issues if they occur. By default, the log messages are written both to the console window and to the log files in the %temp%
directory.
You can configure the log settings the same way as you do for Firely Server: Log settings.
Performance counters
You can get insights into the tool performance by means of performance counters. There are many ways to monitor the performance counters. One of the options is using dotnet-counters.
To monitor the counters for FSI, you can execute the following command:
dotnet-counters monitor --counters 'System.Runtime','FSI Processing' --process-id <process_id>
where <process_id> is the PID of the running FSI tool.
Known issues
FSI does not support scenarios where resources of different FHIR versions are stored in the same database;
When importing data from large
*.ndjson
files, the memory consumption may be quite high.
Licensing
The application is licensed separately from the core Firely Server distribution. Please contact Firely to get the license.
Your license already permits the usage of FSI if it contains http://fire.ly/vonk/plugins/bulk-data-import
.
Contact us
For questions, feedback, or consultancy for Firely Server, please send an e-mail to server@fire.ly. We look forward to hearing from you!