dime
dime copied to clipboard
Namespaced keywords
I love the idea. I was thinking however to avoid naming collisions I might need to use namespaced keywords. For example, I found another cool library that generates API and basic type conversions from openapi spec or swagger files. I may want to have multiple such api clients in my application and thus was thinking to namespace qualify
However, if I want to use a namespace keyword then things get a lot more verbose.
I noticed that the double colon ::keys
syntax does not seem to work.
{::keys [swagger api-root authentication-header tracing-interceptor]
I either need to use ^{:inject ::swagger} swagger
or [^:inject {:keys [ya.ynab/swagger ya.ynab/api-root ya.ynab/authentication-header ya.ynab/tracing-interceptor]}]
It would also be nice if I could somehow make namespace keywords the default as well so that I did not need to also override on expose (defconst ^{:expose ::martain} martain ...
Personally, I think maybe namespace keywords should be the default and maybe another implementation of dv/ns-vars->graph that prefers namespace keywords for ^:expose
and ^:inject
@kurtharriger I think I get your idea behind the auto-namespaced keywords throughout a project, and Dime should make it easy for someone who wants such an arrangement. Maybe support for ::inject
and ::expose
to auto-infer with namespaced keywords can help? (However, note that visually distinguishing between :inject
and ::inject
may be hard, during debugging for example.)
The ::keys
syntax not working is clearly a bug (not in parity with clojure.core
) and I would like to fix that.
While I think it makes a lot of sense to use namespaced keywords, I am not so sure about auto-namespaced keywords being default for the reasons below:
- Non-namespaced keywords are simple, not requiring cognitive overhead of namespaced ones
- Naming is a stylistic choice people make for their projects, and simple is a better default when not obstructing other choices
- Namespace association of a keyword is not limited to real namespaces, one could use synthetic namespaces
- Auto-namespace (the
::shorthand
notation) is always tied to a real namespace - In complex projects, injection context could follow synthetic namespaces, for example
- Synthetic namespaces may be a better tool to communicate design (subject to people involved)
- Auto-namespace (the
Personally, I think maybe namespace keywords should be the default and maybe another implementation of dv/ns-vars->graph that prefers namespace keywords for
^:expose
and^:inject
I agree that a mechanism to enforce naming style in a project may be useful. The constraint being, it should compose with the fundamental expose/inject constructs instead of altering how those are interpreted (creates ambiguity that is hard to fix out of band). Since dv/ns-vars->graph
returns just data, it should be easy to write a function that reads that data and verifies the naming style.
Oh, I like the idea of ::expose
and ::inject
when you want to use qualified keys.
I also just discovered that using slight variation in syntax does work and is not much more verbose.
(dv/defconst ^{:expose ::connection-pool} make-qualified-db-pool
[^:inject {:keys [::db-host ::db-port ::username ::password]}]
:dummy-pool3)
I think maybe my hesitation around unqualified keywords is also exemplified by the foo.db example in tests.
In the tests you have used :db-host
and :db-port
which are likely global enough within the scope of the project, but what about username and password. Initially, these seem reasonable names when only one username is needed. Perhaps you later move your db logic into a shared library for use in multiple projects.
Perhaps you could also create a facade that builds the graph and does di/inject-all so that consumers don't need to know that dime is used internally, but if you are using dime in all your projects now perhaps it is easiest and more flexible to just expose the graph so that consumers can override components, etc. After all, it's not all that unusual in the java spring boot world to have libraries that assume you are using spring to inject dependencies, and it would be otherwise difficult to determine how to construct these dependency graphs manually without using spring.
Later perhaps you create a project to crawl github account for open issues and this service also needs a :username
but since these are unrelated at the time there is no conflict. Now you decide hey I should put these issues in a database and pull in your db library, but now when you go to inject connect-pool it tries to use your github username and fails to connect.
With a little foresight maybe db library should have used :db-username
instead... but now it is part of a library and changing it may break other consumers. Within the context of a single application it is easy enough to avoid conflicts... and rename as necessary, but if you start using dime in libraries naming collisions could become an issue.
In spring a dependency also has a type which is the fully qualified package name of the interface and thus the potential for bean name collisions of items of the same type is smaller. But without a type associated with arguments naming collisions seem more likely.
I suppose it would be possible to transform the imported db dependency graph before merging it since it is just data... but my thinking is if names are qualified then you could easily move code into libraries and continue to use dime to wire dependencies and allow library consumers more flexibility to override components without any ambiguity or naming conflicts when merging graphs that may change over time as libraries are updated.
Qualifying keywords seems like an easy solution to avoid these collisions in the first place. However, it is certainly not the only solution and I agree that namespace keywords may introduce their own complexities.
I just see naming collisions could be a barrier to using dime within libraries without creating a facade to hide the internal graph, and subsequently make it more difficult for consumers to override the components.
Maybe another alternative would be some helper functions for merging subgraphs such that both the db library and github library could use :username
independently but when the graphs are merged one can apply a transformation that will rename db :username
to :issuedb-username
and github library graph from :username
to :github-username
and any unmapped collisions would be isolated to the subgraphs perhaps?
As you've correctly brought out above, the example code needs work to name the arguments properly.
I have thought about the library problem, and found name-collision and context-bridging to be a twisted problem as you noted as well. Though it's been on my TODO items, I think I need to learn more from applying Dime to complex scenarios, probably using transformations like you described as one of the ways.