This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2020-10-25
Channels
- # announcements (1)
- # architecture (3)
- # beginners (31)
- # calva (61)
- # cider (1)
- # clojure (43)
- # clojure-dev (17)
- # clojure-europe (85)
- # clojure-uk (8)
- # clojurescript (31)
- # cryogen (2)
- # cursive (7)
- # data-science (12)
- # datalog (1)
- # datomic (4)
- # defnpodcast (1)
- # figwheel-main (11)
- # fulcro (32)
- # hoplon (1)
- # leiningen (1)
- # malli (47)
- # pedestal (1)
- # rdf (2)
- # re-frame (11)
- # reagent (4)
- # reitit (7)
- # shadow-cljs (22)
- # vrac (8)
- # xtdb (2)
@raymcdermott instead of DI you just def things, you mean?
It uses SAT algorithms and constraints to produce maps that have been made with the assurance that the system is feasible
Our programs don’t control the world and we just need some maps to understand how to talk to the outside world
@raymcdermott RE port reclaim, I'm thinking if you do this:
(def db (make-connection))
(def srv (jetty/run-jetty (make-handler db) {:port 3000}))
and then you change make-handler
, how do you solve that situation?None of the Clojure DI libraries really do DI anyway, they just do two things: 1) Toposort a graph 2) Run that serial set of actions, allowing for partial collected results to be collected for, e.g. shutdown on failure. I don't think that does anything like: > My serious point is that DI often introduces time into a system through the back door
use #'make-handler
. also you should probably defonce
these things in order to not lose the references.
That's fine for:
(def srv (jetty/run-jetty #'make-handler {:port 3000}))
But not for a function which returns a handler (e.g. it uses a closure for the db)Your def
solution solves the serial set of actions with partial results. And you have a human toposort rather than a computer (which I don't have a problem with, think that's great!).
@raymcdermott In our system, a lot of functions at the edges (those hooked to routes) usually have the signature: (fn [system user ...])
. system is just a map we can use to pluck out the components we need for whatever parts. None of those functions know about component.
@raymcdermott can you explain what you mean by viral?
@raymcdermott oh, you're referring to how with component records tend to propagate throughout your code?
Integrant and clip both avoid that problem by using values instead of objects with methods. Methods are called on the returned value.
For me, that’s not interesting if you accept that one or more services might not be available.
You mean the dependency ordering? Well, if you use a graph approach, you don't. If you use a def approach, so that you can def them in the right order.
A reference to the db must exist before the web server can be created, doesn't necessarily need to be up.
@dominicm Why? One can also just use a global connection pool + global db config? Devil's advocate here
@borkdude those are both references. The web server needs a handler to run on request. The handler needs some way to look up the database when it's called (it's behavior on failure is up to it).
@dominicm > The handler needs some way to look up the database when it's called This isn't an argument against the global def approach though
@borkdude nope. But the question is why do you care about ordering. We don't, except that we need an order to our references.
namespaces already take care of that and then you end up with something like mount. which has pros and cons (I'm not a fan). yeah, I guess it basically comes down to taste
Right. But I think that's a separate discussion to whether you need a topology or not.
Admittedly I can't think of a time for the db case where that's true, but I assume I could configure hikari to behave that way.
namespace vs map reification has these points:
1. Namespaces are already a tool you know how to use to incrementally build up resumable conditions that might fail. It's easy to eval something when you're part of the way through:
2. Copying, it's very hard to copy a namespace. Especially as you can't do things like merge
into it, or update
it, etc. So very hard to create deviations for things like testing or production.
3. Some tools discourage bad practices, some rely on discipline. Mount falls into the discipline category, with the whole "globally available, but pls don't use" vs component's "you have to be explicitly given things, and you have to ask for those things". The latter makes it trickier to do something "incorrect" by reaching globally for a db. This matters so you can "swap out" the database, e.g. during development, or when testing against prod from a local database, etc.
Maybe swapping out things in tests is also an anti-pattern. I usually don't go there and write tests against a real running environment
Sure, it seems integration tests are variable in utility. Different projects, different needs.
I definitely load extra components in dev, for example components for compiling sass or cljs (but that's an anti pattern too).
script/dev.clj:
#!/usr/bin/env bb
(ns dev
(:require [babashka.process :refer [$ destroy-tree *defaults*]]))
(alter-var-root #'*defaults* assoc
:out :inherit
:err :inherit
:shutdown destroy-tree)
(defn cljs []
($ "./clojure" "-A:frontend:cljs/dev"))
(defn less []
($ "./clojure" "-A:frontend:less/dev"))
(defn clojure []
^{:inherit true} ($ "./boot" "dev"))
(cljs)
(less)
(-> @(clojure) :exit (System/exit))
This very much resembles our old boot tasks, but now they are just factored out into a scriptI guess the real question is whether you should use your system library or not for tests. Whether you should is really dependent on whether your test is of a function or handler (unit, but with state, whatever that's called?). If you're testing that "on login, counter is updated", but the counter update might be async based on a job queue. So the job queue is part of the system, but you might not need to know how it's implemented as part of the system. You might want to allow a function's list of arguments to grow, e.g. To later add a job queue without having to rewrite all your tests.
Don't take that as a real stance, but I can see how someone might want to do those things.
Sometimes people use mocks, and they test that jobs end up on queues, or calls are made to services, etc. I don't personal see the value in testing those particular things, but I think it's great that you can. Why not give someone the choice?
Maybe I should try and take these and turn them into a ramble of a blog. I have loads of design notes from clip which might have something to add.
The risk would be making a new scar in this space in the clojure community. We had this discussion, it was respectful, a bit confusing, and tiring. Although perhaps we're at the point where clarity is useful.
As long as you mean "in a server application" by "all the time", then for me the "component derivatives" are the clear winners :)
(defonce srv (make-srv db))
(defonce db {})
Does work if you run it multiple times :p