This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-10-21
Channels
- # announcements (1)
- # aws (18)
- # babashka (5)
- # beginners (72)
- # biff (2)
- # calva (38)
- # cider (2)
- # clj-commons (6)
- # clj-yaml (2)
- # clojars (7)
- # clojure (41)
- # clojure-austin (5)
- # clojure-europe (78)
- # clojure-nl (1)
- # clojure-norway (18)
- # clojure-uk (3)
- # clojurescript (13)
- # component (9)
- # cursive (37)
- # datahike (3)
- # datomic (7)
- # fulcro (7)
- # graphql (3)
- # holy-lambda (2)
- # honeysql (8)
- # introduce-yourself (1)
- # jobs (1)
- # kaocha (1)
- # leiningen (19)
- # lsp (104)
- # malli (5)
- # nbb (8)
- # off-topic (60)
- # polylith (22)
- # portal (2)
- # reagent (24)
- # reveal (1)
- # shadow-cljs (126)
- # test-check (11)
- # tools-build (39)
- # vim (23)
- # xtdb (10)
Hi everyone! 👋
Component's README says:
> "... a component is a collection of functions or procedures which share some runtime state."
If there's no shared state, what is the Right Way to pass transitive dependencies around to downstream functions that need them?
Example: Suppose I have a basic store web app. The Webserver
component starts up a web server and hooks up some route handlers. Those handlers call functions in the app.store
ns to (e.g.) fetch all products to render in a products list. I.e. app.store
ns functions require database access via a Db
component.
Does this mean that the Webserver
component should depend on the Db
component, and pass it along to the app.store
functions that need it? This strikes me as a bit odd, since, conceptually, the web server itself has nothing to do with the database. In other words, Webserver
starts accumulating dependencies of downstream code.
It becomes a bigger problem when (e.g.) the app.store
code starts using a new external service, represented by another component. Now the web server code needs to know about it.
Alternatively one could define a Store
component, which has Db
as a dependency, have the Webserver
depend on Store
, and pass that Store
along to app.store
functions. Those functions can then pick out the Db
component from the Store
. While the web server depending on a store makes more sense conceptually, this also feels weird/wrong, because the Store
component has no runtime state, and essentially serves only as vessel for dependencies.
Which is the Right Way, or is there a completely different and better way?
Here's an alternative that has crossed my mind, but the global state feels icky:
(ns app.store
(:require [app.db :as db]))
(defonce ^:private the-db (atom nil))
(defn list-products
[]
(db/fetch-products @the-db))
(defrecord Store [db]
component/Lifecycle
(start [this]
(reset! the-db db)
this)
(stop [this]
(reset! the-db nil)))
(defn component
[]
(component/using (map->Store {}) [:db]))
I like to make each handler it's own component, and the webserver depends on them all (possibly through an intermediate routes component)
That's clever! 💡 In that case your handler components are also "just" vessels for dependencies, so I'm taking that as a +1 for that approach.
At work I wrote a RingHandler record that has single field handler and when invoked as a function the ring handler adds any dependencies to the request map and passes it to the handler
@UJY23QLS1 We tend to have fairly narrowly-focused components. Our WebServer
is just the HTTP server itself. Our Application
is for the core, shared code, and includes caching, datasources, email templating, Redis, and several other stateful components that are used by pretty much everything. Then each web app tends to have a "system" component that combines our core Application
, the WebServer
, and any additional app-specific components (for example, our internal Admin-facing web apps have a component for dealing with O365 authentication).
(`Application` is just a collection of other common components so it can be pushed into the Ring request and made available to handlers -- for the apps that don't have a separate component for each Ring handler, which is most of them)