Ehr-adapter: Declarative EHR integrations in Clojure

Hi, I’m Samuel Carriles Baños, and I’m currently deepening my knowledge of Clojure. Two years ago, I started out with this language—in fact, it’s the first language I ever learned—and honestly, I’ve been enjoying it a lot.

A few months ago, I started working for a small startup that handles integrations with EHR (Electronic Health Record) providers. To give you an idea, these are the systems that hospitals and clinics use to manage all patient information: medical histories, appointments, billing, etc.

Our job was to develop several small services that connected to these EHR providers. But I quickly ran into a problem: the codebase I had to work on didn’t support multi-tenancy cleanly. There was quite a bit of spaghetti code handling the interaction with each EHR, and the real headache was discovering that the operations performed against the EHR were scattered across all the namespaces in the project.

To make matters worse, I later found out that we would have to duplicate these types of connections in other services.

That was the moment I paused and told myself, “There has to be an easier way to handle this.”

I started thinking: How could I abstract all these connections to create a unified mechanism for any EHR provider? EHR integrations are notoriously complex: authentication is a unique process for each vendor. Many implement proprietary or hybrid methods, not just standard protocols like OAuth2 or SMART on FHIR. There were a ton of moving parts that became a constant headache.

But it was worth it, because that pain drove me to build a solution.


ehr-adapter

That’s how ehr-adapter was born: a Clojure library that allows you to define complete EHR integrations as simple data maps. No classes, no hidden state, no boilerplate code.

The idea is simple: instead of writing code for each EHR provider, you describe what you want to do (authentication, operations, etc.), and the library takes care of the how.

The best part is that the approach is 100% declarative. Your configuration is just a Clojure map that you can version-control, share, and modify without touching a single line of business logic.


A Concrete Example

Look at how explicit and readable a real-world configuration is:

(require 
'[babashka.http-client :as http]
'[ehr-adapter.middleware.bb-http-client :as bb-middleware]
'[ehr-adapter.middleware.jsonista :as jsonista])

(def ecw-config
  {:domain :eclinicalworks/tenant-alpha
   :base-url "https://staging-fhir.ecwcloud.com/fhir/r4/FFBJQA"
   :middlewares [bb-middleware/wrap jsonista/wrap]
   :network-config {:request-handler http/request}
   
   :auth {:initial [{:type :smart-on-fhir/backend-services
                     :client-id "your-client-id"
                     :key-id "your-kid"
                     :algorithm :rs384
                     :scopes ["system/Patient.read"]
                     :audience "https://staging-oauthserver.ecwcloud.com/oauth/oauth2/token"
                     :token-url "https://staging-oauthserver.ecwcloud.com/oauth/oauth2/token"
                     :private-key-set {:keys [{:kty "RSA" :kid "your-kid" :n "..." :e "AQAB" ...}]}}
                    {:type :normalize
                     :token [:body :access_token]
                     :token-type [:body :token_type]
                     :expires-in [:body :expires_in]}]}
   
   :operations [{:name :get-metadata
                 :method :get
                 :path "metadata"
                 :auth? false
                 :description "Get server CapabilityStatement"}
                
                {:name :get-patient
                 :method :get
                 :path ["Patient" :ref/patient-id]
                 :description "Retrieve a patient by ID"}]})

;; Initialize it once
(def adapter (ehr/initialize ecw-config))

;; And then invoke operations whenever you need them
(ehr/invoke adapter :get-metadata)
;; => {:status 200, :body {:resourceType "CapabilityStatement" ...}}

(ehr/invoke adapter :get-patient {:patient-id "12345"})
;; => {:status 200, :body {:resourceType "Patient" ...}}

That’s it. No classes to instantiate, no methods to chain, no state to manage manually. Just data.


Feature Highlights

A few things that make ehr-adapter special:

  • Flexible Authentication: Supports OAuth2, SMART on FHIR Backend Services (with JWKS), Basic Auth, and you can extend it with your own strategies. Token refreshing is completely automatic and thread-safe.
  • Partial Reference Resolution: You can define templates with placeholders (:ref/patient-id) that resolve at runtime. This allows you to reuse the same configuration for multiple patients, appointments, or any other resource.
  • Strict Validation: Everything is validated using Malli at runtime. If your configuration is wrong, you’ll know before making the first request, not in production at 3 AM.
  • Selective Operations: With the :auth? flag, you can mark which operations require authentication and which don’t. This is perfect for public endpoints like /metadata in FHIR.
  • Extensible Middlewares: The middleware architecture allows you to transform requests and responses exactly as needed. Need logging? Custom retries? Data transformation? Just plug it in as a middleware.

Current Status

The library is currently at version 2.0.1, fully functional, and battle-tested in real integrations. What started as a workaround for a specific issue has evolved into a general-purpose tool capable of adapting to any EHR provider.

It features a comprehensive test suite and is published on Clojars, ready for production use.


Wrapping Up

If you work with EHR integrations in Clojure, or if you’re just interested in seeing how to tackle abstraction problems with a data-driven approach, feel free to check it out:

I’m wide open to feedback, suggestions, and, of course, contributions. If you have an interesting use case or an EHR provider that is giving you headaches, let me know in the ehr-adapter discussions!

And if you made it this far, thanks for reading. Clojure continues to be an incredible journey.