6 min read

Clean code 4: Dependency injection in practice: Handling commands

This article aims to give you some foundational tools towards mastering CQRS and Hexagonal architectural pattern.

Instead of going through a thorough overview of those patterns explaining the whys, I'll start by giving you pieces of the hows.
A whole views of those patterns will --in my opinion– make more much sense with a solid grasp on the basic bricks it's composed of.

Without further ado, let's get into it.

Useful concepts

DTO

A DTO (for Database Transfer Object) is a short lived object meant
to contain data and carry it (Initially to a Database, but it's common
to refer to objects that fits this definition as DTO eventhough no DB is
involved).

Note: DTO Should be immutables. For the sake of only focusing on our
topic, we'll keep that aspect for a future part.

Command and Query

A command is simply a DTO, that will express an intention and contain data
needed to perform the action intended. An action is whenever a mutation occurs

A query is pretty much the same thing, except its goal is to access data rather than inducing a mutation

Handler

A handler is the function associated with a command or a query (also events, that's a pretty generic term. Basically, a handler is a bunch of code meant to handle stuff)

Repository

A repository is a class that implements an interface and take care of interactions
with the persistence layer of your application.

Where to our project

Oh, I didn't tell you. From this article on, we're getting real ! We're boostraping a real world, actual project.

Where to start: Use cases

When you build a traditional CRUD application, it's common to start
by modeling your database schema.

Either with an ERM (Entity relationship models) modeling tools (as MySQL workbench)
or doing it as code by writing entities using your favorite ORM.

While nothing forbids that in the world of CQRS, Use Cases are
a much better starting point.

This is not a subject of itself. It's a whole bunch of subjects. DDD, BDD, Cucumber...

The goal of this part is to give you an understanding of some of CQRS/Hexagonal architecture base concepts. What this is definitely not: a thorough guide on all the good processes and practices to do that yourself in prod

(But we'll come to that, that's exactly our intended destination)

So rather than

Customer
column type
id int
email varchar(100)
address ...

(And so on...)

What your first step will look like should more be along the lines of:

As a customer I want to add articles to my basket

(Which could then be declined into several scenarios, on which base you
may write or generate tests. But hey, we're not aiming for prod ready
processes here. Only the basics of CQRS. So (B|D)DD will have to wait
a later part)

Here come the C and and the Q of CQRS.

Uses cases will reflect as interactions with our system

Those interactions will fall in one of two categories:

  • Getting stuff (Query)
  • Doing stuff (Command)

Let's get real

Use cases

That's it guys we're actually coding our application !

Here are the use cases we'll want to implement in our first iteration:

  • As a non-user I want to be able to register an account

  • As an user I want to create TodoLists

  • As an user with a TodoList, I want to add it tasks

  • TodoLists should have:

    • A title
    • A description
  • Tasks should have:

    • A title
    • A description
    • A date of creation
    • A date at which the task should be done
    • A data at which MUST be done (which we'll call deadline)

We'll stick to that for now :)

(Actual scenarios, Cucumber. Those are two items that would
make our process real world ready. We'll tackle that in the future.
Just keep in mind world readiness is not what we're aiming yet)

Architecture

Requirements and project structure

Create a file named requirements.txt with this content

rich
pytest
toolz
flask
flask-login
SQLAlchemy
Flask-SQLAlchemy
psycopg2
werkzeug

Then create the project structure

python3.8 -m pip install -r requirements.txt

mkdir -p faire/{aggregate,command,query,repository,cqrs,middleware,tests,utils}
touch faire/{aggregate,command,query,repository,cqrs,middleware,tests,utils}/__init__.py
touch faire/{aggregate,command,query,repository}/user.py
touch faire/aggregate/aggregate.py
touch faire/command/command.py

Quick side note: Aggregates are, for now, simple entities. We'll see
in future parts how they're not. What conceptually tell them appart from
regular entities. For this part, just assume we could have called them
Entities

Dependency injector

Dependency Injection is something you need to use and understand thoroughly.

Especially in large applications, it's not always totally simple to use.

here are two hurdles that come with it:

Verbosity

If you need to instantiate an object, that depends on an other, that depends on
an other...

my_foo = Foo(BarImplementation(BazImplementation(BlaImplementation())))

It gets confusing

Single instance

It may happen that you need to use an object on two distinct parts of
your code. And you want to use the SAME object.

It's not that hard to achieve, but many ways to do so will be painful
and prone to bug.

There is a pattern for that, it's called Dependency Injector or
Dependency Container


Create the file faire/utils/dependency_injector.py with this code:


from typing import Any

class DependencyInjector:
    """
    Dead Simple dependency injector

    Assumption: Only one type and instance per interface
    """

    def register_instance(self, instance: Any, *aliases) -> None:
        pass

    def get(self, cls):
        pass

Here's what we want it to do:

Given FooImpl implementing FooInterface,

Given BlaRegistery has FooInterface as a dependecy (and signal that fact by type hint)

assuming foo is an instance of FooImpl an DI of DependencyInjector

  • If DI.register_instance(foo_impl, 'foo_bar', 'bar_foo')
    • DI.get(FooInterface) is DI.get(FooImpl) is DI.get('bar_foo') is DI.get('foo_bar')

In the same scope

  • If DI.get(BlaRegistry) on its first call will return a new instance of
    BlaRegistry with the correct dependency injected

We have scenarios !
We can write tests

create faire/tests/dependency_injector_test.py with this

from faire.utils.dependency_injector import DependencyInjector


def test_DependencyInjector():
    class FooInterface:
        __interface__ = True

    class FooImpl(FooInterface):
        pass

    class BlaRegistry:
        def __init__(self, foo: FooInterface):
            self.foo = foo

    DI = DependencyInjector()
    DI.register_instance(FooImpl(), "foo_bar", "bar_foo")

    assert (
        DI.get(FooInterface)
        is DI.get(FooImpl)
        is DI.get("foo_bar")
        is DI.get("bar_foo")
        is DI.get(BlaRegistry).foo
        is not FooImpl()
    )

A last thing

DependencyInjector implements the Singleton pattern.
DepencyInjector.instance returns always the same instance, which is created if needed

Which translate in test as: (append this to the same file)

def test_DependencyInjectorSingleton():
    assert DependencyInjector.instance is DependencyInjector.instance

run in terminal

python3.8 -m pytest faire/tests/dependency_injector_test.py

Which should fail. If it doesn't fail, that's in itself a fail
(Wait, would it means it's therefore a success ?)

We'll first take care of the Singleton mechanism

create the file faire/utils/singleton.py with this

from typing import TypeVar

T = TypeVar("T")


class Singleton(type):
    """
    Dead simple singleton helper
    :use:
    ```python
    class Bla(metaclass=Singleton):
        def __init__(self): # <= Assumes __init__ awaits no parameter
            ...

    Bla.instance <= unique instance of Bla
    """

    @property
    def instance(cls: T) -> T:
        # TODO type hint with generic
        cls._instance = getattr(cls, "_instance", cls())

        return cls._instance

then, replace the content of faire/utils/dependency_injector.py with

from itertools import cycle
from typing import Any

from toolz import valmap

from faire.utils.singleton import Singleton


class DependencyInjector(metaclass=Singleton):
    """
    Dead Simple dependency injector

    Assumption: Only one type and instance per interface
    """

    def __init__(self):
        self._instances = {}

    def register_instance(self, instance: Any, *aliases) -> None:
        """
        :Note to myself: Perhaps too much magic. TODO: Test limit cases
        """
        for base in instance.__class__.__bases__:
            if getattr(base, "__interface__", False):
                aliases = [*aliases, base]

        aliases = [*aliases, instance.__class__]

        self._instances.update(dict(zip(aliases, cycle([instance]))))

    def _create_instance(self, cls):
        # TODO Type hint with Generics
        self.register_instance(
            cls(
                **valmap(
                    lambda type_: self._instances[type_],
                    cls.__init__.__annotations__,
                )
            )
        )

    def get(self, cls):
        if not cls in self._instances:
            self._create_instance(cls)

        return self._instances[cls]

Again run

python3.8 -m pytest faire/tests/dependency_injector_test.py

Which should work

Important note

What I'm aiming at, writing this series of articles, is talk to folks in the shoes I was when I first started code.

Though, there is an hidden purpose behind this dependency injector. Which
is to talk to the version of myself of not so long ago and yell as this idiot "Dude, that's too much magic".

See, magic in code may be great, but must be handled carefully.

For now this is a mere not real world ready note of caution.
At this point, and for all intents and purposes of that article,
DependencyInjector behaviour is just fine.

A future part will address how too much magic auto-wiring may happen
to be disastrous.

EndPoint, Flask, SQLAlchemy

Our application will present an HTTP API, and persistance will be done thanks
to SQLAlchemy + PostgreSQL.