I've known about Component for a while and thought I finally have a project where it makes sense to use it, but after digging into it a bit more I'm leaning towards implementing my own solution.
Mainly I noticed this line in the README: > For small applications, declaring the dependency relationships among components may actually be more work than manually starting all the components in the correct order. You can still use the 'Lifecycle' protocol without using the dependency-injection features, but the added value of 'component' in that case is small.
What interested me as I'm trying to make this library more modular was not having the atomic map I use as a store global and having a constructor to initialize it. A dependency graph of my functions wouldn't be valuable in this case, so it seems to make more sense to just have a macro called from main that creates the atomic map, populates it, and implicitly passes the var to whatever functions are going to be called at the beginning of the chain.
I'd be interested to get anyone familiar with Component's opinion before I abandon it and go that route, i.e. if there's anything it could offer me that would make it worthwhile to use without the dependency graph.
the more deconstructed your application is, the more useful it is going to be in stitching it together
the other incredibly useful feature of component is your system is an anonymous value, it has no name unless you give it one, you can create multiple instances of your systems in a collection and map over them, or run tests against different instances in parallel, etc
Oh, that last part is very interesting
Although I'm not sure useful in this case
if you are doing stuff with vars you are already dealing with global singletons and have thrown away all the benefits of values
I'm not sure what you mean by "deconstructed," but the functions in this library call each other in a fairly deterministic manner. The reason it made me think of Component was because they all pull and push data to a global atomic map. So rather than passing data between themselves they pass a reference to the map, sort of like handrolled continuations and for a similar purpose: because I needed to heavily alter the control flow.
a global atom is not component at all
My understanding is that components are a way to wrap applications that use this pattern so as to avoid using a global map.
But since it's a library and there will be only one global map, I figure I can just encapsulate it in the namespace and use a macro as the constructor called by whatever namespace is using it.
you may just want a graph library and a toposort function
I think that's the part of what components do that I don't want 🙂
Hence why I'm asking if they're even worth using in this case
I doubt they are
if you want to structure an application around in memory global state (which I think is a bad idead to start with), I don't think component is good way to do it
As mentioned, my understanding (largely from watching Stuart's talk on them a few years back) is that they provide an alternative to using a global map.
people tend to seem to have a hard idea coming to grips with what component actually does, so I have this little demo https://gist.github.com/hiredman/075b45eaeb01e4b526ce6f8854685487 which has all the functionality of component, just not as nicely packaged
the alternative they provide is to not use a global map
the state lives in the components that make up the system, not def'ed in vars
it provides an alternative in the "doing something else" sense, not the "makes it easier to structure applications around" sense
so if you want an alternative to using a global map, you can do that with component
So you mean every instance of shared state would constitute its own component?
it depends
I think the difference is in this case I can't pass a map around, I need it to be an atom that all my functions refer to. Components seem to fit the former model better.
but something like at atom is (def x ...)
and then mutated, that state is held in a component, so to mutate the atom you need to depend on that component, and that component has start and stop methods to setup or wipe the state as needed
component is functional and well scoped
Right, so that was the main feature I found appealing.
I would strongly recommend you write functional well scoped code 🙂 in which case component will work great
And I'm understanding now the benefits you get form defining a dependency graph in that, as you mentioned, you can store it in a collection and operate on it that way. But that wouldn't be of use to me with this library. I'm interested in making it a component for the lifecycle management and avoidance of global state.
you need to either directly wire stuff together, or have the dependency graph
But in this case I can't make everything functionally scoped so it seems the patterns are incompatible. The reason I'm passing a reference to a global atom instead of a map itself is because I need to transform the control flow between function calls I chain together.
if you have function F, and function G, and they both fiddle with a global def A, there is a dependency there, component just makes it more explicit
And it would encapsulate the global def?
if you are already passing a reference to a global map, why not pass a reference to a local map?
no
you get rid of the global def
By wrapping the entire library in a component?
sure, and passing functions what they need instead of refering to a global named value
Right. that's what I'm interested in. An alternative to using namespaces for encapsulation and having to pass the reference.
when I start a new project, if it is a library, I want to parameterize it, make it possible for users to pass in whatever values as needed
But I wouldn't get any use out of a dependency graph since the map isn't being passed around. The reference to it is just static.
when I write a server of some kind, that is when I use component to fill in the parameters basically
Yes, and I'm saying I could parameterize it on the namespace level and just use a macro as the constructor. That's the decision I'm trying to make.
Considering I don't need the dependency graph, which seems to be a huge draw of using components.
right, and what I am saying is, that global reference goes against the grain, and I think defeats the purpose of using component
The deal you make with component is that in order to access stateful parts of a lib you express it as a dependency, then you access the stateful parts via your component, they are passed in when your start method is called.
you could define a component with no deps, that someone else could access
@noisesmith that's a helpful explanation. Thanks.
Basically that defining the dependency graph is necessary to avoid passing around a reference to the state.
like many other architectural constructs, it tends to be contagious - to use a component you should implement a component etc.
you still pass it around, the graph just defines where you get it intially
well, a client needs a graph, you could just provide a component
(depending on the scope of your own lib of course)
I guess I'm trying to evaluate the value I'd get from that given I'm just discussing one small library in its own namespace. If it were just a part of the library then I wouldn't have the option of using global state and ns level encapsulation.
for a library I am not sure if it is valuable, I've only used it for constructing applications
And applications calling the library definitely do not need access to its dependency structure. That might be the crux of my decision?
@hiredman I think you're onto something there
some libraries are more like embedded applications though
I’ve made libraries that implement a component but no dependency graph, so that an application can instantiate it and access its state within its own graph
But if I'm not planning on calling it from a component you seem to be saying it's not worth it?
it depends
I definitely would not stick a global singleton in a library
those are the worst libraries
cosign
Is there nothing to say for the ability to use namespaces as modules?
eg. I know two parts of my own cluster both need to set up kafka in a certain way, making a lib that defines that component is useful - because a given server might want to act on N kafka channels and needs to know they are initialized and restartable etc.
well, if a namespace is a module I wouldn’t want any globals that hold state
since we can’t simply make a fresh copy of a given namespace
What if all the state is passed in through a constructor?
as long as it isn't global
if I call the constructor twice with two different states, I expect to get two different things to fiddle with
Ah I see. That's what I wouldn't get rolling my own solution
What's lacking with the "namespaces as modules" pattern is an ability to have multiple instances of them
namespaces are not modules, so there is a lot lacking in that pattern
(depending on which langauge's concepts of modules you are coming in with)
To be clear, I definitely can't require an ns twice with different aliases, right?
no, namespaces are not parameterized
Or rather I can, but it wouldn't do what I'm imagining...
Ok, so that's the functionality I'd be looking for along with a constructor
Or rather the tradeoff I'd be making if I don't use Component
And the README seems to say one can absolutely use it just for the lifecycle methods. I think @noisesmith touched on what that would look like with only shared state in the graph
Still debating...the cost of refactoring seems a bit large, but I don't seen another way to get multiple instances of the library running
Especially since I don't ever plan on integrating this one potential component into a "system." It would literally just be a way to be able to construct multiple instances of it from the client.
I think my conclusion is making this a component doesn't add much more value than just refactoring it to pass around a map (if possible) rather than using a def, which would be a prerequisite anyway.