Rolling your own Dependency Injection

What if you want to employ the Dependency Injection pattern in your application, but don’t want to use a third party framework for it?

By way of exploring this question, I’ll tell you an anecdote. I am currently developing a new FOSS project. I won’t tell you what it is. At this early stage, it’s a secret. Given that the project uses a lot of service oriented interfaces, I thought using Dependency Injection throughout would reduce over-all complexity. I didn’t want to use the Delphi Spring Framework or something similar – I won’t go into the reasons. I ended up rolling my own DI code.

Most of rolling your own DI is pretty much straight forward. Use an Abstract Factory for your service container and Attributes & RTTI to identify injection targets.
Then we come to the bug-bear: How to inject an instance into the interface data members of a object being constructed by the DI service?

The problem defined.

What we have:

  • We have an object, whose type is unknown at compile-time. This is the object which is being constructed with the aid of dependency injection. We’ll call it the Product.
  • One of the interface data members of the Product needs to be set. This is dependency injection. We’ll call it the Dependency Member. The dependency member is unknown at compile-time. At run-time we can identify the Dependency member with attributes and so we have an instance of type TRttiField describing the dependency member.
  • The dependency member is an interface pointer of some type. We don’t know what type it is at run-time, but from RTTI, we can get the GUID of the interface type that the dependency member will implement.
  • We have the Injection. This is an interface to an object (read “service”) of the same interface type as the dependency member.

What we need:

  • We need to set the Dependency member to the Injection.

Sean’s Solution:

One might expect that there would be some simple RTTI function to set the dependency member, like so..

procedure Inject( var Rtti: TRttiContext;
                  Product: TObject; DepMember: TRttiField;
                  const ServiceGUID: TGUID;
	          const Injection: IInterface;
		  {Injection is hard-cast from the unknown service interface type.});
begin
DepMember.SetInterfaceValue( Product, ServiceGUID, Injection)
end;

However, I don’t believe there is. Here is what I came up with:

procedure Inject( var Rtti: TRttiContext;
                  Product: TObject; DepMember: TRttiField;
                  const ServiceGUID: TGUID;
	          const Injection: IInterface;
		  {Injection is hard-cast from the unknown service interface type.});
var
  InjectionAsRTTIValue: TValue;
begin
Assert( DepMember.FieldType.TypeKind = tkInterface);
Assert( DepMember.FieldType is TRttiInterfaceType);
Assert( IsEqualGUID( ServiceGUID, TRttiInterfaceType( DepMember.FieldType).GUID));
Assert( Supports( Injection, ServiceGUID));
TValue.Make( @Injection, DepMember.FieldType.Handle, InjectionAsRTTIValue);
DepMember.SetValue( Product, InjectionAsRTTIValue)
end;
Posted in Coding Techniques, RTL
3 comments on “Rolling your own Dependency Injection
  1. warmonkey75 says:

    First off, great post – good food for thought.

    Just a quick question though, why do you have a TRttiContext parameter on the Inject procedure?

    Would be great to see a worked example of how you use the Inject method.

  2. Sean says:

    About the TRttiContext parameter. You need to keep at least one of these records alive, even if you dont directly use it, otherwise all your other Rtti objects cease to be valid. Refer to Barry Kelly’s blog entry (http://bit.ly/tUqueJ) for details.

    I’ll post a worked example shortly – a simple bare-bones DI framework.

  3. Arioch says:

    > I didnโ€™t want to use the Delphi Spring Framework or something similar โ€“ I wonโ€™t go into the reasons.

    Pity Thart could be the most insightful and practical part of the article ๐Ÿ™‚