Table of Contents
- Background
- Requirements
- How it works today
- Proposed Redesign
- FHIR-Server and Subscription Server Configuration
- FHIR-Server Startup
- Subscription Server Startup
- Subscription arrives on FHIR-Server
- Subscription arrives at Subscription Server
- Resource arrives at Subscription Server
- Backwards compatibility
Background
In the fall of 2018, we modified the HAPI-FHIR Server Subscription functionality to add support for running subscription processing in a separate server from the REST server. This happened in stages:
In-memory matcher added a new class SubscriptionMatcherInMemory that matches incoming resources against subscription criteria by examining the content within the resource directly rather than querying the database.
Standalone subscription added a new maven module called hapi-fhir-jpaserver-subscription and moved classes there from hapi-fhir-jpaserver-base so that the SubscriptionMatcherInMemory had everything it needed to match resources against subscription criteria without access to a database. Classes needed by both hapi-fhir-jpaserver-subscription and hapi-fhir-jpaserver-base were moved into two new maven modules called hapi-fhir-jpaserver-model and hapi-fhir-jpaserver-searchparam.
One final change remains in order to be able to process subscription outside of the REST server: maintaining an in-memory cache of active subscriptions.
For simplicity, I don't cover the DELETE case below. Assume that DELETE undos whatever the CREATE did.
Requirements
Four things need to happen when a new subscription arrives on the REST server:
- Validate the criteria on the subscription and reject it if the criteria are invalid.
- Activate the subscription (that is change the status of the subscription from REQUESTED to ACTIVE)
- Add the subscription to an in-memory cache of active subscriptions that future resources are matched against.
- Create a new Delivery Queue and DeliveryHandler. Matching resources will be sent to that queue and sent to the end point by the DeliveryHandler. The DeliveryHandler will perform the action specified in the subscription, e.g. e.g. send an e-mail, make a REST call, etc.
How it works today
Currently, all of four of these functions are managed within HAPI-FHIR Interceptors. Most of the functionality resides within the abstract class BaseSubscriptionInterceptor. There are subclasses of this for each type of subscription: SubscriptionRestHookInterceptor, SubscriptionEmailInterceptor etc... When the HAPI-FHIR JPA Server starts up, subscription processing functionality is added to it by enabling one or more of these interceptors.
Interceptor startup
When the interceptor starts up, it creates the following objects that it stores internally within itself:
- A Processing Queue for processing incoming resources
- SubscriptionActivatingSubscriber responsible for extra processing on incoming subscriptions
- Three subscription caches: active subscriptions, subscription channels, and delivery handlers
- A SubscriptionCheckingSubscriber responsible for matching incoming resources against cached subscriptions
Then at startup, the Interceptor reads the database and for each subscription of this interceptor type, it performs the "Subscription arrives" operation described below.
Effectively there are multiple subscription caches--one for each type of interceptor. Further, only subscriptions for which there is a registered interceptor, will be activated. (e.g. if there is an EmailInterceptor and a RestHook subscription arrives, it won't be activated.) This subscription-type based activation, helps inform end-users which kinds of subscriptions are supported by this server.
Subscription arrives
This is the extra processing that the SubscriptionActivatingSubscriber does on incoming subscription resources:
- Validate the criteria, and reject the CREATE/UPDATE if it's invalid.
- Ignore subscriptions not of the type of this interceptor (e.g. email subscriptions ignored by rest interceptor)
- If the subscription is REQUESTED, change it to ACTIVE and send it back the interceptor to be processed a second time.
- If the subscription is ACTIVE, create a Delivery Queue and a DeliveryHandler (the interceptor knows what kind of handler to create) and populate the three subscription hashes in the interceptor.
Resource arrives
When a resource arrives, it is sent to each type of interceptor. Each interceptor does the following:
- Submit the resource to the Processing Queue
- Then in a separate thread, the SubscriptionCheckingSubscriber for this interceptor pulls the resource from the Processing Queue and does the following.
- Loop through all the subscriptions in that Interceptor's cache and check if the resource matches it.
- If the resource matches, it sends it to the Delivery Queue belonging to that subscription.
- The DeliveryHandler then pulls it from the Delivery Queue and sends it out as e-mail, a rest call, etc.
Proposed Redesign
In a configuration where subscription matching happens in a separate server from the REST server, it will work as follows. The following process below assumes that all resources successfully processed by the FHIR-Server are also sent to the Subscription Server. The process of how resources are sent to the Subscription Server is not covered here.
FHIR-Server and Subscription Server Configuration
Both the FHIR Server and the Subscription Server will have a new configuration attribute that contains a list of Subscription types supported by this cluster of FHIR servers.
FHIR-Server Startup
A new SubscriptionActivatingInterceptor at startup activates any supported subscriptions in the database that need to be activated. It then processes subscription resources as described below.
Subscription Server Startup
A new SubscriptionRegistry will maintain 3 caches for all supported subscriptions (active subscription, delivery queue, delivery handler). When it starts up, it loads the caches with all supported subscriptions. (There are no longer separate caches for each type).
Subscription arrives on FHIR-Server
- SubscriptionActivatingInterceptor validates the criteria, and reject the CREATE/UPDATE if it's invalid.
- If the subscription is REQUESTED, change it to ACTIVE and send it back to itself to be processed a second time.
Subscription arrives at Subscription Server
- If the subscription not ACTIVE, ignore it.
- Otherwise, the SubscriptionRegistry creates a Delivery Queue and a DeliveryHandler based on the type of Subscription (e.g. an email DeliveryHandler for eMail subscriptions) and populates its three subscription hashes.
Resource arrives at Subscription Server
- Loop through all the subscriptions in the SubscriptionRegistry cache and check if the resource matches it.
- If the resource matches, it sends it to the Delivery Queue belonging to that subscription.
- The DeliveryHandler then pulls it from the Delivery Queue and sends it out as e-mail, a rest call, etc.
Backwards compatibility
In the case where the FHIR Server and the Subscription server are on the same server, it will still work in this new way. The main differences are that:
- The new config parameter is used to determine which types of subscriptions are supported (rather than the presence of an interceptor of that type)
- All subscriptions will be looped over in one thread rather than the looping happening in different threads for different subscription types.