public virtual async Task <string> SaveObservationAsync(ILookupTemplate <IFhirTemplate> config, IObservationGroup observationGroup, IDictionary <ResourceType, string> ids) { var identifier = GenerateObservationIdentifier(observationGroup, ids); var cacheKey = $"{identifier.System}|{identifier.Value}"; if (!_observationCache.TryGetValue(cacheKey, out Model.Observation existingObservation)) { existingObservation = await GetObservationFromServerAsync(identifier).ConfigureAwait(false); // Discovered an issue where FHIR Service is only matching on first 128 characters of the identifier. This is a temporary measure to prevent merging of different observations until a fix is available. if (existingObservation != null && !existingObservation.Identifier.Exists(i => i.IsExactly(identifier))) { throw new NotSupportedException("FHIR Service returned matching observation but expected identifier was not present."); } } var policyResult = await Policy <(Model.Observation observation, ResourceOperation operationType)> .Handle <FhirException>(ex => ex.StatusCode == System.Net.HttpStatusCode.Conflict || ex.StatusCode == System.Net.HttpStatusCode.PreconditionFailed) .RetryAsync(2, async(polyRes, attempt) => { // 409 Conflict or 412 Precondition Failed can occur if the Observation.meta.versionId does not match the update request. // This can happen if 2 independent processes are updating the same Observation simultaneously. // or // The update operation failed because the Observation no longer exists. // This can happen if a cached Observation was deleted from the FHIR Server. _logger.LogTrace("A conflict or precondition caused an Observation update to fail. Getting the most recent Observation."); // Attempt to get the most recent version of the Observation. existingObservation = await GetObservationFromServerAsync(identifier).ConfigureAwait(false); // If the Observation no longer exists on the FHIR Server, it was most likely deleted. if (existingObservation == null) { _logger.LogTrace("A cached version of an Observation was deleted. Creating a new Observation."); // Remove the Observation from the cache (this version no longer exists on the FHIR Server. _observationCache.Remove(cacheKey); } }) .ExecuteAndCaptureAsync(async() => { if (existingObservation == null) { var newObservation = GenerateObservation(config, observationGroup, identifier, ids); return(await _fhirService.CreateResourceAsync(newObservation).ConfigureAwait(false), ResourceOperation.Created); } // Merge the new data with the existing Observation. var mergedObservation = MergeObservation(config, existingObservation, observationGroup); // Check to see if there are any changes after merging and update the Status to amended if changed. if (!existingObservation.AmendIfChanged(mergedObservation)) { // There are no changes to the Observation - Do not update. return(existingObservation, ResourceOperation.NoOperation); } // Update the Observation. Some failures will be handled in the RetryAsync block above. return(await _fhirService.UpdateResourceAsync(mergedObservation).ConfigureAwait(false), ResourceOperation.Updated); }).ConfigureAwait(false); var exception = policyResult.FinalException; if (exception != null) { throw exception; } var observation = policyResult.Result.observation; _logger.LogMetric(IomtMetrics.FhirResourceSaved(ResourceType.Observation, policyResult.Result.operationType), 1); _observationCache.CreateEntry(cacheKey) .SetAbsoluteExpiration(DateTimeOffset.UtcNow.AddHours(1)) .SetSize(1) .SetValue(observation) .Dispose(); return(observation.Id); }