<div dir="ltr">Hi Ian,<div><br></div><div>Shouldn't the `ViewController_B` be a dependency and not be instantiated inside a `ViewController_A`? Or ViewControllers/Controllers are not considered dependencies?<div><br></div><div>Thanks,</div><div>Tadeas</div></div></div><br><div class="gmail_quote"><div dir="ltr">On Thu, Jun 16, 2016 at 10:50 PM Ian Terrell via swift-users <<a href="mailto:swift-users@swift.org">swift-users@swift.org</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr">Hi Mike,<div><br></div><div>Thanks for your response. :) Let me address your points. I apologize for the length; this is a topic I care about and I know my co-workers are reading it. :)</div><div><br></div><div>I'm going to edit the message history for brevity.</div><div><br></div><div class="gmail_extra"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><span><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><div dir="ltr"></div></blockquote></span></div></div></div></blockquote></div></div></div><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><span><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div></div><div>My first point of confusion is that these libraries seem to be less about dependency injection and more about service location. For instance, in your GitHub example in the playground, we see the following:</div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><font face="tahoma, sans-serif">struct GithubListMembersServiceImpl : GithubListMembersService {<br> let githubURL: TaggedProvider<GithubBaseURL></font></blockquote><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><font face="tahoma, sans-serif"> <br></font></blockquote><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><font face="tahoma, sans-serif"> func listMembers(organizationName: String, handler: [String] -> ()) {<br> let url = githubURL.get()</font></blockquote><div><br></div></div></blockquote></span></div></div></div></blockquote></div></div></div><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><span><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div>To my mind it doesn't look like githubURL was injected. It looks like it was looked up in a service locator. </div></div></blockquote></span></div></div></div></blockquote></div></div></div><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><div>I'm not very familiar with the service locator pattern to be honest, however, I can assure you that the base URL is being injected. I think there may be some confusion on either how the <span style="font-family:tahoma,sans-serif">GithubListMembersServiceImpl is being constructed, or tagged providers in general.</span></div></div></div></div></blockquote></div></div></div><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"></div></div></div></blockquote><div><br></div><div>I don't believe I'm confused over those features, and I do understand that githubURL is being injected from your point of view. What I mean though is that it's being injected from a global location, not from the main call site.<br></div><div><br></div><div>In service location code that needs a service asks for it, and the service locator provides it. In this case you are asking for a GithubBaseURL. The (in my opinion problematic) pattern occurs even if you rewrite the service implementation as you did:</div></div></div></div><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><div><br></div><div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><font face="tahoma, sans-serif">struct GithubListMembersServiceImpl : GithubListMembersService {<br> let githubURL: <b>NSURL</b></font></blockquote><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><font face="tahoma, sans-serif"> <br></font></blockquote><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><font face="tahoma, sans-serif"><span> func listMembers(organizationName: String, handler: [String] -> ()) {<br> let url = githubURL<br></span><span> .URLByAppendingPathComponent("orgs/\(organizationName)/public_members")<br> let dataTask = urlSession.dataTaskWithURL(url) ...</span></font></blockquote></div><div><br></div></div></div></div><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><div>Definitely from the perspective of GithubListMembersServiceImpl the githubURL is injected. That makes it nicely testable and well designed. However in this case the service location is merely once removed (and is already present in the prior version as well): now your code is asking a service locator for the GithubListMembersService (client code will now have to call GithubListMembersService.get()).</div><div><br></div><div>I see dependency injection as being given your dependencies. I see service location as asking for your dependencies. In Cleanse and Dagger and Guice I feel like the dependencies are being asked for (the get() method is <i>asking something else</i> to be provided the call site with an instance).</div><div><br></div><div>In this case, I believe your "component" is the service locator.</div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"></div></div></div></blockquote></div></div></div><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><div>and then change the binding statement to be</div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"> binder<br> .bind()<br> .to(value: NSURL(string: "<a href="https://api.github.com" target="_blank">https://api.github.com</a>")!)</blockquote></div></div></div></blockquote></div></div></div><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><div><div><br></div><div>The downside of this in an actual application is that you may want to inject other URLs. These "<a>Type Tags</a>" TaggedProviders are essentially just a ...</div></div></div></div></div></blockquote><div><br></div><div>And this is why I think Dagger and Guice and Cleanse are improperly categorized as dependency injection frameworks. I think in reality they are service locators and factories. Dependency injection needs no framework!</div><div><br></div><div>With manual dependency injection, when different services want different URLs you simply inject them normally.</div><div><br></div><div>call site A: <span style="font-family:tahoma,sans-serif">MembersServiceImpl(baseURL: productionURL)</span></div><div>call site B: <span style="font-family:tahoma,sans-serif">MembersServiceImpl(baseURL: stagingURL)</span><span style="font-family:tahoma,sans-serif"><br></span></div><div><span style="font-family:tahoma,sans-serif"><br></span></div><div><span style="font-family:tahoma,sans-serif">That requires that the call sites manage that dependency in order to know what to pass (feature not a bug).</span></div></div></div></div><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><span><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div></div><div>I've always thought of dependency injection as being a tool for avoiding global state. But in this case it seems to me like the service locator is globally managed. The language of service locators is even present in Guice's <a href="https://github.com/google/guice/wiki/Motivation" target="_blank">excellent motivation page</a> (my emphasis):</div><div><br></div></div></blockquote></span><div><br>The only "global" state are things that are declared singletons. (which are still only singletons in the scope of each time one calls build). They have very similar semantics to <a href="https://github.com/google/guice/wiki/Scopes" target="_blank">singletons in Guice</a> (however no custom scopes yet as mentioned above).</div></div></div></div></blockquote><div><br></div></div></div></div><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><div>Global is not quite accurate, but it is not terribly close. Your Component object tree is what I mean, and I view it (perhaps wrongly) as "semi-global." I get the sense that in most applications there will end up being exactly one. A better word is "external". Now your objects rely on external state, whether it is global or semi-global.</div></div></div></div><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><div> <br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><span style="font-family:tahoma,sans-serif;font-size:12.8px">For the former, there was feedback regarding the constructors being implicitly made for structs in this </span><a href="https://www.reddit.com/r/swift/comments/4o2lno/squarecleanse_swift_dependency_injection/d49pu6c" style="font-family:tahoma,sans-serif;font-size:12.8px" target="_blank">reddit thread</a><span style="font-family:tahoma,sans-serif;font-size:12.8px">.</span><br></blockquote></div></div></div><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><div><br>You pointed out in that thread that these frameworks make it easier to keep up with new dependencies, reordered initializer parameters, etc — but all that hiding I fear would lead to poor architectural decisions in practice. Too many objects end up depending on too many things, because those dependencies are hidden from them. That means that real refactors (trying to use these objects in un-forethought contexts, etc) become more difficult, and your app actually becomes more tightly coupled (it's so easy to .get() that other object!).<br></div></div></div></div><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><span><div> Guice will inspect the annotated constructor, and <b><i>lookup</i></b> values for each parameter.</div><div> <br></div></span><div>Cleanse actually achieves the same thing, but without reflection. I attempted to explain it in this part of the <a href="https://github.com/square/Cleanse/blob/master/README.rst#terminating-methods-top1factory-p1---e-1st-arity" target="_blank">README</a>, but I'll be honest, I struggled while attempting to make it both simple and detailed enough to convey what's going on.</div></div></div></div></blockquote><div><br></div></div></div></div><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><div>I think I get what's going on. I only meant that the words "look up" imply service location to me. In my mind you're registering (via bind()) service factory instructions to the service locator. At runtime, the service locator/factory <i>looks up </i>how to build it and returns you an instance.</div><div> </div><div>Big snippet below for all the context:</div></div></div></div><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><span><div>Another point I've heard people talk about is that these libraries help as dependencies grow. I hope this isn't a straw man, as I interpret the following line in your README in that way.</div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex">As the functionality of this app grows, one may add arguments to RootViewController and its dependencies as well as more modules to satisfy them.<br></blockquote><div><br></div><div>The argument seems to go that this is easy to maintain:</div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><font face="tahoma, sans-serif">init(a: A)</font></blockquote><div><br></div><div>But this is difficult:</div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><font face="tahoma, sans-serif">init(a: A, b: B, c: C, d: D, e: E, ...)</font></blockquote><div><br></div><div>The argument goes further by saying that it becomes especially difficult as you may need to pass the dependencies through the some nodes of the object graph that seem not to need them. For instance, in the object graph A -> B -> C, if C has a dependency on Foo but B does not, why should B know about Foo?</div><div><br></div><div>But I find that argument unconvincing on two grounds. First, I believe an object's dependencies are the union of its and its children's dependencies. In the example above, B has an implicit dependency on Foo. "Skipping" passing them around or instantiating them in the owning class is actually only hiding an object's true dependencies by using global state. Second, as a single object's dependencies become more unwieldy it seems in practice to indicate an architectural problem where objects are doing too much. These problems are related, as the architectural problem may not be as visible when the true dependencies are hidden!</div></div></blockquote><div><br></div></span><div>So this is an interesting point. I'm a strong believer that DI solves this issue really well.</div><div><br></div><div>Let's take this example (I think its similar to the straw man you are referring to):</div><div><br></div><div><a href="https://gist.github.com/mikelikespie/c54a017677265322df7eb785d9f36345" target="_blank">https://gist.github.com/mikelikespie/c54a017677265322df7eb785d9f36345</a><br></div><div><br></div><div>Basically, VC_A, needs serviceA, and a VC_B to do its job, however, for it to create VC_B it needs VC_B's dependencies + its transitive dependencies which happen to be serviceA, serviceB, serviceC.</div><div><br></div><div>So I agree with the arguments made in the straw man, e.g. don't need to change the constructor hierarchy all the way down when VC_D needs a new service or needs to start depending on something. It takes a pretty large example to start to see these benefits, but its kind of like how you don't really see a benefit from a quicksort over a bubble sort until you have a lot of items to sort.</div><div><br></div><div>So if we refactor as is (without turning anything into protocols) to use Cleanse, we come up with something like:</div><div><br></div><div><a href="https://gist.github.com/mikelikespie/3abe371bf7f7ab67f71d6cfa22c0145d" target="_blank">https://gist.github.com/mikelikespie/3abe371bf7f7ab67f71d6cfa22c0145d</a><br></div><div><br></div><div>Now, say serviceD incurred a new dependency, one would just add it to its constructor and make sure the dependency is configured... don't have to change any of the other view controllers in the hierarchy.</div></div></div></div></blockquote><div><br></div></div></div></div><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><div>Ok! So, we're on the exact same page with the code and the problem, and perhaps even the same conclusion, but the exact opposite solutions.</div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex">I'm a strong believer that DI solves this issue really well. ... It takes a pretty large example to start to see these benefits<br></blockquote><div><br></div><div>I believe that true dependency injection is the solution, but I think that Cleanse/Dagger/Guice aren't really providing that in a useful way. I think the refactored version using Cleanse is significantly worse than the non-refactored version. Additionally, I think the larger the project (in your view where it shines), the worse the problem gets (in my view where it hinders).</div><div><br></div><div>Concretely: In both examples, ViewController_A has dependencies on services A, B, C, and D. But only in the non-Cleanse version is that visible. This means that it has hidden, non-explicit dependencies, that have to be looked up somewhere else. And they're looked up from the service locator/factory VC_A_Component. We cannot use ViewController_A outside of the context of that special knowledge and setup.</div><div><br></div><div>These frameworks aren't removing dependencies, they're hiding them. This makes it less easy to reason about the code. </div><div><br></div><div>As an example of reasoning, a reader of the component/service locator setup in the Cleanse example has to wonder: Why am I binding Services A, B, C, and D and VCs A, B, C, and D just to create a single VC_A? Now the reader has to inspect all of the view controllers to find out! After which she learns, well, VC_A uses S_A and can create a VC_B, which uses S_B and can create a VC_C, which uses S_C and can create a VC_D, and VC_D uses S_D. All of that information must be discovered and tied together just to get() a single VC_A! </div><div><br></div><div>In the non-Cleanse example, the question is: Why do I need to instantiate VC_A with Services A, B, C, and D? And the answer is simple and comes from a single view controller (the one we care about): because VC_A uses S_A can create a VC_B and VC_B uses services B, C, and D.</div><div><br></div><div>That's a huge contrast in reasoning, and it's because Cleanse/Dagger/Guice are hiding information in external service locators.</div><div><br></div><div>I suppose I might phrase my point of view as saying that I don't believe in transitive dependencies; and definitely not in treating them differently. You have your dependencies, and by depending on something else that has dependencies, those transitive dependencies are de facto yours, directly. They get no special treatment, because they cannot; treating them specially relies on external state. That is breaking encapsulation.</div></div></div></div><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><div>This gets me to the next point, testing. VC_A's purpose is to call serviceA and present a view controller. To properly unit test its functionality, it may not be necessary to provide a concrete implementation of the VC it wants to present. In general if one can test a component and its functionality correctly with less dependencies that's better.<br></div></div></div></div></blockquote><div><br></div></div></div></div><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><div>I would never advocate anything that wasn't properly and nicely testable. I take your point that it seems simpler to test in this setup, but I don't really believe it's simpler. Again, that's because the true dependencies are hidden.</div><div><br></div><div>In the case of manual dependency injection as I advocate, the solution is protocols and empty implementations. I agree that empty implementations of protocols can be annoying to maintain, but I find they solve the problem very well in a clean and "dumb" manner.</div><div><br></div><div>For instance, to test VC_A, assuming all the services were protocols, you could create an instance solely to test its service A functionality like so:</div><div><br></div><div><div>let testedVC = ViewController_A(serviceA: TestingServiceA(), serviceB: DummyServiceB(), </div><div> serviceC: DummyServiceC(), serviceD: DummyServiceD())</div></div><div><br></div><div><a href="https://gist.github.com/ianterrell/c86aa1ad64453a214966ac81b31f75e9" target="_blank">https://gist.github.com/ianterrell/c86aa1ad64453a214966ac81b31f75e9</a><br></div></div></div></div><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><span><div></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div>I do think there is value in aiding with property injection for the cases where we can't use initializer injection (we do often use storyboards at my company, although we try to keep them small and unwieldy!), but I think those cases are solved with a few clever extensions. See post-script if you're curious.</div></div></blockquote><div><br></div></span><div>Cleanse <a href="https://github.com/square/Cleanse#property-injection" target="_blank">does support property injection</a>. My examples in the readme only really demonstrate using it for the AppDelegate, but it also makes sense for storyboard injection. It is very explicit though (less magic than other DI frameworks). I also think there would be room for a cleanse extension that specifically deals with storyboards. (I kinda like what RxSwift did with RxCocoa).</div></div></div></div></blockquote><div><br></div></div></div></div><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><div>Yes, I saw it. I think I only meant that I do see manual property injection with UIKit classes as sometimes cumbersome, but I believe there are better solutions than frameworks like these.</div><div><br></div><div>I don't believe I've misinterpreted your project or it's documentation. I anticipate that we may simply believe in different solutions to the hard problem or dependency management. In that case I leave this conversation happily agreeing to disagree, just as I do with some of my other colleagues here. :) But at least I hope I've well explained why I personally view these solutions as detrimental.</div><div><br></div><div>Thanks,</div><div>Ian</div><div><br></div></div></div></div>
_______________________________________________<br>
swift-users mailing list<br>
<a href="mailto:swift-users@swift.org" target="_blank">swift-users@swift.org</a><br>
<a href="https://lists.swift.org/mailman/listinfo/swift-users" rel="noreferrer" target="_blank">https://lists.swift.org/mailman/listinfo/swift-users</a><br>
</blockquote></div><div dir="ltr">-- <br></div><div data-smartmail="gmail_signature"><div dir="ltr">Hello majk!<div>das</div><div>d</div><div>asd</div><div>as</div><div>dasd</div></div></div>