This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-10-20
Channels
- # announcements (33)
- # aws (1)
- # babashka (8)
- # beginners (100)
- # calva (59)
- # clara (4)
- # clj-kondo (33)
- # cljdoc (9)
- # cljs-dev (30)
- # cljsrn (1)
- # clojure (28)
- # clojure-australia (1)
- # clojure-boston (1)
- # clojure-dev (4)
- # clojure-europe (14)
- # clojure-france (5)
- # clojure-italy (7)
- # clojure-nl (1)
- # clojure-uk (36)
- # clojurescript (13)
- # clojureverse-ops (6)
- # conjure (2)
- # cursive (2)
- # datahike (11)
- # datalevin (1)
- # datomic (106)
- # graphql (3)
- # helix (10)
- # holy-lambda (24)
- # kaocha (2)
- # lambdaisland (3)
- # lsp (199)
- # malli (35)
- # off-topic (16)
- # pathom (7)
- # polylith (38)
- # portal (16)
- # quil (2)
- # re-frame (18)
- # reagent (57)
- # shadow-cljs (11)
- # testing (3)
- # xtdb (9)
I think I’m understanding the benefits of an interface.clj - but for the path of refactoring where a component’s surface area won’t be a single namespace, and for situations where i just don’t care about later being able to plug/play with a different implementation - is there something i’m missing out on if i don’t have it?
"refactoring where a component’s surface area won’t be a single namespace" -- can you expand on what you mean by this?
A component
must have an interface
namespace -- I'm not sure what you mean by "if I don't have it"?
The naming convention is enforced by the poly
tool -- and in exchange, you get automatic reporting on and checking of dependencies/structure.
We have a decently large codebase which is a mix of clj, cljs, cljc, and typescript. If we want to componetize the clj part, we need to do it for the cljc and cljs parts because they share significant amounts of code
Your interface
can contain the implementation (so you're not forced to have a separate delegation ns, i.e., pure delegation in interface
and the real code in, say, impl
).
and we have at least a few potential things we could spin off as services from the larger monolith, but they would share a large ‘chunk’ of code with the declarations of domain models
but given that its a large number of namespaces which define constructor functions for different domains, accessor functions, transit serialization, etc it isn’t really practical to drill it down to a single namespace
Benefits of the separation, as far as I'm concerned, include the ability to have the functions in alphabetical order, making them easier to find/read, and by having the implementation separate you get a nice readable ns containing just the signatures/docstrings. You can also have different docstrings so the interface
has docstrings for users and the impl
has docstrings for maintainers.
It's hard to know what to suggest (or indeed what problem you're trying to describe) without seeing more of your code, or at least a concrete example of what you mean...
So far, as we've refactored, we've been able to pull out functions into nice, small, self-contained components
that are shared across multiple bases
(which are essentially the "applications" themselves).
okay, so we currently have this one shared-src directory which has a bunch of cljc files. Under the domain
package we have three namespaces for essentially every db table (and some things that are not backed by db tables)
thing.cljc
(defprotocol Thing
(field-1 [_] [_ new-value])
(field-2 [_] [_ new-value]))
... other functions that are just logic ...
thing/model.cljc
(defrecord Thing
[...]
thing/Thing
(field-1 ...)
...)
thing/transit.cljc
... transit definition ...
core.cljc
(defn thing
"docstring for constructor function"
[args]
(do ...
(thing-model/map->Thing args)))
"three namespaces for essentially every db table" -- that sounds very OOP-ish to me, and not very idiomatic for Clojure?
yeah well there are 20,000 lines of it. There are benefits as well as downsides. it is the codebase i am working with and the tradeoff we are rolling with for the moment
but you see how this is difficult to fit into what the tool wants (though we probably wouldn’t use the tool on account of the cljs and ts, so this is mostly a philosophical conversation)
Looking at the above, I'd push the protocols to thing.interface.protocols
and otherwise keep the other three nses (as "collective implementation") and create thing.interface
with the specific functions that are actually invoked from other code.
I think there's benefit in having a clearly-defined surface area for the externally-callable stuff, separate from all the other public functions that exist mostly because you already have thing
split into three or four pieces.
I won't sugar-coat that migration to Polylith can be a lot of work and can seem very tedious if you have a large, existing codebase that has a very different structural model 😐
After all, we've been doing this for months and only have a quarter of our codebase refactored so far -- and some subprojects have only been partially refactored at this point, partly because we're having a hard time figuring out good names for some things 🙂
I think the main benefit we want to pull from the "polylith structure" is a monorepo that we can work with all at once and the ability to experiment with different deployment strategies
As well as giving individual teams which might have different standards of testing/verification/style a clearly delineated place to put in place restrictions
And the cost of a large refactor, for whatever benefit, isn't really affordable right now
But it does feel like we could do it without requiring months of effort by being a touch lazy
You have multiple repos today, and you "release" versioned artifacts internally to share code between those projects/teams?
No we have a few repos today, one of which releases a versioned artifact, a few misc, and our monolith which has all our code and deploys as one service
Have you read my blog series on monorepo/deps stuff? Maybe you "only" need to get to where we were near the start of this migration to Polylith?
i.e., maybe just migrate the non-monolith repos into the the monolith repo and use :local/root
for dependencies that are currently externally versioned and integrate those new repo-projects into the top-level deps.edn
as aliases perhaps, so you can get to a single REPL with all code?
Maybe? I'll dig into it more over time. Right now a common first step is to move to deps.edn for our backend code
Yeah, migrating your repos from lein
to the CLI and deps.edn
is definitely your first step. Not really worth worrying about Polylith until you have that step done, and all the repos migrated into a single repo so you can get a REPL with "all" the code on, and no need to release versioned artifacts just for internal reuse.
Okay... so stepping back and looking at the bigger picture, is it actually feasible to get it all into one repo? Is it worth integrating the TS repo and its build process into a cljc/cljs/clj repo? (I have no idea about that -- it's not a rhetorical Q) What are the specific pain points you have today that you think a single corporate repo, containing all the clj, all the cljs/cljc, and all the TS, might address?