from Hacker News

Structuring Clojure applications

by nefreat on 12/19/22, 2:16 PM with 63 comments

  • by twawaaay on 12/19/22, 5:25 PM

    I love Clojure and also Common Lisp (basically, Lisp in general). But I also observed every single corporate Clojure project I had any connection with to fail spectacularly. Typically as a complete unmaintainable mess. Some as unmaintainable mess that is very slow and unreliable.

    My theory is that this is result of no guardrails on how to structure your application. Clojure to be productive must be used by people who absolutely know what they are doing when it comes to structuring your app.

    When it comes to Java you get hordes of devs still producing passable results. The structure is largely imposed by the frameworks (mostly by Spring/Spring Boot) and available help, literature. Even some antipatterns at the very least achieve some level of convention/predictability that is needed to be able to find your bearing around the codebase.

    I would say, if you have a normal-ish project, care about productivity but don't have really stellar and mature developers -- skip Clojure.

    Choose Clojure if you know how to use all that additional power, have need for it and understand what your added responsibilities are.

    If you don't know how to wield Clojure's power there is very little you can gain by choosing it but a lot to loose.

  • by TacticalCoder on 12/19/22, 2:41 PM

    A similar post, also mentioning protocols and integrant, was posted here a few days ago and may also interest some:

    https://mccue.dev/pages/12-7-22-clojure-web-primer

  • by rmuslimov on 12/19/22, 8:54 PM

    This is very informative and beginner friendly write up which came be used as strategy for organizing apps in Clojure. I personally have something very similar which Redis storage used as persistent storage for storing jobs and tasks within workflow are potentially executed on diff hosts. I would recommend to extending this topic and share your thoughts about component/mount like abstraction to the code. For example notify-sender should have credentials to connect to the services. How they are delivered? as input to the :handle-action method or as as component/mount. Interesting to learn about your approach here.

    Thanks for sharing!

  • by dig1 on 12/19/22, 4:33 PM

    I am not fond of the multimethods because they can easily tangle the code and give you a false sense of scalability. For example, in a blog post, "handle-action" is nicely decoupled with 3 different actions, but let's imagine how that will look after someone adds 20 new actions. Good luck debugging that.

    Also, I saw numerous cases where people will copy/paste multimethod arguments without knowing what they are used for.

    I still find case/cond more readable and way more performant, especially since the author uses the same type for a multimethod dispatch, but YMMV.

  • by haolez on 12/19/22, 4:09 PM

    I'm an experienced developer and I'm getting the feeling that advanced languages are getting less relevant for most applications, since you usually just need a little glue code to glue together mainstream solutions or managed services. I don't need the power of Clojure to connect SQS to Lambda with some extra custom logic.

    But Clojure does look amazing :)

  • by dwohnitmok on 12/19/22, 3:27 PM

    It's interesting to see a lot of FP communities independently arriving at the same architectural structures. See e.g. Haskell's "handle:" https://jaspervdj.be/posts/2018-03-08-handle-pattern.html
  • by logistark on 12/19/22, 4:05 PM

    For me, protocols i tend to not use it, because it makes it harder to understand the code and Cursive cannot find instances that implements the protocols.

    For testing purposes is easier to redef a function than implementing a full new test protocol.

  • by nkh on 12/19/22, 5:44 PM

    Any one tried Polylith with the multi-method approach mentioned in the article?
  • by the-alchemist on 12/20/22, 8:25 PM

    It seems like a lot of the anti-Clojure sentiment boils down to 1) lack of static typing, 2) poor IDE support.

    I'm wondering, though, doesn't the same apply to Ruby, Python, and Node projects?

    I've over-hauled 80k line Python projects, and the "lack of typing" there seemed to apply as well.

    Why don't Ruby, Python, and Node projects suffer from the same critique? Genuinely curious...

  • by shaunparker on 12/19/22, 10:30 PM

    I haven't looked at Clojure in a bit, but shouldn't the (nil? to-info) check in the transfer implementation of handle-action come first? It seems like that would never be reached in the current implementation.
  • by beders on 12/19/22, 6:17 PM

    This approach is not simple. It complects a business process state with multi-methods. Don't do it.

    Model state where it belongs: in a database.