Injection / Inversion of Control

Why ?

Hi-level Objects often need to relate each others.

When building a self contained flow, this is not an issue. But while building flow libraries, you face a situation where you have to deliver Objects for every related high level concepts so that they can interact thru their relations.

The situation is also bad for the library users who will need to adopt all the concepts of the library because they can’t change the relation between them.

As an example, let’s consider you want to build a Task system:

You use a flow.Map to manage a list of Task Objects. A Task has start_date and duration, as well as a assigned_user which is a Connection to a User. At this point you need to define what is a User and the interface it needs to interact with your Task. So you had a Team map containing User items… You Task system now contains Tasks and Users and in order to use it in a pipeline flow one needs to adopt your Task system as well as your User system. This sucks !!!

On this example, an Inversion of Control is to give the pipeline flow the control over the relation used by the Task lib flow.

A common way to achieve this is to inject the dependencies inside the Task flow lib from the pipeline flow instead of letting the Task flow lib define them.

Why is it a problem ?

A flow Object dependencies are defined by the Child and Map contained in all its children. (Param are Child, but Parent and Relative are special and dont apply here).

More precisely, they are defined by the type of Objects each Relation relate to and the type of item mapped to each Map.

The classic way to change those (in order to change the dependencies) is by inheritance: you inherit the relation owner and override the relation by defining a new one with the same name.

The probleme is that you now need to use this new class instead of the one containing the Relation you altered. So you also need to inherit its parent’s class. And this goes up to your Project class !

This sucks !!!

But keep your pants on and read on, we have a solution for this situation: Dependency Injection

Also, it’s worth specifying that dependency injection also makes writing test code extremely easier as it lets you replace/mock-up part of the system to isolate the component you want to test.

How ?

So we want to “override” the related type of some Relation, and/or the mapped type of some Maps.

We want to do it with different types on different flow (be them pipeline flows or lib flows) because we might use the same lib in different contexts where our dependencies differ.

As alwawy, we want to do from within the flow itsef so that everything is well tight together.

Also, as a lib flow developer, we want to specify which types can be injected (“overidden”) in my lib.

The first step is specifying that a Relation supports injection. This is done by calling injectable() on it.

In this example, the my_studio.flow.lib.foo lib flow defines a Foo Object that contains a FooChild Object. Let’s say you want the lib users to be able to inject their Object intead of using your ‘FooChild’:

# my_studio/flow/lib/foo.py`

class Foo(flow.Object):
    my_child = flow.Child(FooChild).injectable()

The second step is to use the foo lib and inject a new type instead of FooChild.

This is done by defining an _injection_provider() classmethod in any parent of the Foo Object. You can conveniently inherit kabaret.flow.InjectionProvider to do so, but it is not mandatory.

In this example, we inject a FooChild subclass that extends it with the is_awesome Param.

from my_studio.flow.lib.foo import Foo, FooChild

class MySuperFooChild(FooChild):
    is_super = flow.BoolParam(True)

class Project(flow.Object, flow.InjectionProvider):

    foo = flow.Child(Foo)

    def _injection_provider(self, slot_name, default_type):
        if default_type is FooChild:
            return MySuperFooChild

With this set up, your Project flow is using the foo lib with your very own FooChild.

This rocks \o/ !!!

More example

There are comprehensive examples in dev_studio.flow.unittest_project.showcase.injection.

Create a project with the type dev_studio.flow.unittest_project.UnittestProject to see the result, and read the code to learn more !