What I do for DI...

This is connected to the "Why I hate DI..." post. It was a single post... but got stupid long... so I split it.

Let's take a moment to look at what I do... do. HAHAHAHAHAHA... It's my blog, I get to laugh at that if I want to

I recently (over the past two days and finished it earlier today) read Dependency Injection by Steven van Deursen and Mark Seemann.

It showed me that how I do things very quickly becomes an anti-pattern if not careful. Which... Oh man that rocked my world until I identified why it's NOT an anti-pattern.
I'm holding onto this pretty tentatively; so... I'd appreciate getting knocked into that it is an anti-pattern and need to change.

I do constructor chaining.

class ProductService{
    public ProductService():this(new SqlProductRepository()){}
    public ProductService(IProductRepository repository){...}
}

This is their example (or close enough) of the anti-pattern CONTROL FREAK. Which... ... That's what I DO!? It gave me a little bit of a crisis. My way out of this crisis is where the SqlRepository concrete type lives - In a separate library.

ProductService is in LibraryA and SqlRepository is in LibraryB.
IRepository is in LibraryA.
We're cyclic. Never go cyclic.

The solution to this is move the dependency on LibraryB to wherever the DI code is let that be dependent on BOTH LibraryA and LibraryB.

In this scenario; we're controlling the code of both LibA and LibB. While I think some of this different library separation/abstraction is useful; it creates situations where DI is the only way to solve the cyclic dependency. This ... is kinda a problem of your own making. We control the code; we can remove the dependency.
The product has all those dependencies... so we won't be affecting that at all. It's just affecting exactly how portable sub-units of are code are.

They aren't.

Code re-use is not consuming the library in another application. Re-usable libraries require different considerations and result in different APIs. We can't just lift and shift.
DI doesn't really change that.

I know a lot of this comes from what architecture is preferred. Dependencies should flow up to the most stable component(s). In a typical 3 tier application; that's the domain layer. It's why IRepository is in LibaryA.
Theory - Good. Practice - Not always.

I'll get to that disagreement in a moment. I want to get to why my use of constructor chaining isn't CONTROL FREAK.

As mentioned, SqlRepository is in LibB; this makes it "Foreign" class to anything in LibraryA. If we use the constructor chaining for something in LibA; that's a "Local" class.

class LocalDefaultExample{
    public LocalDefaultExample():this(new SampleLocalDefault()){}
    public LocalDefaultExample(ILocalDefault localDefault){...}
}

This is fine. The classes are "local" to each other. It's the default.

I'm going to ignore that they recommend a setter to inject a different dependency if we need something different...

When we have a "Foreign Default", that's the CONTROL FREAK anti-pattern.

phew Safe.

Foreign Defaults are transitive, so I can never have a Local Default if ANY dependency in the chain has a Foreign Default. Sure.

I'm not sure Foreign Defaults are bad. It depends on how widespread they are. If they're widespread in your system, you've done it wrong.

Foreign code needs to be isolated; they are 3rd part code. The cyclic dependency is a different issue. I resolve that by not having LibraryB depend on LibraryA.

I create an abstraction for the code LibraryA uses from LibraryB.

public class ProductRepository : IProductRepository{
    public LibraryBSqlRepository():this(new LibraryB.SqlProductRespository()){}
    public LibraryBSqlRepository():this(LibaryB.IDoesNotMatter repository){_repo = repository;}
}

This will be the ONLY place in the code that uses LibraryB.SqlProductRepository. If I want to change what LibraryA does for a product repository - Right here. From this one class the ENTIRE system is changed.

The fact it's SQL is abstracted from the rest of the system; as it should be. DI does give us that; so does this.

My ProductService will now look like

class ProductService{
    public ProductService():this(new ***ProductRepository***()){}
    public ProductService(IProductRepository repository){...}
}

ProductService has a default ProductRepository. OK... Why do I need DI to do that for me?

If you want to take the library from the web and use it in a UWP app... I don't agree that's a good idea.

I'll brain storm on it....
OK.
IFF you decide you'll re-use the non-reusable library because we're oh-so-good at separation of concerns... I favor registration.

LibraryA.ProductRepository(new LibraryUwp.NoSqlProductRepository());

I'm sure there's a pattern (Oh I hope not an anti-pattern) name for this.

Our ProductRespository gets to change a little. We're now an INDEPENDENT library configured to be used in multiple contexts.

public class ProductRepository : IProductRepository{
    private static IProductRepository _staticRepo;
    public static void Register(IProductRepository repo){
        //Do some locking for safety if desired
        if(repo != null) throw RepoAlreadySetException();
        _staticRepo = repo;
    }
}

And now we're re-usable for any consumer.
WPF? Done.
Console? Done.

Sure; it's got some smell; but we're asking the lowest level of our library to be configured by the highest level of our app, the entry point. There's gonna be SOME smell. I find this less bad than DI Frameworks.

Reusable and no DI Framework.

DI smells to me. I'll prefer a similar smell for less cryptic tooling.

Let's simplify.

We have a ProductRespository and a CustomerRepository. These probably have similar functionalities.
Let's create a common interface for those
IRepository<T>. ProductRepository : IRepository<Product> and CustomerRepository<Customer>.
OK.
Now... I have no idea how to do this in any DI Framework. I've DONE it... it was 10-15 lines of cryptic crap that didn't make sense. A copy and paste from their documentation.

It was far worse for the code than ProductRepository.Register(new Blah()).

Making the code simpler and refactoring to more common interfaces drives UP the complexity of using DI Frameworks. Making the code better is HARDER with DI Frameworks.

That has been my experience. I want to find a situation that it doesn't so I can understand when to use DI Frameworks effectively.

Show Comments