Fork me on GitHub

I am still in the first step in the migration from a monolith and I am a bit confused by the way dependencies (or libraries in polylith linguo should be organized. My first assumption was that the libraries should be added as deps/extra-deps in the smaller brick: project > bases > components. Being at the first step of the migration I put all the libraries in the base. It worked for the project tests, but when trying to kick off a repl with the dev and test profiles I'm now missing dependencies. Do all libraries have to be duplicated in the extra-deps of the top-level deps.edn to have them available in the development project? What I find odd with that pattern is that if one updates a dependency only in ./deps.edn, it will use the new version in development, but will use the old one in the projects?


It depend how your root deps.edn looks like. If you have a setup where the dev alias of your root deps.edn lists the components and bases as :local/root in the :extra-deps, then the development REPL will get all the dependencies from the individual components and bases. However, if you are listing the components and bases in the :extra-paths, then you need to duplicate all the dependencies again in the deps.edn. Polylith relies on the default behavior of tools.deps when it comes to resolving dependencies.


Answer to second question: Polylith does not check the dependency versions for you, you should always make sure you have what you want in any project, whether it’s development or production. What Polylith provides is the flexibility to change dependencies for your development or for your projects. You can also define libraries in the project level deps.edn to set them to a certain version, overriding the version that comes from your component, if needed. That is handy if indirect dependencies in different components have different versions, so you can choose which version to use at the project level.


Also, if you have tests that fail due to different versions in development vs project, you’ll catch them when the project level tests run.


At work we use the :extra-deps approach in :dev with :local/root so each brick can have its own (external) library dependencies. Then the :dev alias only contains any additional/specific dependencies we want for development -- which is a specific set of logging libraries and a JDBC driver.


Then each project specifies just those extra dependencies (and, in one case, pins a specific version of the Jackson libraries to avoid a breaking change that breaks just one of our apps). This allows for different databases for dev vs built projects and different logging setups (although, right now, we are using the same ones in all but one project).


@U2BDZ9JG3 I tried the local/root path already, and noticed it wasn't working for the extra deps of the test profile of my base, I will try again


I think @U04V70XH6 has the best setup that you can have in regards to handling the library dependencies. That is how Polylith intended the :extra-deps and :local/root to be used.


@U0DB715GU In any case, either :local/root in :extra-deps or with :extra-paths you need to have a :test alias in your root deps.edn and start your REPL with at least :dev and :test aliases in order to get test paths are included in your REPL session. If you have any library dependencies in components/bases :test alias, you need to duplicate them to :extra-deps in :test alias of the root deps.edn. The reason for this is, :local/root does not bring the aliases.


It would be great if tools.deps supported something like {:local/root "components/comp-1" :aliases [:test]}, then you wouldn’t need duplication test paths and dependencies.


Thank you both for the super clear explanations! I think incorporating that to the doc might be quite useful for others attempting the jump


Is that still true? I thought that poly test brought in dependencies from :test in bricks when building the classpath for testing projects (including development)?


@U04V70XH6 it does for poly test, I didn't specify the test dependencies in my project and the tests are running fine with it. What doesn't work is running the tests from the repl after starting it with dev and test aliases in the development project


Ah, right. I think we must have very few test-only dependencies at work -- at least in our Polylith code... our legacy code definitely has some test-only dependencies so we probably just haven't run into this. Our :test alias is:

:test ; Polylith's testing context for the development project
  {:extra-deps {worldsingles/worldsingles-test {:local/root "worldsingles-test"}
                com.github.seancorfield/expectations {:mvn/version "2.0.0-alpha2"}
                org.clojure/test.check {:mvn/version "1.1.0"}}
where worldsingles-test is a legacy subproject that we haven't migrated yet and it brings in a lot of external deps via its dependencies.


Do you have all your projects in one Polylith workspace? Do you try to keep all components as versatile as possible? Or do you separate components by project like My understanding of this example is that /article/ contains the generic components that the developer associates with that word, and /realword/ further specifies the meaning - which is determined by the project with the same name. Right?


I am about to migrate my projects to Polylith but i am still not sure what is the good way to organize all components i will create / extract.

Andrés Rodríguez20:11:56

As a user still early in Polylith adoption: We don't, but we have considered it and we already know we have a couple of projects we will integrate into our main polylith because they have slightly cross-cutting concerns and there's potential for code reuse. The others will remain in the monorepo but outside of the polylith (which is its own folder). If there's little to no potential to reuse code, I probably would not introduce it there. Mainly because I worry about polluting the component space too much with little to show for it and just end up creating components like projectA-billing and projectB-billing when there's a conceptual overlap but implementations that need to be clearly different. Not sure if that worry is justified, but I'm instinctually inclined to believe two isolated trees (or workspaces, in Polylith lingo) would be cleaner if they really don't have too much to do with each other.

👍 1

We have 6 projects, more than 100 components in a single Polylith workspace. Our components have single responsibility and usually around couple hundred lines. We create projects and bases when the non-functional requirements change: e.g. a specific part of the codebase needs to scale differently than other parts, then a new project is created and has its own deployable artifact. We keep everything in one workspace and intended to continue that as a principle because we love to have a single development environment that gives us a REPL with access to all of our code. Very easy refactoring and debugging. We keep components small and they are responsible of one thing. 1) It makes them easily replaceable, if needed. If there is a logger component with a function log, its easy to write a replacement component that has same signature but using another logging library. 2) It makes them very reusable, if we need the set of functions that a component exposes anywhere, we require it.

👍 1

@U2BDZ9JG3 This is why I would like to start using Polylith. It seems to me that the combination of modular codebase + repl can offer a developer an environment that resembles Smalltalk - an extensible image/workspace from which different applications emerge. But I’m still trying to understand this namespace from the realworld demos. (ns clojure.realworld.article.core) clojure. you are using to decouple from CLJS I assume. But why is it part of the realworld namespace if the components are supposed to be universal and used across projects (and realworld is just one of the projects, right?). thx


clojure.realworld is the top-namespace for that specific workspace, so every namespace in the workspace needs to be under that top-namespace ( The poly CLI tool does static code analysis to provide many features and it uses the top-namespace to differentiate namespaces from external dependencies from the components and bases.


The specific words in clojure.realworld is totally my personal choice. Realworld has implementations in many different languages, so, I thought it would be nice to indicate it in the namespace. It has nothing to do with Polylith.


You can interpret this namespace clojure.realworld.article.core as: workspace top namespace: clojure.realworld component name: article Similar with bases, e.g.: workspace top namespace: clojure.realworld base name: rest-api

thanks 1

A little clarification. Often the component name and its interface share the same name as in this example (`article`) but to be correct, article in clojure.realworld.article.core is actually the interface name, and if we create another component, let’s say another-article we can let it implement the same article interface and if it also had a core namespace, then it would also live in clojure.realworld.article.core . One would live under components/article and the other one under components/another-article but they would share the same clojure.realworld.article “interface top namespace”.

polylith 2