Architecture: dependency injection, build assemblies, implementation hiding

I am working on a personal project that, in addition to doing something useful for myself, I tried to use as a way to continue searching and learning architectural lessons. One such lesson appeared as a Kodiak bear in the middle of a bike lane and I struggled with it very hard.

The problem is essentially an amalgam of problems at the intersection of dependency injection, build failure, and implementation hiding (i.e. implementing my public interfaces using inner classes).

In my work, I usually find that different layers of the application store their own interfaces, which they publicly expose, but internally implement. Each DI code in an assembly registers an inner class with a public interface. This method prevents assembly assemblies from appearing by an instance of the implementation class. However, some of the books I read while creating this solution have spoken out against it. The main things that conflict with my previous thinking have to do with the root of the DI composition and where to maintain interfaces for a given implementation. If I move the dependency registration to one, the global root composition (as Mark Semann points out), I can walk away from each assembly so that it does its own dependency registrations. However, the disadvantage is thatthat implementation classes should be public (allowing any assembly to create them). In terms of decoupling assemblies, Martin Fowler instructs to put interfaces in a project with code that uses the interface, not the one that implements it. An example is the diagram he provided and, for comparison, a diagram for how I usually implement the same solution (ok, it's not exactly the same, kindly focus on the arrows and note the borders instead of composition shooter).it's not exactly the same, kindly focus on the arrows and note the borders instead of the composition arrows).it's not quite the same, kindly focus on the arrows and note the borders instead of the composition arrows).

Martin Style

Martin style

What i used to see

Not martin style

I immediately saw the advantage in Martin's diagram that it allows the lower assemblies to be replaced with others, given that it has a class that implements the interface in the layer above it. However, I've also seen this seemingly major flaw: if you want to change the build location at the top level, you essentially "steal" the interface that the bottom level implements.

After thinking about it a bit, I decided that the best way to be completely decoupled in both directions is to have interfaces that define the contract between layers in their own assemblies. Consider this updated diagram:

Proxy style

Is it nutty? Right? For me, this seems to solve the interface segregation issue. However, it does not solve the problem of not being able to hide an implementation class as internal. Is there something sane that can be done there? Should I not worry about this?

One solution I'm playing with in my head is for each layer to implement the proxy layer interface twice; once with the open class and once with the inner class. So the public class can just wrap / decorate the inner class like:

Proxy and decorator style!

Some code might look like this:

namespace MechanismProxy // Simulates Mechanism Proxy Assembly
{
    public interface IMechanism
    {
        void DoStuff();
    }
}

namespace MechanismImpl // Simulates Mechanism Assembly
{
    using MechanismProxy;

    // This class would be registered to IMechanism in the DI container
    public class Mechanism : IMechanism
    {
        private readonly IMechanism _internalMechanism = new InternalMechanism();

        public void DoStuff()
        {
            _internalMechanism.DoStuff();
        }
    }

    internal class InternalMechanism : IMechanism
    {
        public void DoStuff()
        {
            // Do whatever
        }
    }
}

      

... of course, I still have to solve some problems related to installing the constructor and passing the dependencies injected into the public class into the inner one. There's also a problem that external assemblies can possibly create a new public transport mechanism ... I need a way to ensure that only the DI container can do this ... I suppose if I could figure it out I wouldn't even need an internal version ... Anyway, if anyone can help me understand how to overcome these architectural problems, it would be much appreciated.

+3


source to share


3 answers


However, the downside is that the implementation classes must be public (allowing any assembly to instantiate them).

Unless you are creating a reusable library (which is published to NuGet and used by other codebases that you have no control over), there is usually no reason to make the classes internal. Moreover, since you are programming for interfaces, the only place in the application that depends on these classes is the Composition Root.

Also note that if you move abstractions to a different library and both the consuming and embedding assemblies depend on that assembly, those assemblies should not depend on each other. This means that it doesn't matter at all whether these classes are public or internal.

This level of separation (placing interfaces in a native assembly) is unlikely to ever be needed. At the end of the day, it is all about the granularity required during deployment and the size of the application.



In terms of decoupling assemblies, Martin Fowler instructs to put interfaces in a project with code that uses the interface, not the one that implements it.

This is the Dependency Inversion Principle , which states:

With direct application of dependency inversion, theses belong to the upper / political layers

+3


source


This is a somewhat opinion-based topic, but since you asked, I'll give mine.

Your emphasis on making as many assemblies as possible is as flexible as possible, very theoretical, and you have to weigh the practical value versus cost. Don't forget that assemblies are just a container for compiled code. They only become mostly relevant when you look at development, build and deployment / delivery processes. Therefore, you have to ask a lot more questions before you can make the right decision about how exactly to split the code into an assembly.

So, here are some examples of questions I would ask beforehand:

Does it make sense in your domain application to split assemblies this way (for example, you really need to swap assemblies)?

Do you have separate teams to develop them?

What will the scope be in terms of size (both LOC and team size)?

Do you want to protect the implementation from accessibility / visibility? For example. are these external interfaces or internal?

Do you really need to rely on assemblies as a mechanism to enforce your architectural separation? Or are there other more effective measures (like code reviews, code reviews, etc.)?

Will your calls really only be done between builds or will you need remote calls at some point?

Do you need to use private assemblies?

Would sealed classes help your architecture instead?

For a very general view, leaving these additional factors aside, I would agree with Martin Fowler's diagram because it is just a standard way of providing / using interfaces. If your answers to the questions indicate additional value, further code separation / protection, which might be fine. But you have to tell us more about your app domain and you will need to justify it well.



Thus, you are faced with two old wisdoms:

  • The architecture tends to follow organizational settings.
  • It is very difficult to redefine architectures (overly complex), but very difficult to simplify them as much as possible. Simple is better in most cases.

When you come up with architecture, you want to consider these factors ahead of time, otherwise they will come and haunt you later in the form of technical debt.

+2


source


However, the downside is that the implementation classes must be public (allowing any assembly to instantiate them).

This doesn't seem like a flaw. Implementation classes that relate to abstractions at the root of composition can probably be used explicitly elsewhere for a variety of other reasons. I don't see any benefit from hiding them.

I need a way to ensure that only the DI container can do this ...

No no.

Your confusion is probably due to the fact that you think of AI and Root Composition as if there is a container, of course.

In fact, however, the infrastructure can be completely "container agnostic" in the sense that you still have your dependencies, but you don't think about "how." The composition root that the container uses is your choice, as good a choice as possible, another composition root in which you manually compose the dependencies. In other words, the root composition may be the only place in your code that knows about the DI container, if used. Your code has the idea of ​​dependency inversion built in, not the idea of ​​the dependency inversion container.

A short tutorial in my opinion can shed some light here

http://www.wiktorzychla.com/2016/01/di-factories-and-composition-root.html

+2


source







All Articles