clojure-europe

For people in Europe... or elsewhere... UGT https://indieweb.org/Universal_Greeting_Time
dominicm 2020-10-25T06:56:07.233700Z

@raymcdermott instead of DI you just def things, you mean?

dominicm 2020-10-25T06:56:50.234700Z

How do you stop them, to reclaim the port?

raymcdermott 2020-10-25T09:04:04.235900Z

I don’t care about that. Why should I

raymcdermott 2020-10-25T09:09:57.236800Z

My other library - going the opposite direction - is satisfactory

raymcdermott 2020-10-25T09:11:19.238800Z

It uses SAT algorithms and constraints to produce maps that have been made with the assurance that the system is feasible

raymcdermott 2020-10-25T09:13:45.240Z

My serious point is that DI often introduces time into a system through the back door

raymcdermott 2020-10-25T09:14:48.241700Z

Our programs don’t control the world and we just need some maps to understand how to talk to the outside world

2020-10-25T09:17:46.242300Z

How do you create an environment to not care about it then?

raymcdermott 2020-10-25T09:20:36.242600Z

Terraform

raymcdermott 2020-10-25T09:49:05.243500Z

DI mixes things up in a confusing manner imho

raymcdermott 2020-10-25T09:50:16.245Z

Test fixtures can still have start / stop semantics of course using some data in a map

dominicm 2020-10-25T11:12:47.245100Z

@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?

dominicm 2020-10-25T11:12:55.245200Z

(same sort of thing applies to tests though)

dominicm 2020-10-25T11:15:23.245300Z

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

borkdude 2020-10-25T11:16:24.246Z

use #'make-handler. also you should probably defonce these things in order to not lose the references.

dominicm 2020-10-25T11:16:35.246100Z

@borkdude That doesn't work here :)

dominicm 2020-10-25T11:17:10.246400Z

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)

borkdude 2020-10-25T11:17:16.246600Z

yeah you're right. it needs start/stop and I like component for this

borkdude 2020-10-25T11:18:02.247Z

we do a lot of useful stuff in our start/stop, like flushing redis caches etc

borkdude 2020-10-25T11:18:42.247500Z

but we also have a separate function for this: (clear-cache [system ...])

dominicm 2020-10-25T11:18:52.247600Z

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!).

borkdude 2020-10-25T11:19:19.248Z

The def solution is basically just the mount solution without any other benefits

👍 2
raymcdermott 2020-10-25T11:25:36.248900Z

I don’t need libs with benefits ;-)

raymcdermott 2020-10-25T11:26:37.250200Z

some of the other problems with Clojure DI libs esp component is that it’s viral

raymcdermott 2020-10-25T11:27:20.251400Z

I know that they’re not all like that but that part is unacceptable to me

borkdude 2020-10-25T11:28:26.252600Z

@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.

dominicm 2020-10-25T11:29:04.253100Z

@raymcdermott can you explain what you mean by viral?

raymcdermott 2020-10-25T11:33:40.253900Z

It’s a whole system rather than something at the edge

raymcdermott 2020-10-25T11:34:29.255100Z

So I’m with @borkdude on the general approach to limit exposure

dominicm 2020-10-25T11:38:05.255900Z

@raymcdermott oh, you're referring to how with component records tend to propagate throughout your code?

dominicm 2020-10-25T11:39:03.257300Z

Integrant and clip both avoid that problem by using values instead of objects with methods. Methods are called on the returned value.

raymcdermott 2020-10-25T11:39:27.257500Z

I know

dominicm 2020-10-25T11:41:29.257800Z

Oops, I missed that.

raymcdermott 2020-10-25T11:42:40.258700Z

Why do you need to understand the topology?

raymcdermott 2020-10-25T11:46:43.262300Z

For me, that’s not interesting if you accept that one or more services might not be available.

dominicm 2020-10-25T11:46:54.262700Z

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.

dominicm 2020-10-25T11:47:46.264200Z

Hmm, system frameworks and service availability doesn't seem related?

raymcdermott 2020-10-25T11:49:14.265900Z

Question is must DB start before web server...

raymcdermott 2020-10-25T11:50:03.267100Z

If that’s a criterion they are related and they shouldn’t be imho

dominicm 2020-10-25T11:53:16.268500Z

A reference to the db must exist before the web server can be created, doesn't necessarily need to be up.

borkdude 2020-10-25T11:54:28.269100Z

@dominicm Why? One can also just use a global connection pool + global db config? Devil's advocate here

dominicm 2020-10-25T11:57:36.270700Z

@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 2020-10-25T11:57:58.271300Z

You could do a "lazy" component which returned things on deref if you wanted

borkdude 2020-10-25T12:02:10.272200Z

@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

dominicm 2020-10-25T12:06:19.273100Z

@borkdude nope. But the question is why do you care about ordering. We don't, except that we need an order to our references.

dominicm 2020-10-25T12:07:31.275500Z

(def db {})
(def handler (mk-handler db))
You can't reorder statements.

borkdude 2020-10-25T12:07:51.276Z

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

dominicm 2020-10-25T12:08:35.277300Z

Right. But I think that's a separate discussion to whether you need a topology or not.

raymcdermott 2020-10-25T12:09:05.278600Z

As long as there’s no expectation that things are available, it works for me

dominicm 2020-10-25T12:10:27.279800Z

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.

borkdude 2020-10-25T12:10:54.280200Z

Maybe everything. The ultimate Clojure way of life.

1
dominicm 2020-10-25T12:12:02.280500Z

Maybe not 😛

borkdude 2020-10-25T12:13:14.281200Z

maybe everything is the same as maybe not right?

borkdude 2020-10-25T12:16:52.282800Z

Eh, no database? Sure, we'll write to a .csv today

dominicm 2020-10-25T12:23:41.283Z

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.

borkdude 2020-10-25T12:28:33.283500Z

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

borkdude 2020-10-25T12:29:14.283900Z

And if you need to swap out: make things arguments and pass different arguments

1
borkdude 2020-10-25T12:29:39.284100Z

opinions! :P

dominicm 2020-10-25T12:41:19.286Z

Sure, it seems integration tests are variable in utility. Different projects, different needs.

dominicm 2020-10-25T12:43:21.287400Z

I definitely load extra components in dev, for example components for compiling sass or cljs (but that's an anti pattern too).

borkdude 2020-10-25T12:45:15.287600Z

We do this with babashka

borkdude 2020-10-25T12:45:56.288200Z

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 script

dominicm 2020-10-25T12:53:10.293800Z

I 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.

borkdude 2020-10-25T12:53:55.294600Z

Yeah, at my current job we avoid testing at this implementation-specific level

dominicm 2020-10-25T12:54:15.295200Z

Don't take that as a real stance, but I can see how someone might want to do those things.

borkdude 2020-10-25T12:54:41.295600Z

We either test pure functions or systems as a whole

dominicm 2020-10-25T12:56:05.297600Z

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?

dominicm 2020-10-25T12:57:27.299Z

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.

borkdude 2020-10-25T12:57:40.299200Z

I would read that

dominicm 2020-10-25T13:01:59.301500Z

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.

raymcdermott 2020-10-25T13:15:12.303200Z

I think the community can take your brave words :)

2020-10-25T13:16:09.304200Z

I think the trade offs a team is willing to make will depend on their circumstances

2020-10-25T13:16:57.305Z

I don't think any of these options are a clear winner to use all the time

dominicm 2020-10-25T13:21:45.305100Z

As long as you mean "in a server application" by "all the time", then for me the "component derivatives" are the clear winners :)

raymcdermott 2020-10-25T13:44:56.306Z

Bah

borkdude 2020-10-25T13:45:19.306200Z

emacs

dominicm 2020-10-25T13:45:55.306300Z

some other shit

dominicm 2020-10-25T13:49:13.306400Z

(defonce srv (make-srv db))
(defonce db {})
Does work if you run it multiple times :p

2020-10-25T14:02:06.307100Z

I suppose as I'm running a batch job it is less of an issue for me

2020-10-25T14:02:49.307700Z

And my io is all file based

2020-10-25T14:03:14.308200Z

So a map in a namespace probably works for me

2020-10-25T14:03:30.308600Z

Ala chapter 6 in Clojure Applied

2020-10-25T14:03:53.309300Z

But a bit simpler as I don't have quite the same complexity of problems

2020-10-25T14:04:25.310200Z

I just need to create and wire together my core.async things before pushing the data through

2020-10-25T14:05:35.311600Z

So having functions I pass in and out channels to should work for me and I'll create all the channels in an assembling namespace