Fork me on GitHub

Any tips for getting clj -Stree to work within a Polylith repo? The output is showing little more than Clojure’s own deps. Our setup is fairly simply, but we have a couple dozen direct dependencies.


@kendall.buchanan clojure -Stree -A:dev -- in Polylith, your main deps all come in behind a :dev alias.


For example, in the Polylith branch of my usermanager example:

/usermanager-example$ clojure -A:dev -Stree
org.clojure/clojure 1.10.3
  . org.clojure/spec.alpha 0.2.194
  . org.clojure/core.specs.alpha 0.2.56
com.github.seancorfield/next.jdbc 1.2.689
  . org.clojure/ 1.0.86
    . org.clojure/tools.logging 1.1.0
  . camel-snake-kebab/camel-snake-kebab 0.4.2
com.stuartsierra/component 1.0.0
  . com.stuartsierra/dependency 1.0.0
compojure/compojure 1.6.2


@seancorfield Thank you thank you! I was looking for how to incorporate the alias, and finding the tools.deps docs impenetrable.


We're using CircleCI at work which requires a single configuration file for the whole repository. On the other hand It would be best if polylith projects were independent of each other in every aspect, including CI/CD., which combines multiple config files into a single one but I'm not a huge fan (seems like a hack). How do you deal with this problem?


We're not using polylith (yet!) but considering switching to a monorepo approach


Both Polylith repository itself and the Realworld Example repository uses CircleCI to build/deploy. You can check out their configuration files.


Oh damn, you're right, sorry. I don't know why I didn't think of that, thanks.


No worries! Also it could be good to take a look at deployer component in the Polylith repository. It uses api/projects-to-deploy function to determine which projects in the workspace has changed since the last release and only deploys those projects (Although at the moment we only deploy poly, previously we had more than one deployable artifacts. The code is still there for multiple artifacts).


Oh. my mistake, we still deploy two actually, api and poly. So, it should be a good example 😄


If you want to calculate changes separately per project, you can do it by specifying one key per project/system in :tag-patterns e.g.: :tag-patterns {... :system1 “system1-* :another-system “another-system-*}


…that lives in workspace.edn.


Nice, so the following could work, right? 1. Allow selective change detection by specifying custom tag-patterns in workspace.edn (i.e. assign groups) 2. build jars/docker images/whatever only for the projects that have changed 3. deploy just them


To get only the projects that have changed on the CI I personally use clojure -M:poly ws color-mode:none get:changes:changed-or-affected-projects since:release ;; => [“api” “cli”]


Yes. We do that in our Polylith workspace at work, that contains three projects that we build separately (and tag separately).


Yes, and you can replace release with other keys in :tag-patterns if you want.


(never thought of that approach of having one tag per project, it’s probably better)


That's great feedback, thank you all 🙂


I think @U2BDZ9JG3 uses releaseonly, but we use one per project, so it depends on your needs.

Anson MacKeracher18:09:45

Anybody have a recommendation on using "`-`" vs "`" in component names? I think I want the "-`" in the namespace names but "``" in the file system? poly create component name:my-name doesn't try and use underscores for the folder structure

Anson MacKeracher18:09:33

Oo it actually does seem to use underscores in the folder paths under src (and test)


Right, you get - in the component name and the namespaces, but _ in the folders that contain the source/test code.


We have quite a few with - in them:

(! 512)-> ls bases/ components/
artifact-uploader-cli	batch-jobs		preview-web

affiliate-link		configuration		exceptions		http-client-response	seo-keyword
artifact-uploader	crud-form		file-system		member-engagement	site
billing-machine		datasources		gdpr			messaging-sdk		slack-notifications
billing-sdk		date-time		google-search-console	safe-coercions		system-properties
caching			environment		host-services		seo-json-renderer	user-attributes

Anson MacKeracher19:09:40

Beautiful, thanks @seancorfield 🙏 makes total sense


@seancorfield Nice component naming! It’s incredible how clear a domain can become with good naming, at the right level of abstraction.


Indeed. Polylith really helps with the focus here. Hence my June article in praise of that!

Matt Ielusic20:09:09

Would it be a good idea to simplify interface files with Potemkin? That is, go from

(ns polylith.component.interface (:require <etc>))
(defn foo [a] (core/foo a)
(defn bar ([b] (core/bar b nil))
          ([b c] (core/bar b c)))
(def my_constant core/my_constant)
(ns polyith.component.interface (:require <etc>))


I’m very tempted to do that too, but you loose at least the IDE documentation popups and autocompletion I guess when using the interface then


The poly tool parses the source files to figure out which def , defn and defmacro definitions they contain. It would not recognise this Potemkin syntax. The effect would be that it thinks that all your interfaces were empty, and the different checks the tool does, that helps you keeping interfaces in sync (if you have more than one implementation of an interface) would have no effect. If this is important, you could write an issue that you want the tool to support this way of defining interfaces.

👍 2
Matt Ielusic20:09:44

Huh, I had forgotten about the poly tool.


I also think it has a value that all Polylith codebases look the same (or at least similar) which makes it easier for new developers to understand a codebase.


@U0295UQ75FG There are several benefits to the interface/impl separation: • You can order the interface functions alphabetically (no worries about calling fns before they're defined) • The interface functions do not need to have the same signatures as the impl fns -- so you can have better ergonomics and more IDE-friendly arglists • You have a simple ns that anyone can easily read, without worrying about big function bodies getting in the way: the interface can be about names and docstrings and IDE-support (arglists etc). • Tool support: stuff like Potemkin makes that harder in all sorts of ways


In one of our interfaces, we have friendly named functions that all call down to a single generic API-invoker in the implementation: the interface allows for a much cleaner API for users that hides the actual detail of how parameters are passed to the API invoker under the hood. There's no fixed rule that says interface to impl should be 1:1 delegation.


Trust me, once you've been using Polylith for a while, you will stop worrying about trying to "make interfaces simpler" 🙂


I realized that was why I pushed for <top-ns>.<brick-name> to be allowed as the interface, with the .interface suffix, but as I used Polylith more, I came to see that having .interface there was actually a very useful signifier -- especially as we're working with a mixed codebase that is migrating to Polylith: at a glance we can identify new, Polylith component namespaces from older, legacy subproject namespaces (because the former always have .interface on the end).

Matt Ielusic21:09:48

> There's no fixed rule that says interface to impl should be 1:1 delegation. My migration process has caused a 1:1 delegation from interface to impl... But point taken.


An example of the above:

;; Billing Admin API bridge

(defn freeze-membership
  "Given a BillingSDK component, an AzureAD token, and a member ID,
  POST to the Billing Admin API to freeze a membership."
  [sdk azure-token member-id]
  (impl/post-to-api sdk :admin azure-token (format "/freeze/%s" member-id)))

(defn thaw-membership
  "Given a BillingSDK component, an AzureAD token, and a member ID,
  POST to the Billing Admin API to thaw (unfreeze) a membership."
  [sdk azure-token member-id]
  (impl/post-to-api sdk :admin azure-token (format "/thaw/%s" member-id)))

(defn cancel-membership
  "Given a BillingSDK component, an AzureAD token, and a member ID,
  POST to the Billing Admin API to cancel a membership."
  [sdk azure-token member-id]
  (impl/post-to-api sdk :admin azure-token (format "/cancel/%s" member-id)))


Client code doesn't need to know URLs. Impl code doesn't need to know about the actual API functions.


(we're doing our migration manually so we can improve the naming, modularity, and structure of the code as we do it)