Dependency Injection the mORMot way

Dependency Injection and Inversion of Control for part of the SOLID principles. That’s a lot of gobbledygook, but trust me, it’s a Good Thing™ It will help in keeping things separate and prevent the urge to create cross dependencies.

I haven’t seen any implementation of these patterns that doesn’t make use of interfaces. Interfaces are great as they enforce a contract between provider (whatever implements the interface) and consumer (the thing that uses the interface).

Real world example time:

Let’s say we have a service we want to be able to talk to without knowing the particulars of it’s implementation. For this example we’ll use an application settings provider. Application settings can be stored in many different formats, eg INI files, JSON files, Registry or even in a database. When reading these settings, we really shouldn’t care where these settings are stored.

The contract

The first step would be to declare the IApplicationSettings interface:

type
  IApplicationSettings = interface(IInvokable)
    ['{E1E76D15-B43A-4DF5-91CE-9CAC7AAAF789}']
    function getValue( aSetting : string; aDefault : string ) : string;
    function getValueInt( aSetting : string; aDefault : integer ) : integer;
  end;

This is just a basic example, but good enough to demonstrate the principles.
NB: Your interface needs to descend from IInvokable, or it won’t contain any RTTI. It also needs to be registered with mORMot. It makes sense to register the interface in the unit’s initialization section:

initialization
  TInterfaceFactory.RegisterInterfaces( [TypeInfo(IApplicationSettings)] );
end.

The service provider

A basic class that can provide what the contract demands.

Implementation of our interface:

type
  TINISettings = class( TInterfacedObject, IApplicationSettings )
  private
    fINIFile : TINIFile;
    fSection : string;
  public
    constructor Create( aSettigsFileName : TFileName; aSection : string );
    destructor Destroy; override;
    //Implement the interface
    function getValue( aSetting : string; aDefault : string ) : string;
    function getValueInt( aSetting : string; aDefault : integer ) : integer;
  end;
implementation

{ TINISettings }

constructor TINISettings.Create(aSettigsFileName: TFileName; aSection : string);
begin
  inherited Create;
  fSection := aSection;
  fINIFile := TIniFile.Create( aSettigsFileName );
end;

destructor TINISettings.Destroy;
begin
  fINIFile.Free;
  inherited;
end;

function TINISettings.getValue(aSetting, aDefault: string): string;
begin
  result := fINIFile.ReadString( fSection, aSetting, aDefault );
end;

function TINISettings.getValueInt(aSetting: string; aDefault: integer): integer;
begin
  result := fINIFile.ReadInteger( fSection, aSetting, aDefault );
end;

Simple and easy.

The magic

First we declare our class.

type
 TMyApplication = class(TInjectableObject)
 private
   FSettings: IApplicationSettings;
 public
   procedure PrintSomeSettings;
 published
   property Settings: IApplicationSettings read FSettings write FSettings;
 end;

The magic happens in as we descend from TInjectableObject. Any published interfaces will automagically be provided to our class.

This means that the following code will be executed without error and provide the expected output.

procedure TMyApplication.PrintSomeSettings;
begin
  writeln( 'Value of TestString: ', Settings.getValue('TestString', 'Not found' ) );
end;

All that’s left is to create our class, and call our test method

procedure TestIt;
var
  MyApplication : TMyApplication;
begin
  MyApplication := TMyApplication.CreateInjected( [],[], [TINISettings.Create( ChangeFileExt(ParamStr(0), '.INI'), 'Test' )]);
  MyApplication.PrintSomeSettings;
end;

The first two empty arrays are for stubs and other resolvers respectively, none of which we need in this example.

Why bother

As Stefan Glienke pointed out, in a previous version of this post, we were implementing the Service Locator pattern, which is considered an anti-pattern by many. It’s very close to Dependency Injection, but tends to hide the class’s dependencies. It also breaks the “Tell, Don’t Ask” principle.

Advertisements

6 thoughts on “Dependency Injection the mORMot way

  1. You should simply use a TInjectableObject, put the IApplicationSettings as published property, and specify the resolver when you create the TInjectableObject instance.

    Do not use global variables to store the resolver: it breaks most of DI/IoC benefits.
    If you need global injection registration, use TInterfaceResolverInjected.RegisterGlobal() which is a thread-safe global container of DI for the whole system.

    Liked by 1 person

    1. True, but they are so similar. I was trying to reduce the complexity of the example – perhaps I’ve gone too far. I’ll reword the article a bit and start on a bit more complex example.
      You’re right, and an easy mistake to make if you’re not careful. I should maybe write something on that.

      Like

  2. Of course, injecting at constructor level is not perfect. DI/IoC does make sense when used with a Resolver, e.g. the TSQLRest.Services resolver.

    Also note that the framework is also able to create stubs and mocks of the interfaces, at creation, if needed.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s