Fork me on GitHub
#component
<
2017-06-07
>
sophiago20:06:42

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.

sophiago20:06:47

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.

sophiago20:06:46

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.

sophiago20:06:01

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.

hiredman20:06:48

the more deconstructed your application is, the more useful it is going to be in stitching it together

hiredman20:06:00

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

sophiago20:06:29

Oh, that last part is very interesting

sophiago20:06:39

Although I'm not sure useful in this case

hiredman20:06:09

if you are doing stuff with vars you are already dealing with global singletons and have thrown away all the benefits of values

sophiago20:06:13

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.

hiredman20:06:02

a global atom is not component at all

sophiago20:06:44

My understanding is that components are a way to wrap applications that use this pattern so as to avoid using a global map.

sophiago20:06:13

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.

hiredman20:06:38

you may just want a graph library and a toposort function

sophiago20:06:11

I think that's the part of what components do that I don't want 🙂

sophiago20:06:22

Hence why I'm asking if they're even worth using in this case

hiredman20:06:32

I doubt they are

hiredman20:06:03

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

sophiago20:06:03

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.

hiredman20:06:38

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

hiredman20:06:55

the alternative they provide is to not use a global map

hiredman20:06:17

the state lives in the components that make up the system, not def'ed in vars

hiredman20:06:49

it provides an alternative in the "doing something else" sense, not the "makes it easier to structure applications around" sense

hiredman20:06:17

so if you want an alternative to using a global map, you can do that with component

sophiago20:06:42

So you mean every instance of shared state would constitute its own component?

sophiago20:06:34

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.

hiredman20:06:55

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

hiredman20:06:22

component is functional and well scoped

sophiago20:06:24

Right, so that was the main feature I found appealing.

hiredman20:06:18

I would strongly recommend you write functional well scoped code 🙂 in which case component will work great

sophiago20:06:14

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.

hiredman20:06:06

you need to either directly wire stuff together, or have the dependency graph

sophiago20:06:27

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.

hiredman20:06:02

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

sophiago20:06:26

And it would encapsulate the global def?

hiredman20:06:32

if you are already passing a reference to a global map, why not pass a reference to a local map?

hiredman20:06:46

you get rid of the global def

sophiago20:06:03

By wrapping the entire library in a component?

hiredman20:06:35

sure, and passing functions what they need instead of refering to a global named value

sophiago20:06:19

Right. that's what I'm interested in. An alternative to using namespaces for encapsulation and having to pass the reference.

hiredman20:06:50

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

sophiago20:06:56

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.

hiredman20:06:17

when I write a server of some kind, that is when I use component to fill in the parameters basically

sophiago20:06:47

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.

sophiago20:06:02

Considering I don't need the dependency graph, which seems to be a huge draw of using components.

hiredman20:06:16

right, and what I am saying is, that global reference goes against the grain, and I think defeats the purpose of using component

noisesmith20:06:25

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.

noisesmith20:06:50

you could define a component with no deps, that someone else could access

sophiago20:06:52

@noisesmith that's a helpful explanation. Thanks.

sophiago20:06:31

Basically that defining the dependency graph is necessary to avoid passing around a reference to the state.

noisesmith20:06:57

like many other architectural constructs, it tends to be contagious - to use a component you should implement a component etc.

hiredman20:06:07

you still pass it around, the graph just defines where you get it intially

noisesmith20:06:09

well, a client needs a graph, you could just provide a component

noisesmith20:06:29

(depending on the scope of your own lib of course)

sophiago20:06:59

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.

hiredman20:06:40

for a library I am not sure if it is valuable, I've only used it for constructing applications

sophiago20:06:41

And applications calling the library definitely do not need access to its dependency structure. That might be the crux of my decision?

sophiago20:06:56

@hiredman I think you're onto something there

hiredman20:06:21

some libraries are more like embedded applications though

noisesmith20:06:01

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

sophiago20:06:29

But if I'm not planning on calling it from a component you seem to be saying it's not worth it?

hiredman20:06:56

I definitely would not stick a global singleton in a library

hiredman20:06:05

those are the worst libraries

sophiago20:06:20

Is there nothing to say for the ability to use namespaces as modules?

noisesmith20:06:31

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.

noisesmith20:06:45

well, if a namespace is a module I wouldn’t want any globals that hold state

noisesmith20:06:03

since we can’t simply make a fresh copy of a given namespace

sophiago20:06:06

What if all the state is passed in through a constructor?

hiredman20:06:19

as long as it isn't global

hiredman20:06:44

if I call the constructor twice with two different states, I expect to get two different things to fiddle with

sophiago20:06:59

Ah I see. That's what I wouldn't get rolling my own solution

sophiago20:06:34

What's lacking with the "namespaces as modules" pattern is an ability to have multiple instances of them

hiredman20:06:50

namespaces are not modules, so there is a lot lacking in that pattern

hiredman20:06:07

(depending on which langauge's concepts of modules you are coming in with)

sophiago20:06:09

To be clear, I definitely can't require an ns twice with different aliases, right?

hiredman20:06:24

no, namespaces are not parameterized

sophiago20:06:31

Or rather I can, but it wouldn't do what I'm imagining...

sophiago20:06:48

Ok, so that's the functionality I'd be looking for along with a constructor

sophiago20:06:10

Or rather the tradeoff I'd be making if I don't use Component

sophiago20:06:29

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

sophiago20:06:53

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

sophiago20:06:15

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.

sophiago20:06:11

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.