DI・DIコンテナをflaskで使う

## はじめに
DIをflaskで使う方法を記事にしました。
本記事ではflask-injectorを使用します。
併せて、DI・DIコンテナについても説明していきます。

## DIとは？
まずは前提知識となるDIについて説明をしていきます。
*Dependency Injection*は依存性の注入と訳されますが、
依存性の注入では何を表現しているのか？が掴みづらいと思います。

この依存性を言い換えるなら、**依存関係にある要素・オブジェクト**です。

「依存関係にあるオブジェクトを注入する」ことがDIパターンとなります。

## DIパターンを使用することの利点

依存関係にあるオブジェクトを外部から注入することにより、
依存元のモジュールから依存しているオブジェクトを外部に出すことができます。
つまり、モジュール結合度を弱くすることができます。

### DIパターンを使用していない場合

モジュール結合度を弱くすることで保守性の強いソースコードを作ることができます。
例えば、下記のようなコードがあるとします。

```python:test.py
from dataclasses import dataclass

@dataclass
class Cat:
  name: str
 
class Pet:

  def call_name(self) -> None:
    cat_cls = Cat("hoge")
    print(cat_cls.name)
  
if __name__ == "__main__":
  pet = Pet()
  pet.call_name()

```

Petクラスの内部でCatクラスのインスタンスを生成しています。
PetクラスはCatクラスに強く依存している状態です。

依存状態が強いと、何が問題なのか？
上記を説明するためにCatクラスのインスタンス変数に変更を加えます。

```python
@dataclass
class Cat:
  name: str
  age: int  
```
変更点として、Catクラスのインスタンス変数にageという変数を追加しました。
変更はCatクラスに行ったものですが、
PetクラスでCatクラスのインスタンスを生成しています。

Petクラスでも修正が必要になるのです。

```python
  def call_name(self) -> None:
    cat_cls = Cat("hoge") # <-- この箇所
    print(cat_cls.name)
```

下記ではモジュールが少ないのでイメージが付きづらいと思います。
極論にはなりますが、100個のモジュールがCatクラスに依存しているとします。
その場合、**依存している100個のモジュール全てを修正していく必要があります**。

変更したかったのはCatクラスのインスタンス変数であるにも関わらずです。

このようにモジュール結合度が高いと、変更に弱いソースコードになってしまいます。

### DIパターンを使用する場合

ここでDIパターンを使用していきます。
CatクラスをPetクラスの中で呼び出すのではなく、
外部から注入します。

```python:test.py
from dataclasses import dataclass

@dataclass
class Cat:
  name: str
  age: int
 
class Pet:
  def __init__(self, cat: Cat):
    self.cat = cat

  def call_name(self) -> None:
    print(self.cat.name)
  
if __name__ == "__main__":
  cat = Cat("taro", 3)
  pet = Pet(cat)
  pet.call_name()

```

こうすることてCatクラスに変更があった場合でも
修正箇所を特定しやすくできます。

※依存関係においてはSOLID原則の依存性逆転の原則などもあります。
　取り扱っている事柄などが似通っていますが、
　DIパターンと依存性逆転の原則は別のものです。

## DIコンテナについて
DIコンテナはDIを使用する上では必須ではありません。
複数のインスタンスを渡すときに呼び出しが煩雑になってきます。
インスタンスを管理する場所が欲しくなります。

そこでDIコンテナを使用しています。
DIコンテナの使用用途は煩雑になるインスタンスの管理です。


### flaskでDIを使用する

ここからが本題です。
今回はFlaskにInjectorを追加するため、Flask-Injectorを使用していきます。

Flask-Injector使用時にはFlaskのバージョンも確認しておく必要があります。

>PyPiのFlask-Injectorから引用
>>Flask-Injector is compatible with CPython 3.7+. As of version 0.15.0 it requires Injector version 0.20.0 or greater and Flask 2.2.0 or greater.

:::note warn
注意
なお、バージョン0.15.0以降のFlask-Injectorを使用する場合はInjectorのバージョンが0.20.0であることFlaskのバージョンが2.2.0以上である必要があります。
:::

```
pip install Flask-Injector
```

Flask-Injectorをインストールできたら、
コードを書き進めていきます。

Flask-Injectorは内部でInjectorモジュールを使用しております。
実際に使う場合にはInjectorモジュールの学習も必要になります。

https://pypi.org/project/injector/

かなり簡易的な実装になります。
そちらを留意頂けると幸いでございます。

```python:app.py
from flask import Flask
from flask_injector import FlaskInjector, request, Binder, inject
from model import AbstractUserProfileTable, UserProfileTable
from dataclasses import dataclass

app = Flask(__name__)

@dataclass
class Hoge:
    name: str = "taro"

class Fuga:
    @inject
    def __init__(self, hoge: Hoge):
        self.hoge = hoge

    def test_func(self):
        return self.hoge.name

@app.route("/foo")
def foo(test_cls: Fuga):
    return test_cls.test_func()

@app.route("/bar")
def bar(user_profile: AbstractUserProfileTable):
    users = user_profile.find_all()
    return users

def configure(binder: Binder):
    binder.bind(
        AbstractUserProfileTable,
        to=UserProfileTable,
        scope=request
    )

injecter = FlaskInjector(app=app, modules=[configure])

if __name__ == "__main__":
  app.run()
```


```python:model.py
import os
from dotenv import load_dotenv
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy.schema import Column
from sqlalchemy.types import Integer, String
from abc import ABC, abstractmethod
from sqlalchemy.orm import sessionmaker

load_dotenv()
engine = create_engine(
        f"postgresql+psycopg2://{os.getenv('USER')}:{os.getenv('PASSWORD')}@{os.getenv('HOST')}:{os.getenv('PORT')}/{os.getenv('DATABASE')}")
Base = declarative_base()
session_maker = sessionmaker(engine)
session = session_maker()

class UserProfile(Base):
    __tablename__ = "user_profile"
    user_id = Column(Integer, primary_key=True)
    first_name = Column(String)
    last_name = Column(String)

class AbstractUserProfileTable(ABC):
    @abstractmethod
    def find_all(self):
        pass

class UserProfileTable(AbstractUserProfileTable):
    def find_all(self):
        return session.query(UserProfile).all()

```

コードの解説をしていきます。

```python
class Fuga:
    @inject
    def __init__(self, hoge: Hoge):
        self.hoge = hoge
```

外部からオブジェクトを注入する際には@injectでデコレートする必要があります。
@injectでデコレートしないとFugaクラスのインスタンス生成時にエラーになります。
また、**型アノテーションが必須になる**ので忘れずに宣言しておいてください。

```python
    binder.bind(
        AbstractUserProfileTable,
        to=UserProfileTable,
        scope=request
    )
```
上記のコードは抽象クラスと具象クラスの紐づけに使用しています。

```python
@app.route("/bar")
def bar(user_profile: AbstractUserProfileTable):
    users = user_profile.find_all()
    return users
```
上記ではAbstractUserProfileTableインスタンスを注入しているが、呼びされているfind_allメソッドは具象クラスのメソッドになります。



## 参考にさせていただいた資料・記事
https://pypi.org/project/Flask-Injector/

https://pypi.org/project/injector/

https://injector.readthedocs.io/en/latest/api.html

https://qiita.com/mkgask/items/d984f7f4d94cc39d8e3c

https://zenn.dev/ktnyt/articles/cc5056ce81e9d3