[swift-users] Feedback for Dependency Injection Framework?

Ian Terrell ian.terrell at gmail.com
Wed Jun 15 09:25:28 CDT 2016


Hi Mike,

Thanks for the library. It looks like you've done a lot of great hacking on
it:

Dependency injection is a topic near and dear to me. :) I'm very curious
about this pattern and its libraries and have been investigating them more
deeply lately. I haven't yet seen the value in this approach (not just in
Swift, but in any language), but if you have time and interest I'd be very
happy to learn your thoughts (and others') on why it's superior to manual
dependency injection. I wouldn't normally do this :), but I'm responding to
your invitation:

Even would be stoked with a response to the tune of "You don't need DI in
> Swift for reasons X, Y, and Z."


As an aside, I tried to peek at your example app
<https://github.com/square/Cleanse/tree/github-initial-version/Examples/CleanseGithubBrowser>
to get more context (it's linked at the bottom of the "Satisfying
Dependencies" section of your README), but it appears missing.

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:

struct GithubListMembersServiceImpl : GithubListMembersService {
>     let githubURL: TaggedProvider<GithubBaseURL>


>
    func listMembers(organizationName: String, handler: [String] -> ()) {
>         let url = githubURL
>             .get()
>
> .URLByAppendingPathComponent("orgs/\(organizationName)/public_members")
>         let dataTask = urlSession.dataTaskWithURL(url) ...


To my mind it doesn't look like githubURL was injected. It looks like it
was looked up in a service locator. In fact, it appears that the Binder is
a service locator, and you can simply set up multiple service locators for
multiple environments (with the added flexibility of multiple types via
tags). I call this solving the problem of inflexible global state (usually
singletons) by making it a flexible global state (a global service locator).

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 excellent motivation page
<https://github.com/google/guice/wiki/Motivation> (my emphasis):

Guice will inspect the annotated constructor, and *lookup* values for each
> parameter.
>

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.

As the functionality of this app grows, one may add arguments to
> RootViewController and its dependencies as well as more modules to satisfy
> them.
>

The argument seems to go that this is easy to maintain:

init(a: A)


But this is difficult:

init(a: A, b: B, c: C, d: D, e: E, ...)


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?

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!

So to me — and with great respect for the work you've done! I know a lot of
people (even some of my teammates with Dagger) value this approach
immensely — I don't personally see how the approach adds value over manual
injection, and I think the complexity is a negative.

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.

So: I'd love to know what sorts of use cases you find where this sort of
library is very helpful.

Thanks,
Ian

For contrast, I've been handling property injection on iOS with a few
helpers, depending on how I want to do it.

For segues identified by identifier (at my company we do tend to use
storyboards with medium frequency; although we keep them separated into
small collections of scenes rather than large unwieldy ones), extensions
help create code like this:

let dependency: Dependency       // itself injected, or

// var dependency: Dependency! // if it could not be injected at
> instantiation



override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
> {
>     switch segueFromStoryboardSegue(segue) {
>     case .GoToCustomViewController(let custom):
>         custom.inject(dependency: dependency)
>     }
> }


That's taken from some experiments I did at
https://github.com/willowtreeapps/segue_handler

Or in simple cases where I mostly care about type, I can do things like
this:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
> {
>     if let custom: CustomViewController =
> segue.destinationViewController.injectable() {
>         custom.inject(dependency: dependency)
>     }
> }


With the extension here:
https://gist.github.com/ianterrell/5aa139ab4b835b85cfee8e0f4430b863

Finally, for that style of property injection a precondition in viewDidLoad
or before usage in other types can ensure that the inject method was called.

In my mind the AppDelegate represents the root of my graph, and is
generally responsible for setting up all the dependencies it needs to care
about. For integration tests or XCUITests (such as I've done, which is
limited, which might be skewing my opinion), I use environment variables or
launch options.




On Tue, Jun 14, 2016 at 7:55 PM, Mike Lewis via swift-users <
swift-users at swift.org> wrote:

> Hi,
>
> I've recently open sourced a dependency injection framework for Swift
> called Cleanse. https://github.com/square/cleanse
>
> It does a couple things I'd consider novel with the type system to make
> wiring up functions easy without having to use reflection or other runtime
> or compile-time hacks (I tried elaborating on it in this part of the README
> here
> https://github.com/square/Cleanse#dependency-requesting-terminating-methods
> )
>
> Anyways, I'm looking for feedback on the design and use of this library,
> and maybe strike up a discussion on language features that may make writing
> a library such as this be able to be "cleaner" in the future. Couple things
> that come to mind are custom annotations for qualifying types (instead of
> what we call "type tags"), variadic generic arguments (which we work around
> by code generating the various arities), or even a plugin architecture to
> achieve things similar to what can be done with Java annotation processors.
>
> I'd also be interested in feedback on some more of the implementation
> details. e.g. is using this to key objects by type a good thing or a
> terrible thing?
> https://github.com/square/Cleanse/blob/master/Cleanse/TypeKeyProtocol.swift
>
> Since Swift generics don't seem to be completely understood by the general
> population, was hoping I could get some concrete feedback here. Even would
> be stoked with a response to the tune of "You don't need DI in Swift for
> reasons X, Y, and Z."
>
> Thanks!
> Mike Lewis
>
> _______________________________________________
> swift-users mailing list
> swift-users at swift.org
> https://lists.swift.org/mailman/listinfo/swift-users
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-users/attachments/20160615/547534d6/attachment.html>


More information about the swift-users mailing list