Skip to content

Default Rules For Resolving Unit and Its Dependencies

Ed Pavlov edited this page Jun 8, 2024 · 1 revision

"Default" rules: Your Blueprint for Dependency Injection

Armature embraces a philosophy of flexibility and developer empowerment. Armature doesn't dictate a default configuration, instead it does offer a set of "default-like" registrations as a reference implementation. These registrations encapsulate common dependency injection patterns and can serve as a helpful starting point for your own builder. They demonstrate how to handle scenarios like constructor injection, argument resolution, and build stage management, providing a practical example of how to leverage Armature's capabilities. It provides you, the developer, with the tools to craft your own dependency injection strategy from the ground up.

Building Your Own Builder: A Blank Canvas

At its core, Armature's Builder is a blank canvas, a framework upon which you can paint your own dependency injection masterpiece. You have complete control over how your objects are created, injected, and configured. This freedom allows you to tailor the injection process to the unique needs and architecture of your application.

Let's look at this example of creating a Builder

private static Builder CreateBuilder()
    => new(BuildStage.Cache, BuildStage.Aware, BuildStage.Intercept, BuildStage.Initialize, BuildStage.Create)
        {
            // Inject into constructor
            new IfFirstUnit(new IsConstructor())
               .UseBuildAction(
                new TryInOrder                               // This build action calls nested build actions till a Unit be built
                {
                  new GetConstructorByInjectPoint(),         // Constructor marked with [Inject] attribute has more priority
                  new GetConstructorWithMaxParametersCount() // Constructor with the largest number of parameters has less priority
                },
                BuildStage.Create),

            new IfFirstUnit(new IsParameterInfoArray())
               .UseBuildAction(new BuildMethodArgumentsInDirectOrder(), BuildStage.Create),

            new IfFirstUnit(new IsParameterInfo())
               .UseBuildAction(
                new TryInOrder
                {
                  Static.Of<BuildArgumentByParameterInjectPoint>(),
                  Static.Of<BuildArgumentByParameterType>()
                },
                BuildStage.Create)
        };

Stages

new Builder(BuildStage.Cache, BuildStage.Aware, BuildStage.Intercept, BuildStage.Initialize, BuildStage.Create) Choose which stages of building units you need in your project.

  • Don't use injection into properties and methods? - Maybe you don't need the Initialize stage.
  • Don't use Armature to bind event sources and subscribers? - Maybe you don't need the Intercept stage.
  • Or maybe you need your own stage for you project specific actions?

Choosing a constructor

new IfFirstUnit(new IsConstructor())
   .UseBuildAction(
      new TryInOrder                               // This build action calls nested build actions till a Unit be built
      {
        new GetConstructorByInjectPoint(),         // Constructor marked with [Inject] attribute has more priority
        new GetConstructorWithMaxParametersCount() // Constructor with the largest number of parameters has less priority
      }

Whenever a Unit "constructor" is asked to be resolved, if there are no other rules with a bigger weight, this rule will be applied. You are free to choose your "default" strategy of choosing a constructor to create an object, this is just an example.

Parameters array

new IfFirstUnit(new IsParameterInfoArray())
   .UseBuildAction(new BuildMethodArgumentsInDirectOrder(), BuildStage.Create);

To obtain arguments for a constructor or a methods, Armature resolves the Unit with id ParameterInfoArray, and you can choose in which order those parameters should be resolved, in direct, revers, or maybe your own based on parameter types. It can be important when injecting objects controlling other objects lifetime, or loggers.

Parameter's argument

new IfFirstUnit(new IsParameterInfo())
   .UseBuildAction(
    new TryInOrder
    {
      Static.Of<BuildArgumentByParameterInjectPoint>(), // Build a Unit with UnitId(ParameterInfo.ParameterType, InjectAttribute.Tag)
      Static.Of<BuildArgumentByParameterType>()         // Build a Unit with UnitId(ParameterInfo.ParameterType, null)
    },
    BuildStage.Create)

When it comes to resolving a argument for a particular parameter, this rule first looks whether a parameter is marked with InjectAttribute. Then building a Unit with corresponding UnitId. Again, you can use whatever BuildAction works best for your project.

Key Takeaways

Armature doesn't impose a default behaviour, giving you complete control over your dependency injection strategy.

  • The provided "default-like" registrations serve as a reference implementation and a helpful starting point.
  • You can choose registrations to create a builder that perfectly suits your needs.
  • Armature's flexibility empowers you to create a dependency injection system that is truly your own.

By understanding and embracing Armature's philosophy of customization, you can unlock the full potential of dependency injection and build applications that are both maintainable and adaptable.