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.