前々回と前回の続きです。
クラスの依存関係の管理方法について解説しました。
injectorの実用的なテクニックについて深掘りします。
アーキテクチャ設計と実装
今までのコードでDIの考え方について解説してきました。また、その実装をライブラリを使って実現する方法についても示してきました。このようにアプリケーションのアーキテクチャを設計し、決定した場合、その後実装を行うはずです。
しかし、実際にAWS Lambdaで実装すると、ひとつ思うことがあります。
lambda handlerの最初にinjectorインスタンスを作成し、設定を行う必要があり、複数のLambdaを運用すると重複したコードになります。
これにはいくつかの問題が挙げられます。
- 関係するすべてのLambda実装者がinjectorについて理解している必要があります。
- injectorの使い方に関する変更があった際に手間がかかります。
アプローチは様々考えられますが、ひとつの解決例を提示します。
デコレータ
解決案を提示するためにまず、デコレータについて説明します。
ざっくり説明すると、関数などに前処理や後処理を追加する事が可能なPythonの機能です。
class AppConfig():
@classmethod
def get_url(cls) -> str:
return "https://~"
print(AppConfig.get_url())
標準機能で提供されているデコレータも存在します。例えばクラスに対して静的メソッドを定義したい場合はclassmethodを利用します。利用するときは@の後にデコレータ名を記述します。そしてデコレータは自前で実装する事も可能です。
デコレータの実装
元々解決したかった事は、DIコンテナの初期化が各lambdaで重複する事でした。
以下サンプルコードです。エディタに静的解析を入れている方は警告が出る場合もあると思いますが、動作させるとユーザデータが標準出力に表示されるはずです。
from injector import Injector, inject
class SampleRepository:
def get_users(self) -> list[dict]:
return [{"id": 1, "name": "taro.sample"}]
class SampleService:
@inject
def __init__(self, sample_repository: SampleRepository) -> None:
self._sample_repository = sample_repository
def show_users(self) -> None:
print(self._sample_repository.get_users())
def injector_init(lambda_handler):
def wrapper(*args, **kwargs):
injector = Injector()
return injector.call_with_injection(lambda_handler)
return wrapper
@injector_init
@inject
def lambda_handler(sample_service: SampleService):
sample_service.show_users()
event = {}
context = {}
lambda_handler(event, context)
Lambdaの仕様としてeventとcontextというふたつの引数を受け取り実行されますが、デコレータによって振る舞いを変えています。
具体的にはcall_with_injectionというメソッドでデコレータの対象関数を呼び出しています。これは可能な限りinjectorが依存関係を解決して呼び出すメソッドです。これによってlambda_handlerはeventやcontextではなくSampleServiceクラスのインスタンスを引数に受け取り、処理を実行する事ができたのです。
このようなコードをLayerにして共通化することで先に挙げていた問題の解決になるかもしれません。
まとめ
injector(DIコンテナ)の隠蔽について解説しました。それなりのLambdaになるとeventの情報を引き回すことでスパゲッティコードが生まれてしまいます。
DIを活用することで、ひとつ悩みの解消になるはずです。それでは良いPythonライフを。