polylith

https://polylith.gitbook.io/ and https://github.com/polyfy/polylith
Nikolas Pafitis 2021-06-01T14:31:06.023300Z

Hello guys, great project, big fan. I'd like to ask a couple of questions: • Is there going to be a feature/flag where you select the extension of your generated components/base? For example clj, cljs, cljc, cljr, clje or whatever next implementation of Clojure can be? • Is there going to be a feature/flag where poly cli adds the newly generated brick's path to dev's or another brick's paths? • Why does the interface namespace is generated with each component? Couldn't it be done by having an interfaces/ dir where you have only one interface namespace per, well interface, which that interface would then delegate "polymorphically" to the core namespace, for example.

Nikolas Pafitis 2021-06-02T14:33:45.035600Z

I agree with @seancorfield a simple ext:cljs would be more than enough for most needs. And you also don't really need seperate clj/cljs dirs as the current structure perfectly allows mixed language projects.

Nikolas Pafitis 2021-06-01T14:32:37.023400Z

The mechanism for comparing the same interface namespaces to have the same API looks kinda funky to me

seancorfield 2021-06-01T15:13:49.023800Z

I’ve asked about #3 and it seemed that there might, in the future, be an option to have the component name replace the fixed name interface so you would have com.acme.thing.thing instead of com.acme.thing.interface. You can already have com.acme.thing.interface.subthing (which I’ve been using for protocols: com.acme.thing.interface.protocols). Personally, I’d prefer just com.acme.thing as the interface name but I understand that complicates the directory/file structure for the tool (currently com/acme/thing is always a directory).

seancorfield 2021-06-01T15:16:43.024Z

Regarding #2, it does seem that updating :extra-paths in the :dev and :test aliases in the workspace deps.edn could be automated (but I would only want that if it preserved whitespace and comments when editing my deps.edn file).

Nikolas Pafitis 2021-06-01T15:19:07.024200Z

I think you're right @seancorfield about #3, although it might complicate component "polymorphism", so that's why the devs thought another level of indirection with an interface (I have to give it some thought i don't have a real opinion).

Nikolas Pafitis 2021-06-01T15:19:38.024400Z

@seancorfield about #2 you can do that using https://github.com/clj-commons/rewrite-clj which preserves whitespace.

seancorfield 2021-06-01T15:19:48.024700Z

And, yes, #1 would be a nice setting to have in workspace.edn — but be aware that the Polylith team say they are specifically targeting (server-side) Clojure and not ClojureScript because it’s on the server side where the architecture makes sense: a frontend code base is likely to be structured very differently, and you also don’t have quite the same REPL-based workflow in non-Clojure contexts. I only do server-side Clojure so I don’t really have a good grasp on cljs workflows, for example, but the couple of times I’ve dabbled with cljs in the last year or too it has seemed frustratingly different/behind the Clojure workflow.

seancorfield 2021-06-01T15:21:41.024900Z

The interface naming seems very “odd” when you first encounter it — that was probably the thing I liked least about Polylith’s approach when I first read about it — but I’ve quickly gotten used to it now. And the substitutability (and very clean separation of “interface” and implementation) are worth the “cost” of the slightly odd naming/structure.

seancorfield 2021-06-01T15:22:09.025100Z

Polylith seems to be one of those ideas that is very hard to really appreciate until you actually start using it…

Nikolas Pafitis 2021-06-01T15:24:08.025300Z

About #1 i made a dummy fullstack project using clj/cljs and it works pretty fine actually, makes alot of sense for SPAs as well. It's just that currently i go and rename my files from clj to cljc/cljs and add clojurescript dependency where necessary. Also other implementations of Clojure are viable for server-side, even cljs. I might want to try polylith witth clojerl one day let's say, i think it would be nice to just have an option which just changes the extension of your file

Nikolas Pafitis 2021-06-01T15:25:27.025700Z

I don't mind having an "interface" namespace as much, even though it's not classic idiomatic clojure. It just feels weird to me having to maintain multiple namespaces of the same "interface" where they have to look identical. Don't get me wrong i love the project.

Nikolas Pafitis 2021-06-01T15:26:12.026100Z

And then you get the poly cli to compare your different namespaces to have the same defs between them. Feels clunky and not DRY

seancorfield 2021-06-01T15:26:45.026300Z

I’m not sure what you mean about “compare your different namespaces”?

Nikolas Pafitis 2021-06-01T15:27:02.026500Z

if you have 2 components of the same interface

Nikolas Pafitis 2021-06-01T15:27:17.026700Z

the poly CLI checks whether or not their interface.clj match

Nikolas Pafitis 2021-06-01T15:27:29.026900Z

otherwise poly check would fail

seancorfield 2021-06-01T15:27:43.027100Z

Ah, you mean like the local vs remote component example in the docs?

Nikolas Pafitis 2021-06-01T15:28:11.027300Z

That's how they ensure that your components that implement the same interface don't differ in their API

Nikolas Pafitis 2021-06-01T15:28:41.027500Z

I would just think it's easier if you have another interfaces/ directory where you have only one interface.clj per interface basically

Nikolas Pafitis 2021-06-01T15:29:02.027700Z

i think that's how the leiningen version of polylith did it, if i'm not mistaken

seancorfield 2021-06-01T15:29:49.027900Z

Right. I wonder how common that really is in the wild? What I like about the interface/implementation separation is that you can have a very nice, clean interface.clj with everything in alphabetical order, and then your implementation in whatever order Clojure requires. So I find there’s value in the separation even aside from the substitutability.

Nikolas Pafitis 2021-06-01T15:30:17.028100Z

You'd still have that though

Nikolas Pafitis 2021-06-01T15:30:50.028300Z

You'll have a single user.interface namespace that's all

Nikolas Pafitis 2021-06-01T15:30:59.028700Z

Where it delegates to the core implementation

Nikolas Pafitis 2021-06-01T15:31:50.028900Z

It will delegate to a different implementation of user.core namespace exactly how it's done now depending on which implementation you include in your path

seancorfield 2021-06-01T15:32:11.029100Z

Having each component be self-contained has value though. Having the interfaces be “elsewhere” would complicate things a lot, especially with the limitations of relative paths in deps.edn (which is part of why recent changes have been made on the issue-66 branch, which is what we’re using).

Nikolas Pafitis 2021-06-01T15:42:19.029800Z

Would love to hear @tengstrand's opinion about this

1
tengstrand 2021-06-01T18:34:31.030500Z

Here we go! 1. I haven’t tried it, but I think it will already work in the issue-66 branch to have the monorepo at the root and then have one or several directories per language, e.g. clj (or clojure or something else), cljs, cljc` where some of them are Polylith workspaces (e.g. the clj directory) where the poly tool will work. Then if we put cljc code in the cljc directory, it could be used from the Clojure code via e.g. :extra-paths “../../../cljc/path-to-your-cljc-file. My guess is that you want to have only one Clojure directory, and if so, then we could also let all our cljc code live there, and let the cljs code refer that code. 2. It takes maybe 1 minute to create and add a component manually, which is something you don’t do very often. But the main reson we let you do this manually is that we don’t want to introduce magic, and the rule that we have followed is that the poly tool should only create new files or read existing files, it never updates files (but that can change if we have good reasons). 3. You could have a separate place where you specify your interfaces, and that’s how we implemented it in the [lein-polylith](https://github.com/tengstrand/lein-polylith) tool. The problem with that was that you still have to write your functions (and everything that is included in the interface) in your components. By eliminating these empty interfaces (or [workspace interfaces](https://github.com/tengstrand/lein-polylith#workspace-interface) as we called them) we got rid of that extra moment of maintaining these interfaces. Another thing to notice here is that in most cases, maybe 95% of the time in average, we only have one component per interface (one “copy” of each interface). An example is the Polylith codebase itself that has 25 components and 25 interfaces at the moment. To implement what you suggest, you probably need to use code generation or something similar, at least if you want to keep the code navigation that you get for free today. I will comment on the suggested namespace change another time, got busy.

seancorfield 2021-06-01T20:50:52.031200Z

Slowly making progress on the refactoring (of mostly low-level stuff so far):

👍 1
2
3
tengstrand 2021-06-02T10:45:41.035300Z

At least it will be easier to refactor these components into smaller components if needed.

seancorfield 2021-06-02T14:57:31.037400Z

Yeah, what's taking the time here is properly splitting things up into appropriate components instead of just moving subprojects into components! I don't want to persist that mistake.

seancorfield 2021-06-02T14:58:08.037600Z

I'm also finding a lot of functions that are unused after a decade of code maintenance :thinking_face:

👍 1
seancorfield 2021-06-01T21:05:13.031500Z

@tengstrand I think it would be a lot more intuitive (and a lot more in keeping with how multi-language Clojure/Script projects work elsewhere), to have components/<thing>/src/clj and components/<thing>/src/cljs as “source paths” because the expectations is likely that a single component might contain some Clojure and some ClojureScript in such projects (as well as .cljc files). But I think @silencioseu’s original Q was just about telling poly what file extension to use when creating new files? Since create c only creates two source files, I don’t think it’s a big deal, but having ext:cljs as an option to create c might be enough for this? (and presumably for any other commands that create source files)

tengstrand 2021-06-01T21:09:51.031700Z

Wow, exciting!

seancorfield 2021-06-01T21:12:35.031900Z

I kind of agree about #2 — adding two lines to deps.edn (`src` and test paths) isn’t much work and you’d have to modify that file any time you add test deps (or dev deps that would be part of a project, rather than part of a brick). And it would be a big tradeoff for the amount of work the poly tool would need to do to correctly edit that file without changing anything else about it — yes, there’s rewrite-cljs but it’s non-trivial to use. And I kinda rail against tools that modify my files anyway (I like tools that tell me what needs changing).

seancorfield 2021-06-01T21:13:21.032100Z

And one of our old subprojects has gone away now, refactored into four separate components (cough!).

🎉 1
tengstrand 2021-06-01T21:13:25.032300Z

I’m not sure why you would want to mix Clojure and ClojureScript code in the same component when the code can’t be shared between them?!

tengstrand 2021-06-01T21:17:45.032500Z

Nice!

seancorfield 2021-06-01T21:18:02.032700Z

The most likely scenario I can think of is a reusable component mostly written in .cljc files but with some platform-specific implementation parts in .clj and .cljs files.

seancorfield 2021-06-01T21:19:20.032900Z

And macros still need to be in .clj files in ClojureScript, right?

seancorfield 2021-06-01T21:20:37.033100Z

So, yes, mixing .clj, .cljs, and .cljc files in a component is a realistic situation. I guess the question is whether folks would feel any need to separate them into folders by language, given that components are generally going to be small? (probably not)

tengstrand 2021-06-01T21:32:50.033300Z

Polylith doesn’t stop people from putting whatever code into a component they want. The poly tool only parses .clj and .cljc code at the moment. So it depends on what people expect the tool to do with the non-Clojure code (if anything).

seancorfield 2021-06-01T21:54:20.033500Z

Right. I think this comes down to the simplified point I made above: “Since `create c` only creates two source files, I don’t think it’s a big deal, but having `ext:cljs` as an option to `create c` might be enough for this? (and presumably for any other commands that create source files)”

✅ 1
seancorfield 2021-06-01T21:56:25.033800Z

(and aside from the actual file extension, the whole issue of a mixed-language workspace is already “permitted” and given the flat structure of Polylith there’s no need for the per-language directory structures that some people adopt in full-stack repos today)

tengstrand 2021-06-01T22:00:59.034Z

I guess time will tell how people will want to work with Polylith.