Pythonのクリーンアーキテクチャ:段階的なデモ。 パート2



内容




ドメインモデル



Gitタグ: Step02



StorageRoom



モデルの簡単な定義から始めましょう。 前述のように、純粋なアーキテクチャのモデルは非常に軽量であり、少なくともフレームワークの対応するORMよりも軽量です。



TDDの方法論に従っているため、最初に書くのはテストです。 ファイルtests/domain/test_storageroom.py



を作成し、このコードをその中に配置します。



 import uuid from rentomatic.domain.storageroom import StorageRoom def test_storageroom_model_init(): code = uuid.uuid4() storageroom = StorageRoom(code, size=200, price=10, longitude='-0.09998975', latitude='51.75436293') assert storageroom.code == code assert storageroom.size == 200 assert storageroom.price == 10 assert storageroom.longitude == -0.09998975 assert storageroom.latitude == 51.75436293 def test_storageroom_model_from_dict(): code = uuid.uuid4() storageroom = StorageRoom.from_dict( { 'code': code, 'size': 200, 'price': 10, 'longitude': '-0.09998975', 'latitude': '51.75436293' } ) assert storageroom.code == code assert storageroom.size == 200 assert storageroom.price == 10 assert storageroom.longitude == -0.09998975 assert storageroom.latitude == 51.75436293
      
      





これらの2つのテストは、モデルに渡される正しい値または辞書の助けでモデルを初期化できることを確認します。 最初のケースでは、すべてのモデルパラメーターを指定する必要があります。 後で、必要なテストを作成した後、それらの一部をオプションにすることができます。



それまでの間、 StorageRoom



クラスをファイルrentomatic/domain/storageroom.py



配置して書きましょう。 プロジェクトの各サブディレクトリに__init__.py



ファイルを作成することを忘れないでください__init__.py



はこれをモジュールとして認識します。



 from rentomatic.shared.domain_model import DomainModel class StorageRoom(object): def __init__(self, code, size, price, latitude, longitude): self.code = code self.size = size self.price = price self.latitude = float(latitude) self.longitude = float(longitude) @classmethod def from_dict(cls, adict): room = StorageRoom( code=adict['code'], size=adict['size'], price=adict['price'], latitude=adict['latitude'], longitude=adict['longitude'], ) return room DomainModel.register(StorageRoom)
      
      





モデルは非常に単純であり、説明は不要です。 純粋なアーキテクチャの利点の1つは、各層に、分離されたときに簡単なタスクを実行する必要がある小さなコードが含まれていることです。 私たちの場合、モデルはクラス内の情報を初期化して保存するためのAPIを提供します。



from_dict



メソッドfrom_dict



、別のレイヤー(データベースレイヤーやRESTレイヤーのクエリ文字列など)からのデータからモデルを作成する場合に便利です。



from_dict



関数を抽象化してModelクラスのメソッドとしてfrom_dict



ことで、関数を単純化しようとするのは魅力的かもしれません。 また、特定のレベルの抽象化と一般化が可能であり、必要であり、モデルの初期化が他のさまざまなシナリオと相互作用できる場合、クラス自体に直接実装することをお勧めします。



DomainModel



抽象基本クラスは、 クラスがシステム内のモデルに属することを確認するなど、将来のシナリオのためにモデルを分類する簡単な方法です。 Pythonでの抽象基本クラスの使用の詳細については、 この投稿を読むことをお勧めします。



シリアライザー



Gitタグ: Step03



API呼び出しの結果としてモデルを返したい場合は、シリアル化する必要があります。 一般的なシリアル化形式はJSONです。これは、Web APIに使用される広範な標準であるためです。 シリアライザーはモデルの一部ではありません。 これは、モデルのインスタンスを受け取り、その構造と値を何らかの表現に変換する外部の特別なクラスです。



StorageRoom



クラスのJSONシリアル化をテストするStorageRoom



は、次のコードをtests/serializers/test_storageroom_serializer.py



ファイルにtests/serializers/test_storageroom_serializer.py



ます



 import datetime import pytest import json from rentomatic.serializers import storageroom_serializer as srs from rentomatic.domain.storageroom import StorageRoom def test_serialize_domain_storageroom(): room = StorageRoom('f853578c-fc0f-4e65-81b8-566c5dffa35a', size=200, price=10, longitude='-0.09998975', latitude='51.75436293') expected_json = """ { "code": "f853578c-fc0f-4e65-81b8-566c5dffa35a", "size": 200, "price": 10, "longitude": -0.09998975, "latitude": 51.75436293 } """ assert json.loads(json.dumps(room, cls=srs.StorageRoomEncoder)) == json.loads(expected_json) def test_serialize_domain_storageruum_wrong_type(): with pytest.raises(TypeError): json.dumps(datetime.datetime.now(), cls=srs.StorageRoomEncoder)
      
      





rentomatic/serializers/storageroom_serializer.py



で、テストに合格するコードを渡します。



 import json class StorageRoomEncoder(json.JSONEncoder): def default(self, o): try: to_serialize = { 'code': o.code, 'size': o.size, 'price': o.price, "latitude": o.latitude, "longitude": o.longitude, } return to_serialize except AttributeError: return super().default(o)
      
      





JSON.JSONEncoder



から継承されたクラスを提供し、 json.dumps(room, cls = StorageRoomEncoder)



を使用してモデルをシリアル化します。



コード内で繰り返しが発生する場合があります。 これはクリーンなアーキテクチャのマイナスであり、迷惑です。 可能な限りレイヤーを分離し、軽量クラスを作成するため、最終的にいくつかの手順を繰り返します。 たとえば、 StorageRoom



からJSON属性に属性を割り当てるシリアル化コードは、辞書からオブジェクトを作成するために使用するものと似ています。 同じことではありませんが、2つの機能には類似点があります。



シナリオ(パート1)



Gitタグ: Step04



アプリケーションの実際のビジネスロジックを実装する時が来ました。これは外部で利用できます。 シナリオは、ストレージを要求し、ビジネスルール、ロジックを適用し、心の望みどおりにデータを変換し、結果を返すクラスを実装する場所です。



これらの要件を考慮して、 シナリオを順番に構築していきましょう。 作成できる最も単純なシナリオは、倉庫からすべての保管施設を取得し、それらを返すシナリオです。 ストレージ層はまだ実装されていないことに注意してください。したがって、テストではそれを濡らします(フィクションで置き換えます)。



ここに、すべてのストレージ機能をリストする簡単なスクリプトテストの基礎があります。 このコードをtests/use_cases/test_storageroom_list_use_case.py







 import pytest from unittest import mock from rentomatic.domain.storageroom import StorageRoom from rentomatic.use_cases import storageroom_use_cases as uc @pytest.fixture def domain_storagerooms(): storageroom_1 = StorageRoom( code='f853578c-fc0f-4e65-81b8-566c5dffa35a', size=215, price=39, longitude='-0.09998975', latitude='51.75436293', ) storageroom_2 = StorageRoom( code='fe2c3195-aeff-487a-a08f-e0bdc0ec6e9a', size=405, price=66, longitude='0.18228006', latitude='51.74640997', ) storageroom_3 = StorageRoom( code='913694c6-435a-4366-ba0d-da5334a611b2', size=56, price=60, longitude='0.27891577', latitude='51.45994069', ) storageroom_4 = StorageRoom( code='eed76e77-55c1-41ce-985d-ca49bf6c0585', size=93, price=48, longitude='0.33894476', latitude='51.39916678', ) return [storageroom_1, storageroom_2, storageroom_3, storageroom_4] def test_storageroom_list_without_parameters(domain_storagerooms): repo = mock.Mock() repo.list.return_value = domain_storagerooms storageroom_list_use_case = uc.StorageRoomListUseCase(repo) result = storageroom_list_use_case.execute() repo.list.assert_called_with() assert result == domain_storagerooms
      
      





テストは簡単です。 最初に、以前に作成されたモデルのリストを返すlist()



メソッドを提供するように、リポジトリを置き換えました。 次に、リポジトリでスクリプトを初期化して実行し、結果を記憶します。 最初に確認するのは、パラメーターを指定せずにストレージメソッドが呼び出されたことと、結果の正確性です。



次に、テストに合格するスクリプトの実装を示します 。 コードをファイルrentomatic/use_cases/storageroom_use_case.py







 class StorageRoomListUseCase(object): def __init__(self, repo): self.repo = repo def execute(self): return self.repo.list()
      
      





ただし、このようなシナリオの実装ではすぐに問題が発生します。 まず、通話パラメータを転送する標準的な方法がないため、その正確性を検証する標準的な方法がありません。 次の問題は、呼び出しの結果を返す標準的な方法が欠落していることです。したがって、呼び出しが成功したかどうか、またそうでない場合は、どのような理由でそれを見つけることができません。 前の段落で説明した不正なパラメーターでも同じ問題が発生します。



したがって、 スクリプトの入力と出力をラップするためにいくつかの構造を導入する必要があります。 これらの構造は、 要求および応答オブジェクトと呼ばれます



リクエストと回答



Gitタグ: Step05



要求と応答は、クリーンアーキテクチャの重要な部分です。 スクリプトレイヤーと外部環境の間で呼び出しパラメーター、入力データ、呼び出し結果を移動します。



リクエストは着信APIコールに基づいて作成されるため、不正な値、欠落しているパラメータ、不正な形式などが発生します。 一方、 Answersには、エラーを表示したり、何が起こったかについての詳細な情報を提供したりするなど、API呼び出しの結果を含める必要があります。



あなたにはリクエストとレスポンスの実装を使用する権利があります。クリーンなアーキテクチャはそれについて何も言いません。 データをどのようにパックして表示するかについての決定は完全にあなた次第です。



それまでは、パラメータなしで初期化できるStorageRoomListRequestObject



が必要なだけなので、ファイルtests/use_cases/test_storageroom_list_request_objects.py



を作成し、このオブジェクトのテストをその中に入れましょう。



 from rentomatic.use_cases import request_objects as ro def test_build_storageroom_list_request_object_without_parameters(): req = ro.StorageRoomListRequestObject() assert bool(req) is True def test_build_file_list_request_object_from_empty_dict(): req = ro.StorageRoomListRequestObject.from_dict({}) assert bool(req) is True
      
      





現時点では、リクエストオブジェクトは空ですが、オブジェクトのリストを発行するスクリプトのパラメーターを取得するとすぐに役立ちます。 StorageRoomListRequestObject



クラスのコードは、ファイルrentomatic/use_cases/request_objects.py



あり、次のようになります。



 class StorageRoomListRequestObject(object): @classmethod def from_dict(cls, adict): return StorageRoomListRequestObject() def __nonzero__(self): return True
      
      





現時点では成功した応答のみが必要なため、 要求も非常に単純です。 リクエストとは異なり、 答えは特定のスクリプトに関連していないため、テストファイルはtests/shared/test_response_object.py



と呼ばれtests/shared/test_response_object.py







 from rentomatic.shared import response_object as ro def test_response_success_is_true(): assert bool(ro.ResponseSuccess()) is True
      
      





実際の応答オブジェクトはファイルrentomatic/shared/response_object.py







 class ResponseSuccess(object): def __init__(self, value=None): self.value = value def __nonzero__(self): return True __bool__ = __nonzero__
      
      





パート3に続きます。








All Articles