Generic remote-port adapter: implement any module protocol over HTTP.
Cross-module calls go through a protocol, so slicing a module into its own
process needs one thing the codebase did not have — an implementation of
that protocol that makes a network call. This builds one for an arbitrary
protocol, so the caller keeps calling (ports/create-checkout-session svc …)
and does not learn that the answer now comes over a socket (BOU-90).
Modelled on libs/external's outbound adapters: clj-http, :throw-exceptions false, errors as data rather than exceptions.
FC/IS: shell. The wire contract is pure and lives in
wagoe.platform.core.rpc.
Generic remote-port adapter: implement any module protocol over HTTP. Cross-module calls go through a protocol, so slicing a module into its own process needs one thing the codebase did not have — an implementation of that protocol that makes a network call. This builds one for an arbitrary protocol, so the caller keeps calling `(ports/create-checkout-session svc …)` and does not learn that the answer now comes over a socket (BOU-90). Modelled on libs/external's outbound adapters: clj-http, `:throw-exceptions false`, errors as data rather than exceptions. FC/IS: shell. The wire contract is pure and lives in `wagoe.platform.core.rpc`.
(call! base-url operation args {:keys [context] :as opts})Invoke operation on the service at base-url with positional args.
context supplies correlation-id / tenant / auth to propagate. Retries only
the failures a retry can fix — see default-opts.
Returns whatever the remote protocol method returned, or an {:error …} map
for a transport failure. An exception the remote implementation threw is
raised again here rather than returned — see raise-if-thrown!.
Invoke `operation` on the service at `base-url` with positional `args`.
`context` supplies correlation-id / tenant / auth to propagate. Retries only
the failures a retry can fix — see `default-opts`.
Returns whatever the remote protocol method returned, or an `{:error …}` map
for a transport failure. An exception the remote implementation threw is
raised again here rather than returned — see `raise-if-thrown!`.(protocol-operations protocol)The method names a protocol declares, as keywords.
Read from the protocol's own :sigs, so a method added to a port is carried
without editing anything here — the adapter is generic or it is not useful.
The method names a protocol declares, as keywords. Read from the protocol's own `:sigs`, so a method added to a port is carried without editing anything here — the adapter is generic or it is not useful.
(remote-adapter protocol base-url & [opts])A value implementing protocol by calling the service at base-url.
The returned object satisfies the protocol, so a caller cannot tell it from the in-process implementation — which is the point: the protocol is the contract, and where a module runs is deployment, not code.
Built from the protocol's own :sigs rather than written out per protocol,
so every module's port works without a bespoke client.
Each adapter is its own object implementing that protocol's interface. The
obvious alternative — extend on one shared record type — is wrong in a way
that shows up only once a second protocol is adapted: extend mutates the
class, so every adapter already built starts satisfying the new protocol
too. A payments adapter would answer satisfies? for ICache, and cache
calls made through it would be posted to the payments URL. Nothing would
report a problem until a remote service was asked for an operation it has
never heard of.
Args: protocol - the protocol map, e.g. wagoe.payments.ports/IPaymentProvider base-url - service root, e.g. "http://payments:3001" opts - :path :service-key :timeout-ms :retries :retry-delay-ms :retry-on :context
:path defaults to "/rpc". Set it to "/api/v1/rpc" if the service mounts
the handler as an :api route, since versioning rewrites those.
Example: (remote-adapter payments-ports/IPaymentProvider "http://payments:3001" {})
A value implementing `protocol` by calling the service at `base-url`.
The returned object satisfies the protocol, so a caller cannot tell it from
the in-process implementation — which is the point: the protocol is the
contract, and where a module runs is deployment, not code.
Built from the protocol's own `:sigs` rather than written out per protocol,
so every module's port works without a bespoke client.
Each adapter is its own object implementing that protocol's interface. The
obvious alternative — `extend` on one shared record type — is wrong in a way
that shows up only once a second protocol is adapted: `extend` mutates the
class, so every adapter already built starts satisfying the new protocol
too. A payments adapter would answer `satisfies?` for `ICache`, and cache
calls made through it would be posted to the payments URL. Nothing would
report a problem until a remote service was asked for an operation it has
never heard of.
Args:
protocol - the protocol map, e.g. wagoe.payments.ports/IPaymentProvider
base-url - service root, e.g. "http://payments:3001"
opts - :path :service-key :timeout-ms :retries :retry-delay-ms
:retry-on :context
`:path` defaults to "/rpc". Set it to "/api/v1/rpc" if the service mounts
the handler as an `:api` route, since versioning rewrites those.
Example:
(remote-adapter payments-ports/IPaymentProvider "http://payments:3001" {})cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |