Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Platform/13199/fhir function dynamic message routing #15273

Conversation

david-navapbc
Copy link
Collaborator

@david-navapbc david-navapbc commented Jul 20, 2024

This PR ...

Refactors how queue message routing happens such that the behavior (1) no longer hard coded (2) baked into the queue message itself. The trigger for changing default routing behavior is expected to be the something in the content of the message itself and thus it makes sense for the routing logic to be colocated with the content of the message itself.

Changes

QueueMessage has been updated with a "when" clause (switch/case for Java-heads) that sets default routing behavior for a given queue message type. Should a short circuit of this behavior be required the germane "when" block for that message type may be updated to route the message to a different queue topic. the queueAccess object is provided to the a new function, QueueMessage.send() to keep separation of concern in tact. The QueueMessage object handles routing itself with routing plumbing provided by FHIRFunctions.

QueueMessage, FhirFunctions, and associated tests have been updated.

QueueMessage was changed to attach the correct queue name to the QueueMessage obj typed for that kind of QueueMessage. Functionally speaking, the queue message itself knows where it's supposed to go.

In FhirFunctions the calls to doRoute(), doConvert(), etc have been consolidated into a single common method. The test updates were made to accommodate this change.

Checklist

Testing

  • [x ] Tested locally?
  • [x ] Ran ./prime test or ./gradlew testSmoke against local Docker ReportStream container?

@david-navapbc david-navapbc requested a review from a team as a code owner July 20, 2024 21:35
Copy link

github-actions bot commented Jul 20, 2024

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

OpenSSF Scorecard

PackageVersionScoreDetails

Scanned Manifest Files

Copy link

github-actions bot commented Jul 20, 2024

Test Results

1 201 tests  ±0   1 197 ✅ ±0   6m 18s ⏱️ -23s
  158 suites ±0       4 💤 ±0 
  158 files   ±0       0 ❌ ±0 

Results for commit 420a057. ± Comparison against base commit dafd880.

♻️ This comment has been updated with latest results.

Copy link

github-actions bot commented Jul 20, 2024

Integration Test Results

 63 files  ±0   63 suites  ±0   28m 30s ⏱️ +2s
430 tests ±0  421 ✅ ±0  9 💤 ±0  0 ❌ ±0 
433 runs  ±0  424 ✅ ±0  9 💤 ±0  0 ❌ ±0 

Results for commit 420a057. ± Comparison against base commit dafd880.

♻️ This comment has been updated with latest results.

Copy link
Collaborator

@arnejduranovic arnejduranovic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR appears to me to be very similar to the original code. We are essentially moving extra configuration from one file to another. Additionally, the original implementation has the translate queue names defined in the package space in FHIREngine, which is poor practice. This PR is on the right track, and we can go even further by exploring a solution that keeps the queue name tied to the class, and make it impossible to configure a new QueueMessage class without defining the queue name. See the proof of concept patch below that illustrates this idea:

OOP_QMessage_Cleanup_POC.patch

@david-navapbc
Copy link
Collaborator Author

@arnejduranovic I looked at the patch and I could use a 10m huddle because I think I'm missing something. If I read this right the queue name becomes a hard-coded value tied to the message type which is what I thought we were getting away from. I tied the queue name to a "when" block so we could add logic as needed to short circuit default behavior based on message content. If it's baked into the data class for a given message type then isn't it still hard coded?

@arnejduranovic
Copy link
Collaborator

@david-navapbc I think we are confusing some things. There are two parts here:

  1. The QueueMessage object IS conceptually tied to a specific queue folder. For example, TranslateQueueMessage will ALWAYS go to the translate folder in azure. We would never want TranslateQueueMessage to be tied to the convert folder, for example.
  2. The AC of the ticket is quoted below, which points at the problem we are trying to solve. Presently, we are coupling FHIRFunctions.kt doX functions to specific folder names, when (see point 1 above) the QueueMessage object they receive are conceptually tied to a particular folder already. Imagine in the future we want to make the route step potentially return a different QueueMessage based on some business logic it has, well now you need to add additional logic to doRoute to properly call sendMessage based on the concrete type of QueueMessage. This is the problem we are trying to solve for. Your current proposed changes DO fix this, but it is done in such a way where this extra logic is moved to another place instead of being eliminated. I agree your current solution does provide extra flexibility regarding tying MessageQueues to folders, but that flexibility is not needed and complicates the solution and introduces possible defects (like someone creating a new QueueMessage but forgetting to update your switch statement). With the patch I sent, the code will not compile unless you define the folder name for the QueueMessage.

FHIRFunctions no longer hardcode which queue the QueueMessage gets sent to

@david-navapbc
Copy link
Collaborator Author

copied from slack for observability

looking at the PR comment. I think I get what you're after now and yes I misunderstood the intent.
I thought the ask was to make is so a - for example - a "route" queue message could be put on the "translation" queue.
What I'm understanding now is that no we don't want that. What we want is for the "route" processing step to be able to produce different kinds of queue messages vs having the kind of queue message that results from that processing always being one thing.

NEW

[9:25]
I may still need a huddle but assuming I've got :this-forever: right let me go back and look at the code for a bit and think on it. I can then come to you with thoughtful intelligent questions and save us both some time.

Arnej Duranović
9:26 AM
What we want is for the "route" processing step to be able to produce different kinds of queue messages vs having the kind of queue message that results from that processing always being one thing. yep, this is correct

davidholiday
9:26 AM
ok cool. apologies for the misunderstanding and I get the ask now
🙌
1

9:26
lemmie stare at the code and see what I can come up with

@david-navapbc david-navapbc marked this pull request as draft July 23, 2024 16:12
@david-navapbc david-navapbc marked this pull request as ready for review July 31, 2024 16:17
Copy link
Collaborator

@brick-green brick-green left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, now a convert queue message can only go to the convert step. These changes meet the requirements as I understand them. All tests pass. Looks good :)

import java.util.Base64
import java.util.UUID

const val elrConvertQueueName = "elr-fhir-convert"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better for these strings to just exist in the concrete queuemessage classes they belong to. I don't see a reason why these constants need to be available at a package level? It's kind of a security principle, we should expose information on a need to know basis (i.e. scope things as small as possible).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

our code is open source - we're exposing all of it to the world. Also I think for testing if nothing else it's useful to have these at the top level vs baked into a particular message class.

Happy to make the change though if you feel strongly.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We currently have a requirement (implicit) that specific queue messages should go to specifically named queues in Azure. So, we should "hardcode" these in the concrete queue message classes and make it so the caller cannot change the QueueName. I don't see testing or open source considerations outweighing this requirement in this case.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok done

@@ -96,6 +112,7 @@ data class FhirConvertQueueMessage(
override val blobSubFolderName: String,
override val topic: Topic,
val schemaName: String = "",
override val messageQueueName: String = elrConvertQueueName,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not how the suggested patch set the messageQueueName:

@JsonTypeName("convert")
data class FhirConvertQueueMessage(
    override val reportId: ReportId,
    override val blobURL: String,
    override val digest: String,
    override val blobSubFolderName: String,
    override val topic: Topic,
    val schemaName: String = "",
) : ReportPipelineMessage() {
    override val messageQueueName = "testing"
}

Did the above not work for some reason?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are they not functionally equivalent? what's the difference that makes a difference in having the override messageQueueName in the argument list vs as part of the body?

Happy to do whatever - just curious as to what the issue is

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue is the caller can pass in a different messageQueueName with the way you have it now and we do not want to allow that ability. When a queue message is designed, we know at design time what the name of the azure queue should be so we should prevent it from being changed by the caller -- which is what the snippet I posted does.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -244,12 +244,13 @@ class FhirFunctionTests {
emptyMap(),
emptyList()
)
val message = FhirRouteQueueMessage(
val message = FhirTranslateQueueMessage(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the story here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the test logic wasn't testing what it was supposed to be testing

David Holiday added 3 commits August 6, 2024 11:58
…f github.com:CDCgov/prime-reportstream into platform/13199/fhir_function_dynamic_message_routing
Copy link

Branch deployed to Chromatic 🚀.

  • ⚠️ Detected 0 tests with visual changes.
  • ✅ All tests passed.

View via:

Copy link

⚠️ Broken Links ⚠️

https://cdc.gov/

Error: timeout of 10000ms exceeded


https://www.cdc.gov/about/default.htm

Error: timeout of 10000ms exceeded


https://www.cdc.gov/other/accessibility.html

Error: timeout of 10000ms exceeded


https://www.cdc.gov/od/foia

Error: timeout of 10000ms exceeded


https://www.cdc.gov/eeo/nofearact/index.htm

Error: timeout of 10000ms exceeded


https://oig.hhs.gov/

Error: timeout of 10000ms exceeded


https://www.cdc.gov/other/privacy.html

Error: timeout of 10000ms exceeded


https://www.hhs.gov/vulnerability-disclosure-policy/index.html

Error: timeout of 10000ms exceeded


http://localhost:4173/terms-of-service

Error: timeout of 10000ms exceeded


https://www.usa.gov/

Error: timeout of 10000ms exceeded


https://www.cdc.gov/other/information.html

Error: timeout of 10000ms exceeded


https://www.cms.gov/Research-Statistics-Data-and-Systems/Computer-Data-and-Systems/Privacy/PrivacyActof1974.html

Error: timeout of 10000ms exceeded


http://localhost:4173/terms-of-service#anchor-top

Error: timeout of 10000ms exceeded


https://www.cdc.gov/ophdst/public-health-data-strategy/index.html

Error: timeout of 10000ms exceeded


https://www.cdc.gov/surveillance/data-modernization/index.html

Error: timeout of 10000ms exceeded


https://digital.gov/topics/user-centered-design/

Error: timeout of 10000ms exceeded


http://localhost:4173/assets/pdf/ReportStream_Onepager_20240605.pdf

Error: timeout of 10000ms exceeded


http://localhost:4173/developer-resources/api

Error: timeout of 10000ms exceeded


https://commons.wikimedia.org/wiki/File:Blank_USA,_w_territories.svg

Error: timeout of 10000ms exceeded


http://localhost:4173/managing-your-connection/refer-healthcare-organizations

Error: timeout of 10000ms exceeded


http://localhost:4173/about/our-network#top

Error: timeout of 10000ms exceeded


https://www.cdc.gov/epidemiology-laboratory-capacity/php/about/

Error: timeout of 10000ms exceeded


https://www.cdc.gov/vaccines/programs/iis/technical-guidance/downloads/hl7guide-1-5-2014-11.pdf

Error: timeout of 10000ms exceeded


https://learn.makemytestcount.org/

Error: timeout of 10000ms exceeded


https://www.cdph.ca.gov/Programs/CID/DCDC/Pages/CalREDIE.aspx

Error: timeout of 10000ms exceeded


https://csrc.nist.gov/Projects/risk-management/fisma-background

Error: timeout of 10000ms exceeded


https://csrc.nist.gov/projects/risk-management

Error: timeout of 10000ms exceeded


https://www.fedramp.gov/program-basics/

Error: timeout of 10000ms exceeded


http://localhost:4173/developer-resources/api/documentation/data-model

Error: timeout of 10000ms exceeded


https://github.com/CDCgov/prime-reportstream/releases

Error: timeout of 10000ms exceeded


https://help.okta.com/oie/en-us/content/topics/identity-engine/oie-index.htm

Error: timeout of 10000ms exceeded


http://localhost:4173/daily-data

Error: timeout of 10000ms exceeded


http://localhost:4173/submissions

Error: timeout of 10000ms exceeded


http://localhost:4173/data-dashboard

Error: timeout of 10000ms exceeded


https://github.com/CDCgov/prime-reportstream/blob/e20ac26b0e0be31967fc65def7401382ef786d98/prime-router/examples/generate-jwt-python/jwt-errors.md

Error: timeout of 10000ms exceeded


http://localhost:4173/file-handler/validate

Error: timeout of 10000ms exceeded


http://localhost:4173/manage-public-key

Error: timeout of 10000ms exceeded


http://localhost:4173/about/release-notes#top

Error: timeout of 10000ms exceeded


https://usds.gov/

Error: timeout of 10000ms exceeded


http://localhost:4173/about/case-studies#top

Error: timeout of 10000ms exceeded


https://app.smartsheetgov.com/b/form/da894779659b45768079200609b3a599

Error: timeout of 10000ms exceeded


http://localhost:4173/managing-your-connection/refer-healthcare-organizations#email-template

Error: timeout of 10000ms exceeded


https://www.simplereport.gov/getting-started/

Error: timeout of 10000ms exceeded


http://reportstream.cdc.gov/

Error: timeout of 10000ms exceeded


http://simplereport.gov/

Error: timeout of 10000ms exceeded


https://reportstream.cdc.gov/

Error: timeout of 10000ms exceeded


https://www.simplereport.gov/getting-started/organizations-and-testing-facilities/onboard-your-organization/

Error: timeout of 10000ms exceeded


https://simplereport.gov/

Error: timeout of 10000ms exceeded


http://localhost:4173/managing-your-connection/refer-healthcare-organizations#top

Error: timeout of 10000ms exceeded


http://localhost:4173/getting-started

Error: timeout of 10000ms exceeded


https://app.smartsheetgov.com/b/form/8c71931f25e64e42bf1fef32900bdecd

Error: timeout of 10000ms exceeded


http://localhost:4173/developer-resources/api/getting-started

Error: timeout of 10000ms exceeded


http://localhost:4173/assets/pdf/ReportStream_Onepager_20230621.pdf

Error: timeout of 10000ms exceeded


http://localhost:4173/getting-started/sending-data#top

Error: timeout of 10000ms exceeded


http://localhost:4173/getting-started/receiving-data#anchor-what-you-need

Error: timeout of 10000ms exceeded


https://app.smartsheetgov.com/b/form/b0935d5d1e924c57b2d293b4ed0f2cd5

Error: timeout of 10000ms exceeded


https://hl7-definition.caristix.com/v2/HL7v2.5/Segments/OBX

Error: timeout of 10000ms exceeded


http://localhost:4173/getting-started/receiving-data#top

Error: timeout of 10000ms exceeded


https://github.com/CDCgov/prime-reportstream

Error: timeout of 10000ms exceeded


https://github.com/CDCgov/prime-reportstream/tree/master

Error: timeout of 10000ms exceeded


https://github.com/CDCgov/prime-reportstream/blob/master/prime-router/docs/README.md

Error: timeout of 10000ms exceeded


http://localhost:4173/developer-resources/api/documentation

Error: timeout of 10000ms exceeded


http://localhost:4173/developer-resources/api/documentation/responses-from-reportstream

Error: timeout of 10000ms exceeded


http://localhost:4173/developer-resources/api/documentation/responses-from-reportstream#response-messages

Error: timeout of 10000ms exceeded


http://localhost:4173/developer-resources/api/documentation/responses-from-reportstream#json-error-responses

Error: timeout of 10000ms exceeded


http://localhost:4173/developer-resources/api/documentation/sample-payloads-and-output

Error: timeout of 10000ms exceeded


http://localhost:4173/developer-resources/api/documentation/sample-payloads-and-output#sample-hl7-251-payload-and-output

Error: timeout of 10000ms exceeded


http://localhost:4173/assets/pdf/ReportStream-Programmers-Guide-v4.5.pdf

Error: timeout of 10000ms exceeded


http://localhost:4173/developer-resources/api/getting-started#set-up-authentication

Error: timeout of 10000ms exceeded


https://www.hl7.org/implement/standards/product_brief.cfm?product_id=185

Error: timeout of 10000ms exceeded


https://www.nibib.nih.gov/covid-19/radx-tech-program/mars/hl7v2-getting-started

Error: timeout of 10000ms exceeded


https://hl7v2-gvt.nist.gov/gvt/#/home

Error: timeout of 10000ms exceeded


https://github.com/CDCgov/prime-reportstream/tree/master/prime-router/examples/generate-jwt-python/

Error: timeout of 10000ms exceeded


https://github.com/CDCgov/prime-reportstream/tree/master/prime-router/examples/generate-jwt-python/jwt-errors.md

Error: timeout of 10000ms exceeded


https://github.com/CDCgov/prime-reportstream/blob/master/prime-router/docs/api

Error: timeout of 10000ms exceeded


http://localhost:4173/developer-resources/api/documentation/responses-from-reportstream#errors-and-warnings

Error: timeout of 10000ms exceeded


http://localhost:4173/assets/hl7/Example-hl7-file.hl7

Error: timeout of 10000ms exceeded


http://localhost:4173/support#will-i-ever-have-to-pay-to-use-reportstream

Error: timeout of 10000ms exceeded


http://localhost:4173/support#what-conditions-can-i-send-or-receive

Error: timeout of 10000ms exceeded


http://localhost:4173/support#welcome-to-reportstream-1

Error: timeout of 10000ms exceeded


http://localhost:4173/support#how-do-i-map-to-the-livd-table

Error: timeout of 10000ms exceeded


http://localhost:4173/support#how-do-i-set-up-my-public-private-key-pair

Error: timeout of 10000ms exceeded


http://localhost:4173/support#getting-started-1

Error: timeout of 10000ms exceeded


http://localhost:4173/support#how-do-i-update-or-reset-my-password

Error: timeout of 10000ms exceeded


http://localhost:4173/support#how-can-i-start-sending-or-receiving-more-conditions

Error: timeout of 10000ms exceeded


http://localhost:4173/support#using-reportstream-1

Error: timeout of 10000ms exceeded


https://www.cdc.gov/csels/dls/livd-codes.html

Error: timeout of 10000ms exceeded


http://localhost:4173/support#how-do-i-know-if-a-device-is-considered-otc-over-the-counter

Error: timeout of 10000ms exceeded


https://www.cdc.gov/covid/php/lab/reporting-lab-data.html

Error: timeout of 10000ms exceeded


https://www.cdc.gov/poxvirus/mpox/lab-personnel/report-results.html

Error: timeout of 10000ms exceeded


https://www.nibib.nih.gov/covid-19/radx-tech-program/mars/HL7v2-implementation-guide

Error: timeout of 10000ms exceeded


https://loinc.org/get-started/what-loinc-is/

Error: timeout of 10000ms exceeded


https://www.fda.gov/medical-devices/home-use-devices/frequently-asked-questions-about-home-use-devices

Error: timeout of 10000ms exceeded


http://localhost:4173/support#top

Error: timeout of 10000ms exceeded


Copy link

sonarcloud bot commented Aug 13, 2024

Quality Gate Failed Quality Gate failed

Failed conditions
59.5% Coverage on New Code (required ≥ 80%)
5.1% Duplication on New Code (required ≤ 3%)

See analysis details on SonarCloud

filterManager,
isLoading,
} = useReceiverDeliveries(activeReceiver.name);
const { data: results, filterManager, isLoading } = useReceiverDeliveries(activeReceiver.name);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to figure this out, but your PR should not be touching any .tsx files. That is outside the scope of platform work.

@@ -351,7 +350,7 @@ class UniversalPipelineReceiver : SubmissionReceiver {
if (sender.customerStatus != CustomerStatus.INACTIVE) {
// move to processing (send to <elrProcessQueueName> queue)
workflowEngine.queue.sendMessage(
elrConvertQueueName,
"elr-fhir-convert",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should read this value from one place instead of typing the string in multiple spots. see comment about bringing back the constants.

@@ -40,33 +35,12 @@ class FHIRFunctions(
@FunctionName("convert-fhir")
@StorageAccount("AzureWebJobsStorage")
fun convert(
@QueueTrigger(name = "message", queueName = elrConvertQueueName)
@QueueTrigger(name = "message", queueName = "elr-fhir-convert")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For all of these, read from the appropriate queue object.

@@ -23,12 +23,6 @@ import gov.cdc.prime.router.serializers.Hl7Serializer
import org.jooq.Field
import java.time.OffsetDateTime

const val elrConvertQueueName = "elr-fhir-convert"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Easiest/ least disruptive solution to the problem of defining these strings all over probably is to move these constants to the static area of QueueMessage (inside object), then you can read the value from there instead of defining the string multiple times. Still solves the issue of these being defined at a package level.

@arnejduranovic
Copy link
Collaborator

replaced by #15613. Closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
platform Platform Team
Projects
Development

Successfully merging this pull request may close these issues.

3 participants