How would you do “dependency injection” For example, we currently have a simple db protocol (with fn like save, get, find). Then we have separate implementations of this protocol (in memory db and gcp datastore). Code that needs db access simply includes the db namespaces and calls the functions (similar to an interface), without caring about the underlying implementation. (aka we can swap out the implementation from the repl without having to change code). My knee jerk reaction would be to have 3 components in this case 1. A db component, exposing the interface everyone will use for db access. It will also expose a protocol for database implementation. 2. A component for in-mem db implementation, that depends on 1 for the protocol 3. A component for gcp datastore implementation, that depends on 1 for the protocol This solution is 100% biased about what we have in our current systems. Is this the correct way to approach it in the polylith structure? Or is there a better mechanism to deal with
That sounds good. If you finish the migration to Polylith in production, we could also create a page in the high-level documentation where we put you and other users of Polylith, and maybe an output from the 'info' command and some text explaining your experience with Polylith. Could be interesting for people to read.
Yeah totally. We’ve started migrating (@allandaviesza is heading up the initial work).
Cool!
Good morning! I will try to answer your question. To put a piece of functionality into a component has two benefits: 1. The encapsulation and the exposed interface can make the code easier to use and reason about. 2. You get reuse. In your case you benefit only from 1 and in that case I would probably start by putting everything into one component instead, where each implementation of the protocol could live in its own namespace, e.g.:
components
database
src
my/top/namespace
interface.clj
protocol.clj
in-mem-clj
gcp-datastore.clj
Then you would expose the interface for the database access in the interface, and put the code needed for that in the protocol
namespace and the different implementations in the in-mem
and gcp-datastpre
namespaces. Here the implementing code will only be used by one component, so putting it in the same component makes sense to me. You can also split up in-mem
and gcp-datastore
in sub namespaces when you have the need. And don't be afraid of having long descriptive names, which was the reason I called it database
instead of db
!
@tengstrand Thank you for replying. this also solves the problem where people can accidentally use the implementation component. I’ve been really impressed by poly tool. we are very likely to move our existing stack to it. Thanks for the hard work and sharing
That sounds cool! If you decide to migrate your existing codebase to Polylith, we would be very interested to hear about your experience, and also happy to help out.
:thumbsup: will def give feedback. We have an interesting case where we have both existing node and clj services
(as well as a some ui stuff)
@tengstrand is there any reason that polylith.clj.core.workspace-clj.definitions/definitions
does not also include the doc string of a function?
could be handy to auto generate documentation from the component/interfaces
There are several ways to support this (I have my ideas) and it would be interesting to hear what you think would be a good solution, so feel free to add a PR! We have other work we need to do first, but I wouldn't be surprised if we add it later because I think it could be cool to support generation of e.g. HTML based documentation.
The beauty of polylith tool is the fact that you can just get the workspace.edn and then do with that whatever you want. I’m not entirely sure if the poly tool itself should be responsible for document generation. But it would be nice if there was a command that would return all the meta data for interfaces etc. This can then be leveraged to generate documentation (Either by poly tool, or whatever external code/existing tool). I still need to dabble with the code, but my current thinking is to either 1. have an option that enriches the workspace with documentation. (probably fast, but you would need to alter some existing code) 2. have a separate function to which you can pass the workspace and it will use the workspace to go and figure out the documentation/meta data (probably slower, but can be standalone)
One option would be to include everything that a meta function call would return, e.g. (meta #'my-def-defn-or-defmacro), as a :meta attribute to all def/defn/defmacro definitions in the interface. That would include attributes like :doc, :line, :column and more. That could either be added by default, or added if passing in e.g. :meta as an argument to the 'ws' command.
I weary of bloating the workspace data, so having it as an argument makes sense. I agree :meta is better than doc
@tengstrand after playing around and thinking about things, I’ve come up with a slightly different solution. PR with change information here https://github.com/polyfy/polylith/pull/72
Good evening and thanks for the PR! It seems to work as expected. Due to https://github.com/polyfy/polylith/issues/66 we will soon need to make some breaking changes in the 'ws' structure anyway, so I want to investigate more in the weekend what would be the best long term solution here. See you around and don't forget to watch the Mars lander: https://mars.nasa.gov/mars2020/timeline/landing/watch-online
Just so that I understand your question. Do you ask why the definitions
function has a doc string but not function
? I guess there are a lot of missing doc strings in the codebase, not just here. There is a balance how much documentation to put into the code, because it needs to be maintained. That's the reason I have mainly added it where an algorithm or similar has to be explained, or at the top/interface level. Now it's mainly added to help me, so there are room for improvements to support new developers or curious people like you.
Yes, that's true.
I can submit a PR sometime in the future if this is something you are open to
We are looking into this already, so the PR is not required. We will announce when the decision is made, how we will proceed.
No thats not what I’m asking. I’m suggestion, that when you call definitions on a statement, and that statement has a doc string, that you include that docstring in the return definition. example
[{:name ".."
:type ".."
:doc "foo bar zoo"
...}]
That way documentation can be generated for the components interfaces from the workspace hash-map.
I also realise that the ws file might become very bloated if you just include all the doc strings. But it might be a useful feature if implemented similarly to the :loc optionOkay, now i understand, good input. You can add a PR if you want, but otherwise I will add this anyway.