Fork me on GitHub

I’m on vacation this week, but I’m sort of planing to write a series of blog posts on my experience with Component, or rather dependency injection. The gist of this series is that there seems to be a, shock, trade-off, here 🙂 In the early days of ardoq, more or less every interesting fn received system as its first argument. Which is great, as it can now do whatever it wants. It’s also horrible, because, it can do whatever it wants. Then (just before my arrival at ardoq) we started a move where each service received it’s corresponding Component as the first argument so (foo-service/bar (:foo-service @system) …) which, IMO, was better because you could now reason more clearly about what bar did, since it only had access to its part of the sysem Now, ardoq being a multi-tenant system, each request runs in a, you guessed it, context which would include the user and the corresponding customer (which we call organization) data-store. This lead to the introduction of a ctx which is a simple map which held just these two values. It has since grown, but not out of hand, so we started to call our functions in this way (foo-service/bar (:foo-service @system) (->context @system **current-user**) Now, most of our Components don’t do much more than write to the database, nor do they hold state, and for reasons, writing to the database involved more than just writing to the database, so there were a couple of other Components needed when mutating. But when foo-service wrote some stuff to the database, maybe bar-service also needed to write some stuff, so FooService then had a dependency on BarService All this to say that at ardoq, we currently have a bunch of Components which do nothing more than hide implementation details, like when creating a new (customer) org, we pass (:org-service @system) and the caller doesn’t know/care what kind of side effects org-service might cause when creating an org (think send email, stick stuff in database, stick stuff in queues, talk to other services). This may be good, and this may be bad. Another thing is that some Components hold config, because stuff that could have been stateful, like connections to external services, are often just done over rest (ElasticSearch comes to mind), so they are not “real” Components with lifecycles, since the configs are static over the runtime of the app. Anyways, I’ll try to be more clear in a series of blog posts on this.

💯 5
👀 10

> It’s also horrible, because, it can do whatever it wants It's kind of a grey zone, isn't it? No opinion from my side. Maybe I see some analogy between receiving a whole system and receiving a whole "app state" in a reactive frontend app


Deffo a grey zone, both good and bad 🙂


I think these discussions are important, they touch on concepts like “onion architecture”, “imperative shell, functional core” and “out of the tar pit”. I think it’s important and enlightening read about practical applications of these general patterns and varying degrees of success and practicality!


I’d second that, and that’s part of my motivation to write the series. No critique of the community, but I think we’re lacking (might be my lack of looking) examples of how applications are made. We have good examples of how to write libraries, but as apps tend to be closed source, it’s harder to gain insights into what the current patterns are.


In terms of web development there is a lot of activity around leiningen, luminus, fulcro, ref-frame, reagent etc. and different example projects, templates, tutorials and so on that touch on how to wire things together and how to leverage Clojure and the REPL. But I know what you mean. When it comes to the nitty gritty there are many things to learn and mistakes to be made.


At least to me, taking the whole system map is something that is analogous to @Autowire or whatever other dependency injection thing. The only difference between @Autowire SomeSystem someSystem; in a spring controller class and {:keys [some-system]} in a handler function is that in clojure you have the ability to opaquely take the whole map and pass it down further. if you can avoid that - and can clearly see the defined dependencies at usage site - then it shouldn't be an issue


> ... so FooService then had a dependency on BarService Sounds a lot like the situation we have. If BarService depends on BazService, how do you ensure that FooService receives BazService (to pass along to BarService)?


> I think these discussions are important... Yes! I've definitely learned from this, and feel like I understand the trade-offs better. Thanks to all contributors!


In the last days, I briefly skimmed over an article with the content “Everybody talks about over-engineering, but while architecture is generally over-engineered, code is mostly under-engineered which is as dangerous”. I guess I found it on HackerNews, but am not able to find it, again. Anybody remembers it or bookmarked it, by chance?

☝️ 7

Looking at java.util.PriorityQueue, and I see this: > Implementation note: this implementation provides O(log(n)) time for the enqueing and dequeing methods (`offer`, poll, remove() and add); linear time for the remove(Object) and contains(Object) methods; and constant time for the retrieval methods (`peek`, element, and size). Anyone know if there is a good reason to do contains(Object) linearly here when the items are ordered?


but basically java.util.PriorityQueue orders objects either based on a passed in comparator, or based on the "natural ordering" a build in comparator, which is not always going to be the same as the .equals behavior which .contains uses


the docs from comparator say this about .equals "It is generally the case, but not strictly required that (compare(x, y)==0) == (x.equals(y)). Generally speaking, any comparator that violates this condition should clearly indicate this fact. The recommended language is "Note: this comparator imposes orderings that are inconsistent with equals.""


Ok I was assuming all objects that are .equals should have cmp == 0, but I guess that is wrong


I can see a need for this where you want consistent ordering. So, for instance:

(reify java.util.Comparator
  (compare [_ a b]
    (let [c (compare a b)]
      (if (zero? c)
        (- (System/identityHashCode b) (System/identityHashCode a))


but that would be rare


Nice, I didn't know about System/identityHashCode. Thanks 🙏:skin-tone-2:


Right ok. Seems a shame to have to implement that so pessimistically, but ok I understand. Thanks @hiredman

Drew Verlee20:08:46

Is there a way to tie editor formatting (white space, map values aligned, etc..) to a specific project? Put another way, how are people dealing with all the different formatting rules that are in play. Currently I have to just go turn mine off if a project is too different or deal with git commits where way more is changed by the formatter then by me.


Emacs .dir-locals usually help In projects where there's specific tooling usually said tooling will be configured and CI'd, so as long as one doesn't reformat unrelated code, one should be mostly safe. You'd simply use their tools before each commit Relatedly, lately I'm considering a small Emacs feature: on save, run beginning-of-defun and reformat that defun but not the whole file.


Are you using IntelliJ/cursive? If so you want to be careful about gitignoring all of .idea - only do certain subdirectories

Drew Verlee20:08:34

It's not related to a specific editor per say. Simply that if the project doesn't tell my editor the formatting rules, my editor will use it's defaults, now I add an entry to a map and all the keys get shifted. Now if you review the pr, its noisy.

Drew Verlee20:08:39

Really the solution is for the dif alg to not care about white space.


in any github commit or PR you can add ?w=1 and whitespace will be omitted from the diff

👍 2

> It's not related to a specific editor per say. Maybe ideally, but practically it is probably the only way you can hope to solve it (e.g., .idea/codeStyles for cursive)

👍 2

other than that there's no universal solution. There's a bunch of clojure formatters, ways to configure them, etc. Nothing close to a spec that all editors/formatters adhere to

Drew Verlee20:08:31

That seems to be the case, I just like to ask around once and a while.


:) I tried some time ago to harmonize things across disparate environments, making things fast/smart/correct I keep using it (as some friends do at their workplaces) but I have kind of a limited interest in promoting it much. the Lisp Curse is hard to beat

👍 2

For emacs there's, and more generally there's Generally though I don't bother matching my editor setup to a particular project's style (mine at least). I rely on being able to run something on the command-line which will automatically bring my changes in compliance with the project's style, and is required before a pull-request can be accepted

👍 2
Drew Verlee21:08:33

> will automatically bring my changes in compliance with the project's style, Such as?


cljfmt, kibit, eastwood, clj-kondo, whatever the project chooses (though clj-kondo doesn’t auto update)

Drew Verlee21:08:51

maybe, i'll make a quick example of what i'm referring to.

Drew Verlee21:08:22

This is the right type of tool, it doesn't control everything though. Thanks for sharing it though. ill look into it more.

Drew Verlee21:08:38

A small example of what i mean. I feel like the primary issue is that were diffing by spacing. It's data, my editor should choose the view. The review /diff should be based on data not text.


you can't have shared defaults without a tool that imposes them, perhaps what you want is for git to use a diff that is smart about clojure and ignores whitespace


in practice I've always settled for what a dumb tool (like cljfmt) can enforce


it's better than the alternative


there is the option of saying "I won't accept this PR until the whitespace diff is removed", but that requires some authority and/or buy in


as a compromise, you can request that whitespace "fixes" be separate PRs and not be mixed with code changes


I’ve seen people try to sneak in their preferred style changes in large pull-requests. “C’mon it’s not that big of a deal. Too much work to undo. People are waiting on this”


right - and I totally understand why it happens too, but if you don't have the discipline to make the whitespace changes a separate commit in the history it's not your reviewer who is wasting people's time


it is painful to try to review a large PR without the help of diff tooling, and that is what mixing code changes and formatting changes imposes on a reviewer


Totally agreeing to not combine white space changes with code changes, but one can hide white space changes in GitHub PRs, not sure if other platforms have that feature.


Git itself provides a way to ignore whitespaces so it should be trivial if not already there in other platforms.


not perfect but it can help when it's too late

# undoes whitespace-only changes
unformat () {
	local old=$PWD
	cd "$(echo $(git rev-parse --show-toplevel))"
	git reset --mixed
	git diff -U0 -w --no-color | git apply --cached --ignore-whitespace --unidiff-zero -
	git checkout .
	cd $old

💪 4
👀 2

Your git fu is strong 💪


You can replace old and cding around with pushd and popd

👀 2
Drew Verlee16:08:00

I was able to ignore whitespace changes in magit and that seems to change the diff thats sent to github. So this fixes some (maybe all?) issues 🙂 but ill keep this git snippet handy