Best Practices
Keep FireSonic workflows focused, deterministic, testable, and safe to operate.
Use these guidelines to keep FireSonic integrations focused, deterministic, testable, safe under concurrency, and easy to operate in production.
Keep lifecycle hooks focused
| Hook | Recommended logic | Avoid |
|---|---|---|
onStartup() | Validate configuration, create reusable clients/caches, initialize reference data. | Per-message work or unnecessary production writes. |
shouldProcess() | Fast, intentional filtering decisions. | Mutating HL7, slow lookups, or heavy external calls. |
preConvert() | Deterministic HL7 normalization and correction. | FHIR Bundle manipulation. |
postConvert() | FHIR enrichment and workflow-specific resource logic. | Semantic validation rules that belong in the rule DSL. |
postValidation() | Logging, auditing, notifications, metrics, and issue reporting. | Modifying the generated Bundle. |
onShutdown() | Release project-owned resources when necessary. | Assuming it will run after every individual message. |
Error handling
- Use
FilterResult.reject(reason)for intentional filtering rather than throwing an exception. - Throw when an unexpected invalid state or required dependency prevents correct processing.
- Retry only transient operations that are safe to execute again.
- Use
ScriptSupport.withRetries(...)orrunWithRetries(...)explicitly rather than hiding retry behavior inside helpers. - Log enough message context for diagnosis: internal message ID, MSH-10, message code, trigger event, and the failed operation.
- Do not log credentials, bearer tokens, passwords, or authorization headers.
HL7 modification
- Prefer
HL7Helperfor common path-based reads and writes. - Keep normalization deterministic: the same input and configuration should produce the same normalized message.
- Do not erase clinically meaningful data without an explicit customer requirement.
- When clearing or rebuilding segments, verify that downstream mapping logic correctly handles empty or absent values.
- Keep organization-specific normalization in scripts rather than changing the generic platform mapper.
FHIR operations
- Prefer normal platform delivery for the final HL7 → FHIR transaction Bundle.
- Use
FhirClientfor FHIR searches, terminology operations, and explicit customer-specific FHIR calls. - Use
original()only when the simplified SDK API does not cover the required HAPI FHIR operation. - Remember that
original()uses the underlying HAPI client directly and bypasses SDK wrapper behavior. - Design custom writes to be idempotent where possible so retries do not create duplicate side effects.
- Keep external FHIR dependencies out of
shouldProcess()unless the filtering decision genuinely requires them.
See FHIR Client for the managed FHIR API and direct HAPI-access guidance.
Semantic validation rules
- Keep each semantic rule focused on one business or interoperability requirement.
- Prefer
appliesTo(...)scopes so rules execute only for relevant HL7 workflows. - Use global rules only for requirements that truly apply to every converted message.
- Use stable, descriptive rule IDs, titles, descriptions, categories, and failure messages.
- Keep validation deterministic and read-only; do not mutate FHIR resources from semantic rules.
- Prefer
ctx.each(),ctx.exists(),ctx.findAll(), andctx.count()over manual Bundle traversal. - Report findings through
SemanticIssueCollectorinstead of constructing validation results manually. - Reserve
ERRORfor conditions that must block FHIR delivery. UseWARNINGorINFOfor non-blocking findings.
See Semantic Rules for scopes, validation context, issue reporting, and real-world examples.
HTTP client
- Create managed HTTP clients once during startup and reuse them across hook executions.
- Use stable client names such as
config,notifications,audit, orworkflow-apifor clearer diagnostics. - Use synchronous calls only when the current processing decision depends on the response.
- Use
postJsonAsync()for non-critical best-effort notifications that should not block message processing. - Keep async callbacks lightweight and ensure any shared objects they touch are thread-safe.
- Configure bounded connection/request timeouts and a suitable
maxResponseBytes. - Retry only operations that are safe to repeat, and explicitly decide which HTTP failures are retryable.
- Keep credentials in environment variables or deployment secret stores and never log them.
See HTTP Client for response/error semantics and retry behavior.
Caches and shared state
- Prefer SDK cache abstractions over hand-built mutable maps for shared reference and workflow state.
- Create reusable caches during
onStartup()rather than once per message. - Use bounded cache sizes and appropriate TTLs for data that can grow.
- Use refreshing caches for periodically refreshed reference data from FHIR or external systems.
- Do not use in-memory caches as durable persistence or as a replacement for FireSonic message storage.
Testing and maintainability
- Keep reusable normalization and workflow logic in small classes/functions that can be tested independently.
- Maintain representative HL7 samples for normal, edge-case, and failure-path behavior.
- Write unit tests for hooks/helpers, focused tests for semantic rules, and SDK integration tests for HL7 → FHIR behavior.
- Turn production incidents and discovered edge cases into regression tests.
- Keep configuration externalized so the same tested code can move between development, test, and production environments.
- Version scripts and rules in Git and deploy known-good source revisions.
Separate responsibilities
Keep HL7 normalization, FHIR enrichment, semantic validation, notifications, and external-service integration in the appropriate layer. Clear responsibility boundaries make workflows easier to test, review, troubleshoot, and upgrade.
