Organizing Clojure code - A real problem?

It was (and I think it still) problem for me from my beginning with Clojure so I’ll try to introduce how my adventure is going.

A little background. I’m full-stack developer and I was programming functionally on front-end for years with React + Redux + Ramda with strict functional rules or Elm where I wasn’t worrying about project structure because it’s very opinionated. While migrating to ClojureScript with Reagent + Re-frame I felt like in home. I knew where to put all my stuff because framework and documentation told me. But when it comes to writing back-end it was sinusoid. I was rewriting and starting over my hobby projects multiple times to understand where problem sits and start over again.

One of the first talk about Clojure I watched was “Demonstration of simplicity that is production ready” by Nir Rubinstein and he said something like “a lot of new Clojure developers start their project with structure from Java projects writing their controllers, models etc. and it’s the worst structure you can have with Clojure”. I interpreted it that we shouldn’t have too many layers (like in Java projects but there it’s acceptable and unfortunately sometimes highly recommended) and instead I limited them to composable HoneySQL functions, utility functions for common problems and routes with handlers that take care of fetching, processing and validating data with private functions. Then I separated routes namespace based on entities. At first it felt like “old, good PHP times but that work” but I quickly realized it have a lot of problems. Queries was tied-up with each others, I had problems with naming functions since all my business logic lived in single namespace. As I think about it now it looked a little like some of my Node.js projects where I use pure Knex (query builder) instead of ORM but not as good.

Then I came upon Polylith and liked an idea. I went through documentation and refactored my existing project to something modeled on Polylith. Without tools, interfaces, symlinks to separated projects etc. to not over-complicate things. I made component’s namespaces separated by entities with store and core namespace inside + database, file-uploads etc. Again at first it worked fine but after some time I felt like I’m doing OOP but with functions instead of classes. I faced many problems because of it like dependency injection and breaking pureness of my functions. I was looking for solution to it but couldn’t find one. Even on real-world repo example I found tests that checks whole impure flow. It wasn’t why I moved to functional programming. On front-end with Redux and Redux Saga for example I can check my whole business logic without making single call to API or mocking data and wanted the same on back-end.

Afterwards with all experience I gained I rewrote my project to mix of these two approaches. I made single store namespace where I only fetch data, domain which is something like M from MVC but more about actions than data - most of my business logic lives here and routes with handlers as simple as possible - just fetch things with functions from store, delegate work to domain functions and based on result build HTTP response, so simple I don’t write handlers as functions; just as compojure routes. Other things like database, middlewares, auth, pub-subs, specs etc. lives in their own namespaces. I feel with it like I found solution to my previous problems. I don’t have name conflicts, I can test my core logic without touching database and just ensure my simple routes fetch correct data. But…

Sometime ago there was discussion about Polylith on this forum and I wanted to take a look on it again with fresh head. I again read documentation, this topic, code of real-world app. Polylith really improved since I worked with it for the first time. Moving to cli(j)-tools was very good decision imo. It doesn’t have a feel of some projects glued together that in one specific scenario will work. Also I improved my skills, learned a lot, finally understood some things and realized I can apply it in someway opinionated but still flexible framework which Polylith surely is. Treat components as abstract set of functionalities and not doing one-to-one relation component to database-table.

Conclusion

I don’t think I have answer to your question but I wanted to sh Hearing “forget about good practices you know” doesn’t help because about which one should we forget?ow example of adventure with Clojure from “newbie” and problems I faced even when coming with some FP background. Maybe it’ll help understand problem more experienced folks here.
I think this (and maybe difficulty of setting correct environment with REPL, Docker and all that stuff) is one of the biggest barrier for people who want to try Clojure. Elixir has Phoenix, Node has not only Express but also Mongoose and other ORMs that are responsible for DB stuff so it’s (big) one less thing to worry about, even Haskell and Rust have frameworks which lets you learn language while also learning framework and building web app. And Clojure is so high-level you’re responsible for almost everything. It’s simple to write things where in other languages you use framework for it that nobody share their work. But “simple is not easy” and it’s really challenging even for experienced devs to setup everything to work well on the first try in new environment not knowing ecosystem. It is important in some areas like trying to convince team to use Clojure. Hearing “forget about good practices you know” doesn’t help because about which one should we forget?
For over a year Clojure was a nice tool for me to play with, but recently I started to discover it’s true power and I feel pain working with JS or Rust again. But it took me definitely too long. It’s problem with all Lisps I think. I can hear “Lisp are so powerful”, “after working with Lisp for some time you’ll see how superior the language is” and things like this. Ok, I see now, but for what cost? Months spent on learning editor, architecture, completely new workflow. Don’t get me wrong. I really like Clojure, it’s ecosystem is my favorite, community is great but marketing(?) behind it isn’t the best to say the least. I was learning Rust when it wasn’t mainstream yet, community was small and it was easy ride comparing to Clojure. Maybe it’s how people want it to be (we have better ecosystem because of that, don’t we?) but Clojure isn’t beginners friendly at all and question from this topic is just the tip of the iceberg.

5 Likes

A program tells a story. In Clojure, you have freedom to tell the story in whatever way makes it clear.

I got started in C, which is similarly unstructured. And BASIC: a lot of the DEC VAX userspace was written in a large-scale compiled BASIC! C and BASIC are not a great help at quelling complexity, but at least you can write the program in a way that keeps things in perspective. That takes practice. But I don’t think there’s much special to Clojure about it.

1 Like

Some (ironically) unstructured thoughts from me…

I agree this is a big deal. Having no designated places for code to live means (literally) no design. I have worked on large codebases, and seen the loss of control of code that happens without structure. Logic is added here and there so you have to follow it all line by line to see what comes out. Seeing this some are quick to blame Clojure itself, concluding its (perceived) immaturity compared to their favourite language inevitably leads to an unstructured mess.

Effective code organisation will always be in the eye of the beholder in parts, but objective goals might include supporting:

Modularity - in the sense of having code modules that do not “require” each other, or layers where inner ones do not “require” outer ones. Breaking up code like this is - I think - always doable e.g. using “clean architecture” style dependency inversion with protocols/multimethods etc, and dependency rejection to keep stateful components at the edge. Making this effort will not guarantee good modules - they may be poor abstractions, horribly interlinked, causing more harm than good - but done well the self imposed structure gives natural testing boundaries and clarity of purpose to each part.

Code Accretion - in the sense that new functionality involves adding code and leaving existing code untouched. There is a strong urge among many to refactor at every turn, which (potentially) breaks code everywhere and is tough to test unless you have very thorough e.g. generative testing, and tough to take if your team-mates did it and you don’t like it :slight_smile: . If you can get into a design (again - what goes where) that allows you to implement new stuff by adding new code and even new files only - or implement “change” the same way via “expand and contract” - then you know the old stuff is still there doing what it always did and testing will be far easier. Similarly if some “new-thing” namespace has implementations of 3 existing (inner layer) protocols all in the same new file, that to me is nicer than spreading those implementations across different existing namespaces arranged around the protocols or the layers themselves.

Java folks are using ArchUnit to enforce code organisation and modularity design decisions. I haven’t found an equivalent for Clojure, but I’d love to know about any if they’re out there :slight_smile:

3 Likes

I am wondering whether this isn’t basically a reformulation of the age-old “Clojure doesn’t have frameworks, it has libraries”. Most Clojure projects tend to be developed as a library or as a collection of libraries. Libraries are a free-form design exercise while frameworks have little bins where the stuff goes into.

1 Like

I think the lack of framework might mean there is a lack of teaching. Often time people use a framework, or want to know how to organize their code, but they don’t know why it is organized as such, and why the way the framework organizes things is good/bad. That serves as a learning point, they first learn that way of organizing things, and eventually learn of its issues as well. That starts to give them an understanding of the pros/cons of that framework. Then they can try another framework, and another, at which point they begin to have their own intuition into code organization. So I agree lack of framework means we don’t have a curriculum to follow for learning about these things, people are thrown into the deep end directly.

I think we lack examples around this area. Honestly, I’m not finding a lot of blog posts, or article about it. And some of the ones that try are too theoritical.

The simplest way to start organizing Clojure code is to have everything in one giant namespace. But I get so much resistance to this from beginners to Clojure. I keep saying, yes, put it all in one big namespace, what’s the problem? And they don’t know the problem, but it feels wrong to them. I say, editors now can easily handle a relatively large file, and it’s much easier to search within one file than across many. Clojure will enforce organization within a namespace, because everything after depends only on what comes before. Put everything in one namespace and don’t use declare.

This will take you really far already. Now if you feel things have gotten simply too big that your editor lags, ok, break it up, how you ask? Just take the half point and move it to another namespace.

Ok, but there’s a few things you need to not do that other languages will have thought you to do. Don’t put all your defs at the top of the file. Put them right above the thing that first uses them. Don’t group functions by anything, simply make sure that functions that use others are relatively close to each other as much as possible.

As you do that, groups of defs and defns will naturally form in your namespace, you’ll start seeing sections forming where the defs and defns are only used within that section, when that happens, put a big header comment around the section and give it a name.

;;;; Permission handling

Now you can just search for ;;;; and you find all sections in the namespace which lets you navigate your code pretty easily.

I think this is a good starting place for most Clojure code base. Yet it seems it’s proven a really hard advice for people to be convince of and actually try it.

15 Likes

I second this advice. I agree fully that a Clojure project should start with a single file, and only split when it becomes obvious.

The most annoying thing for me as a project owner is that some programmers bring their Java habit into my Clojure code base, and create tons of tiny files. It drove me nuts. It is counter-productive.

2 Likes

I think most of the push back on this comes from #1 in @ericnormand’s original post: folks are used to structure from other languages/frameworks and have a deep-seated urge to approach Clojure the same way, from the get-go.

I generally develop with Clojure pretty much the exact same way you’ve described here – but I think the “breaking things apart” aspect is too organic for it to resonate with a lot of folks (I guess that’s an aspect of #3 – they don’t have a good sense, upfront, of how they might want to divide the code up?).

Since I’m very REPL-focused, pretty much any new project work starts out in single file with a minimal ns form and a (comment ..) form. I work inside the RCF (Rich Comment Form), writing and eval’ing code, and lifting pieces out into functions above the RCF as patterns emerge. If an obvious grouping of functions emerges, I’ll shuffle those off to a new namespace once they seem close to “done”. Rinse and repeat. As long as you can choose good names for functions – and for namespaces – future readers (including your future self) should be able to start at the -main function and figure out the flow through the code fairly easily (Zach Tellman’s Elements of Clojure is a great read for advice on naming!).

For context, at work we have about 113K lines of Clojure, 89K is source code and the rest is test code. That source code is split into about 360 files, so that’s an average of about 250 lines per namespace. We have just over 3,500 functions in that source code, so that’s an average of about 25 lines per function. All numbers are inclusive of whitespace, comments, etc. We have just two namespaces that have 100+ functions. We have just two namespaces that have 2,000+ lines and only a dozen that have 1,000+ lines.

8 Likes

Yep, this sounds like what I do. Perhaps a video walking people through this would be good.

5 Likes

I can’t run stats like that on all our code bases, but I know our biggest namespace clocks in at 2500 LOC.

Most apps tend to have this structure [this is for backend enterprise apps]:

APP
| deps.edn
| config
| test
| src
| > company
| | > app
| | | > utils.clj ;; Random pure utility functions that are useful in this app, think things missing from clojure.core, once they've proven themselves in an app, we might promote them to our shared util library, nothing tied to our model lives here.
| | | > data_model.clj ;; This contains our data specs (using Clojure Spec), can contain records (using defrecord)
| | | > model_helpers.clj ;; Pure functions that operate on our data model, which are useful to have for reuse. The most common things in here are constructor functions like make-entity, mapping functions from one entity to another, serialization functions to/from for our model (like to JSON and back), validation functions for our model, and things like that.
| | | > globals.clj ;; Global shared state goes here, mostly top level defs, think your loaded config map for the current environment, your instantiated AWS clients, your DB connection pool, your logger/metric publishing service, and if you need too your APP state, like a datascript instance, or an H2, or just a shared atom map, etc. Functions to initialize and destroy all these also go here. Since all constructs to hold state in Clojure is already managed, you don't need any special "encapsulation" for them, so there won't be any `set-player` or `update-gold` kind of thing in here, only the `defs` and functions to support their initialization/destruction.
| | | > service_foo.clj ;; Business logic goes here, those are the operations the user/business wants the APP to support, thus operations over the data model go here. This will contain a lot of impure functions and is an entry point. An app will have these only if `app.clj` has become way too big, normally all operations live in `app.clj`, and are only broken down for very very large apps. In this case foo would be the operation, and service_foo.clj is where it will be handled end to end.
| | | > service_bar.clj ;; Same as service_foo.clj, but for the bar user operation. Keep in mind operations here are processes and activities needed to manage the data and domain under this app's context in order to fulfill the top level business functions required by your users. This is a service in the DDD sense. Best to think of `foo` and `bar` as the APIs you expose to your clients. Whenever you have operations that are pure over the model, they can go in model_helpers and services would use the helper to help them implement the user operation, that's how you reuse things that multiple operation might need to do to your model.
| | | > service_helpers.clj ;; When multiple services start to show chunks of logic which are the same between them, you can move it here and have them all depend on this shared logic. Make sure you inject dependencies into those helpers, though they are allowed to do side-effect.
| | | app.clj ;; This is a giant namespace, all apps start out with only this and all the namespaces above are extracted out from this one when the need arrise only, otherwise they'd just all live as sections into this one namespace.

Most apps aren’t large enough to have all these, so they vary from only an app.clj to the full set of the above namespaces. I use service oriented architecture normally, so each app would be scoped to a given bounded context of the overall business domain, and so the data model would be within that context, as well as all operations would be scoped to that same context. And so for end-to-end top level business functionality, often you’d have one app call another to fulfill the full business need, as many business needs require collaboration between multiple domain contexts.

If you’re curious where do I put the code to fetch data from a DB/externalService or store it, well that goes in app.clj at first, moves to service_foo.clj if things grow large enough, and finally moves to service_helpers.clj when service_bar.clj has the same data fetch/storage needs. But its often mostly all in app.clj like so:

(def db
  {:dbtype ...
   :dbname ...
   :host ...
   :port ...
   :user ...
   :password ...})

(def default-query-options
  {...})

(defn get-user [db query-options user-id]
  (-> (jdbc/query db ["..." user-id] query-options)
      ;; code that converts the DB result into a valid user from my data_model
      (model-helpers/validate-user))

(defn show-user
  [request-map]
  (wrap-metrics
    (try
      (validate-show-user-request request-map)
      (->> (:user-id request-map)
           (get-user db default-query-options)
          ;; Code to convert it to a valid response)
      (catch Exception ex
        ;; log
        ;; handle
        ;; return valid error response))))

Like things would start this simply, and evolve from there.

3 Likes

I also thought about this topic and I cannot find anything true and universal in how I organize the code. It just comes from experience of working in different projects and noticing when code feels comfortable and uncomfortable.
I’ve also noticed that people I pair with have different sense of comfort when they work with a codebase. And even when we agree that it is uncomfortable we often do not agree on how to make it better.
I am curious how to learn/teach design. I’ve picked up some maxims from other people but I do not necessarily understand why they work and in what context they don’t work untill I try. Is there a better way to know than just trying?

1 Like

We tend to write very small namespaces:

(ns app.video-upload.check
  "Checks if an uploaded video can be processed by 
   ffmpeg. Thereby non-supported videos can be 
   rejected in the client app."
   (:require ...))

  ;; I've omitted the rest of the code for this example.

We use the doc-string of the ns form to describe the concept of the namespace, meaning “what problem does it solve” and “why does it solve it” (in the context of the overall system). The real namespace used for this example only contains 2 functions.

The comments in the code then describe how it is done. Rule of thumb: If anything is not covered by the concept description, it should be probably moved into a separate namespace with dedicated concept description.

We also do not use any deeper folder structures than this one. Instead of using a product-specific top-level folder we use app.

5 Likes

This discussion has been very interesting to me. I want to add one thought into the mix for consideration (plus a little side comment), but I can’t tell anyone in this discussion how to organize Clojure code: I don’t work with large Clojure codebases, I’ve never worked with Clojure on a team, I’ve probably written less Clojure code than the experienced people here, and my projects have different goals than most Clojure projects. I know enough to understand that I’m not dealing with the same challenges that others are. Here’s one thought, though.

I usually start with no declare, but sometimes, after adding a lot of definitions, I come back to a file and think, “Wait, why is this function here?”, or “What is all this code doing?” I know what the file (or section of the file–doesn’t matter) is supposed to do, but I don’t know how the pieces of my code serve that purpose. I scroll down and ultimately find a crucial, primary function (or functions) that need(s) the other functions, and from there I can trace back to find the rationale for each of the other functions. At that point, I often add a declare at the top, and move the central organizing function that was at the bottom of the file up to the top of the file or the section: if I start reading from that function, it gives me an overview of what the code below it is is doing, and then I can scroll down to see how different pieces of that operation work, and I know why they’re there. So putting a function that gives an overall purpose near the top (or near the top of a section of code) is a kind of documentation for me. Sometimes that tells the story better.

Sometimes I reorganize functions in other ways within a file for the same purpose, and this can require declare as well.


A side comment:

I like this point (though I don’t have any thoughts about what to do). It reminds me of something else in my work: I have students, and I assign papers. The assignments give students detailed though somewhat flexible advice about how to structure their papers. Not all of the students need the advice, but some do. Those who are already excellent writers and know what to do with the subject matter can ignore my guidelines and get a top grade. The guidelines are there for students who don’t have any idea about how to go about writing a paper of the type I’m assigning. It gives them a starting point for thinking about what’s needed in that kind of paper, and adds to their toolbox of writing strategies that they can use after the class is over.

2 Likes

Eric,

Thanks for bringing this up. I’ve been mulling over the topic of organising Clojure applications on and off for a couple of years now. I tried various things but most valuable insights came from watching other people’s work.

A gem that I keep coming back to is Rafal Dittwald’s “Solving Problems the Clojure Way.” While not strictly about architecture, the journey Rafal takes us on provides valuable insight into how Clojure applications can be structured. The best thing is, he doesn’t show a single line of Clojure. It’s JavaScript all along. It does wonders for the reach of his talk.

An experience report that helped me think about structuring applications is Jarppe Länsiö’s discussion on long-lived projects from ClojuTRE 2018: “First 6 years of a life of Clojure project.” The way their architecture evolved was instructive. Modelling commands as data and pure functions reminds me of domain-specific languages, recently discussed by Hanson and Sussman. I loved the gag about mocks.

Domain-driven design that Sean mentioned is a box packed with valuable tools. The building blocks, such as value objects or entities, translate without much friction into the Clojure thinking about value, identity, and state. Aggregates help find consistency boundaries. Paying attention to the language used in our domain helps identifying bounded contexts and drawing namespaces around them. Thinking domain-first can help with many design hurdles.

“Domain Modeling Made Functional” is a good book that introduced the domain-driven approach using F♯. And while we’re at other functional programming languages, “Designing for Scalability with Erlang/OTP” is a treasure trove, in particular when it comes to handling failure.

I’ve been experimenting with those and other ideas, building applications out of them, sometimes giving talks to discuss the outcomes. The majority of resulting slide decks aged poorly. I suppose I could give a whole new talk that’d be all about pointing out weaknesses in my earlier takes.


Not long ago I contributed to Spacy, my workmate’s web application for planning remote open space events. The main value proposition of that app is its focus on being responsible; an achievement for which I can take no credit. As the main author, Joy Heron, worked on the front-end architecture, I experimented with the back-end.

The result is a loose combination of an onion architecture with a dash of functional core and imperative shell. Inside you’ll find a “domain” namespace that defines schema of our open space events and exposes pure functions transforming entities from one valid state to another. It focusses on the essential domain complexity; technical jargon like “SQL”, “HTTP”, or “JSON” does not belong there.

The neighbouring namespaces require the domain one and provide all the important moving pieces. Databases, ring handlers, and lifecycle of components are external to the problem domain. If I change the database or replace the HTTP interface with a Kafka client, the domain remains untouched. That’s what I want, because over the course of a typical application’s life there’ll be enough business reasons to change the domain logic.

Now we can ask what do we do if we need to add new functionality that has little to do with the existing logic. Say, a chat between participants. Where would we introduce it? Depending on the context, “elsewhere” might be a good answer.

Mind you, I don’t mean microservices. A valid elsewhere would be another tree of namespaces in the same project. I’d keep them isolated from the rest to minimise coupling. (Isolation checks could run in CI, with an ArchUnit-esque tool that Anthony brought up.) No matter what change you introduce in the chat subsystem, it should not break the event planning one.

Having those trees structured in a similar fashion would make the entire app easier to understand. But what’s even more important is exposing a consistent API across all the domains. Say, a Ring handler. That enables composability, another aspect stressed in the aforementioned book.

Composability is a quality I look for when assessing software design. Can I take the entire Spacy and expose it under a URL path prefix next to another Ring application? How much effort would it take, how much code would have to change? Better yet: can I start two instances of the same application in the same JVM behind a single Ring adapter? Implicit dependencies and global mutable state will quickly surface.

The problem with a back-end example like Spacy is its insignificant size. The application has but a handful of use cases, operates in a small domain, and doesn’t need to be fault tolerant or high scalable. It falls into the O(armchair) complexity class. Things start to get hand-wavy when we start talking about database transactions or error handling.

That being said, I think that’s the rough structure I’d start a new project with. The main reasons are composability and malleability. We don’t have to get it spot on the first time as long as we can adapt and change it in the future.

Looking forward to your comments.

8 Likes

That’s interesting, I think you show that there’s different options to organizing code, and maybe most options work pretty well in the end.

I do think Clojure namespaces are purely organizational of the source code, unlike a Class, they don’t create any semantic structure, at the end of the day, you’re just using functions, where you put them is a matter of how it make most sense for you to categorize and recall where the function you look for is.

The only challenge structurally with breaking things up in more namespace is the dependency management between them and avoiding circular dependencies.

But I think this is a bit of a key point. The namespace is not something that holds state, or that encapsulate data, or that creates a hierarchy of types, etc. It’s just a folder. So similarly to a music library of mp3 files, what folder structure you want to have, put them all in a music folder, or have a folder per artist and another per album, etc. is very personal about just helping you navigate and find your way through your music library. I think namespaces are similar.

What matters more is the semantic structuring of your code, such as keeping your functions pure, managing your app state properly, keeping your stack calls shallow, keeping your side effects on the outskirts, writing well factored functions, etc.

Where you put those functions does have some pros/cons, but much less then the above, and is more easily refactored.

I think the considerations of where to put the defs and defns would be:

  • Ramp up time of someone new to the code base to get a mental map of the various piece of functionality
  • Ability to reuse functions and share vars where needed
  • Dependency relationships which can’t be circular
  • Being able to know the scope of use in case you need to refactor things in a breaking way, so you can easily find all references to what you have and know the impact your refactor might have.
  • When reusing, what granularity of code you can pull in

I know what you mean, but what I realized is with declare you rely on good intentions to make sure the story is told in the right order, and that summaries are always going to appear first. And that’s not always true, and good intentions are hard to keep up consistently. If instead you just get used to reading Clojure code bottom up, you’ll get the same benefit, the summary is always at the bottom and you read up to see the logical chain of events, at least that way this order is guaranteed, so it’s much more reliable, well unless someone used declare to mess up the ordering.

2 Likes

I’ve had a slightly different approach to this… Typically, I will start with a dev.clj namespace, outside of src, which is a sort of (mostly) append-only scratch pad. I use this to interact with the REPL, and iterate over my code. Stuff that has been “finished”, I archive inside a Rich comment form (so it’s not eval'ed if I reload the namespace). Once I’m happy with that, I will send it off to its own namespace. For one-off stuff, this could be a single file, but in general, I will split the functions and spec definitions over several namespaces that carry some semantic meaning. For example, I’m currently developing an API with several endpoints grouped by functionality (say, projects, analysis, and diagnostics).

Each one of those groups gets its own little namespace (com.blah.api.projects, etc) with route definitions and handlers, and I compose those into a single api.core ns that builds the actual Reitit routes table from the separate components. I have a similar approach to my Malli specs (which is arguably more useful than for the API components, since they can get fairly verbose, and I prefer working on smaller files).

For the data layer, I have a com.blah.db namespace that deals with all DB matters, and there’s also the com.blah.util ns, with general helpers, a db-specific com.blah.db-utils with small DB-specific helpers. I find that working with several smaller namespaces makes my code feel less cluttered. In this particular project, I have 17 separate namespaces (plus the dev scratchpad). It may sound like quite a lot, but it is actually quite easy to navigate them, and more importantly, they make sense to me.

This last point is probably a caveat, since I’m currently the only person doing Clojure in my team, so if there were more hands in the pot, some sort of formalization on how to group code might be necessary.

Regarding the “append-only” nature of the scratch pad, I sometimes trim stuff that’s become obsolete, to make it easier to navigate the file, but I tend to keep a good amount of “history” there, as well as small helpers I keep coming to when working on the REPL.

I think I should also have a look at Polylith, it looks like it may be a good way to enforce a bit more structure on my approach.

1 Like

I was building Java applications for over 20 years and I am switching to Clojure now. And I have a hard time fighting my OO habbits. In my current pure ClojureScript project I am again back to a MVC like structure.

In the domain namespace I have a namespace for each entity (x.domain.user, .product, .order, …). All pure functions, most of them getters to the keys in the entity map. Some computations and combinations of getters. In the controller namespace I have calls to the database to update the entities. Most of the application is React view components which render the entities and forward events to the functions in the controller namespace. There is also a namespace for some cloud functions.

This is why:

  1. I have a namespace with getters to fields for each entity so I don’t have to use keywords in the view. Because I can not remember the keywords. Instead I use the getters to get auto-completion and compiler errors. I also often start with getters like x.domain.order/status which starts as a real field in the db and gets a computation result later in the project. Having getters from the start I don’t have to refactor the view from using keywords to calling computation functions when this happens.
  2. Since the ui components are by far the most code, I have them split in multiple namespaces. Mostly one for each “page” in the application. Since multiple pages access the same entities, it seams I need these entity namespaces for the shared getters and computations in the entities.
  3. Functions in the controller namespace also use getters an computations from the domain layer to make some decisions for their updates. I have this separate controller namespace because besides the view components it is used by the cloud functions. Instead of user click events it is triggered by cloud functions events.

I see this is the same reasoning I had in my OO code. But what could be a better approach in a serverless (Firebase) application?

1 Like

Using getters and setters is typically seen as non-idiomatic in Clojure. What IDE are you using? I use Cider and LSP, and keywords are auto-completed (if the namespaces have been loaded) for the most part…

2 Likes

You might be interested in a presentation called Solving Problems the Clojure Way .

2 Likes

Ya that doesn’t sound right to me in Clojure, sorry to say.

You’ll get used to it, just trust yourself, start learning your data model and you’ll begin to remember what it contains. Your editor should also auto-complete keywords, just not in a way that you know which entity has what keywords. You’ll also learn to quickly refer to the definition to help you out.

This sounds wrong to me, your model should be data based. Your view will read status and present it, there should be a function in your model that computes the status and sets its new value on your model, which then updates the view with it. It shouldn’t be that the view calls a function to retrieve the status. Think push from model to view instead of pull.

Seems the same issue, your UI shouldn’t be using your model in MVC, the arrow is from View to Controller and Controller to View. Your UI components shouldn’t depend on your Model at all. Instead your model should push to the view a map, and then your View should use that data to render itself. Then user events on the View will be sent to your controller, who will then leverage the Model functions to modify the model data which once updated will call the view with the new model data map and the View will render itself a new.

So your View can be separated in as many namespaces as you want, but think of those namespaces as your presentation logic. You could put it all in one giant namespace at first. In that namespace you’d have functions to format the data from the model in the user specific way, so things to say show a date in a human friendly format, a currency in the locale of the user, etc. And you’d have some render functions: render-page-x for example which takes a map of the data from the model it has to render, and options of how the user wants them rendered. The whole View layer should be pure.

That means how you break your View layer into namespaces is more about your own being able to make sense of it and of which parts of it are shared between different views.

That seems fine, but I feel there’s something about namespaces maybe you misunderstand? Like you could easily have both these controllers (the one used by the cloud functions and the one used by the browsers) in the same namespace. So having “another controller” isn’t a good reason to have two namespaces. It’s also not a reason not too, but you have to understand those are orthogonal concerns. A namespace is not like a class, it is more like a Package.

Thank you for your suggestions.

Regarding getters in the model and pushing precomputed maps to the view. I startet with this approach (which also is the re-frame approach), but I abondened it for the following reason:

When the view is completely stateless, all the intermediate ui state and it’s management has to move somewhere else and the reuse of ui components get’s divided into two parts that have to be synchronized.

An example: Given a page which displays a person (just the name) in an expandable material ui card. When the card is expanded, details for the person get displayed. For the details a separate call to the server/database is required.

In my current architecture the view has it’s own state (card collapsed/expanded) and subscribes to the database query by itself instead of getting data pushed. That way there is a person details component, which only get’s rendered, when it’s parent (the card) is expanded. You get lazy loading of the details for free and don’t have to manage this state by yourself. You get a reusable expandable person component. If you want a second person card on the page you just put a second view component there with a second person-id parameter.

In the push all data to the view architecture such components seem a lot harder, because you can not package all the functionality in one view function. You have to have some code which manages the view state (expanded/collapsed) and then the render function which uses this sate. When you need a second person card on your page, you have to put it there and then you have to refactor some other code which previously was a simple boolean person-expanded? into a map of expanded persons and then more code which distributes this values to the view components. And then you have even more code which subscribes to the db based on the expanded states and passes the results to the view. Following this architecture, you will gat a tree of view components plus a corresponding tree of state somewhere else which kind of matches the view tree. And since a react application already has the state (the component tree) where corresponding data/models can be put alog with the view components, managing another tree of state by hand seams a waste of time and introduces more complexity.

I see, you get pure view functions from the stateless view. But I don’t get how this could be worth it. Will see when the app grows… :cold_sweat: