Fork me on GitHub
#polylith
<
2021-10-20
>
emccue21:10:33

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?

seancorfield21:10:19

"refactoring where a component’s surface area won’t be a single namespace" -- can you expand on what you mean by this?

seancorfield21:10:02

A component must have an interface namespace -- I'm not sure what you mean by "if I don't have it"?

seancorfield21:10:00

The naming convention is enforced by the poly tool -- and in exchange, you get automatic reporting on and checking of dependencies/structure.

emccue21:10:34

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

seancorfield21:10:29

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).

emccue21:10:03

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

emccue21:10:35

so that makes sense in my brain to be one “shared component” between the services

emccue21:10:21

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

seancorfield21:10:39

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.

seancorfield21:10:25

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...

seancorfield21:10:13

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).

emccue21:10:24

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)

emccue21:10:34

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)))

seancorfield21:10:38

"three namespaces for essentially every db table" -- that sounds very OOP-ish to me, and not very idiomatic for Clojure?

emccue21:10:11

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

emccue22:10:18

so c’est la vie on that

emccue22:10:15

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)

seancorfield22:10:51

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.

seancorfield22:10:19

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.

seancorfield22:10:06

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 😐

seancorfield22:10:14

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 🙂

emccue22:10:36

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

emccue22:10:51

As well as giving individual teams which might have different standards of testing/verification/style a clearly delineated place to put in place restrictions

emccue22:10:15

And the cost of a large refactor, for whatever benefit, isn't really affordable right now

emccue22:10:33

So maybe what I want to do isn't really polylith? Idk

emccue22:10:43

But it does feel like we could do it without requiring months of effort by being a touch lazy

seancorfield22:10:38

You have multiple repos today, and you "release" versioned artifacts internally to share code between those projects/teams?

emccue22:10:04

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

seancorfield22:10:33

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?

seancorfield22:10:36

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?

emccue22:10:15

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

emccue22:10:54

Since it doesn't seem like leiningen supports this in any easy way

emccue22:10:11

And it's not the way the wind is blowing regardless

seancorfield22:10:59

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.

emccue22:10:33

The versioned artifact is in TS, so it is gonna still be a wierd one

emccue22:10:47

Right now all the clj code is in the monorepo

seancorfield22:10:14

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?