web-dev-qa-db-ja.com

きれいな建築デザインパターン

enter image description here

https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html

このパターンについていくつか質問があります。データベースは外部レイヤーにありますが、実際にはどのように機能しますか?たとえば、このエンティティを管理するだけのマイクロサービスがある場合:

person{
  id,
  name,
  age
}

また、ユースケースの1つは管理パーソンです。 Manage Personsは保存/取得/ .. Persons(=> CRUD操作)ですが、これを行うには、ユースケースがデータベースと通信する必要があります。しかし、それは依存関係ルールの違反になります

このアーキテクチャを機能させるオーバーライドルールは、依存関係ルールです。このルールは、ソースコードの依存関係は内部のみを指すことを示しています。

  1. これは有効なユースケースでしょうか?
  2. 外部レイヤーにあるデータベースにアクセスするにはどうすればよいですか? (依存関係のバージョン管理?)

GET /person/{id}リクエストにより、マイクロサービスはこのように処理する必要がありますか?

enter image description here

しかし、依存関係の逆転を使用すると、違反になります

内側の円の何も外側の円の何かについてまったく何も知ることができません。特に、外側の円で宣言されたものの名前は、内側の円のコードで言及されてはなりません。これには、関数、クラスが含まれます。変数、またはその他の名前付きソフトウェアエンティティ。


境界を越える。図の右下は、円の境界を越える方法の例です。次の層のユースケースと通信するコントローラーとプレゼンターを示しています。制御の流れに注意してください。それはコントローラーで始まり、ユースケースを移動し、プレゼンターで実行を完了します。ソースコードの依存関係にも注意してください。それらのそれぞれは、ユースケースを内側に向けています。

通常、この明らかな矛盾は、依存関係逆転原理を使用して解決します。たとえば、Javaのような言語では、ソースコードの依存関係が境界を越えた適切なポイントで制御の流れに対抗するように、インターフェイスと継承関係を調整します。

たとえば、ユースケースでプレゼンターを呼び出す必要があると考えてください。ただし、依存関係のルールに違反するため、この呼び出しは直接的なものであってはなりません。外側の円の名前を内側の円で言及することはできません。したがって、内側の円にユースケース呼び出しインターフェース(ここではユースケース出力ポートとして表示)を用意し、外側の円のプレゼンターにそれを実装させます。

同じ手法を使用して、アーキテクチャのすべての境界を越えます。動的ポリモーフィズムを利用して、制御フローに対抗するソースコードの依存関係を作成し、制御フローの方向に関係なく依存関係ルールに準拠できるようにします。

ユースケースレイヤーは、DBパッケージ(フレームワークとドライバーレイヤー)によって実装されるリポジトリインターフェイスを宣言する必要があります

enter image description here

サーバーがGET /persons/1 PersonRestがPersonRepositoryを作成し、このリポジトリ+ IDをManagePerson :: getPerson関数に渡すように要求します。getPersonはPersonRepositoryを認識していませんが、実装するインターフェースを認識しているため、ルールに違反していませんか? ManagePerson :: getPersonはそのリポジトリを使用してエンティティを検索し、PersonRest :: getに個人エンティティを返します。これにより、Json Objektがクライアントに返されます。

悲しいことに、英語は私の母国語ではないので、パターンが正しいことを理解しているかどうかを教えてくれ、私の質問のいくつかに答えてもらえれば幸いです。

事前にタイ

10
Barney Stinson

データベースは外部レイヤーにありますが、実際にはどのように機能しますか?

ゲートウェイ層でテクノロジーに依存しないインターフェースを作成し、それをdb層で実装します。例えば。

public interface OrderRepository {
    public List<Order> findByCustomer(Customer customer);
}

実装はdbレイヤーにあります

public class HibernateOrderRepository implements OrderRepository {
      ...
}

実行時に、内部層は外部層の実装で注入されます。しかし、ソースコードの依存関係はありません。

これは、インポートステートメントをスキャンすることで確認できます。

また、ユースケースの1つは管理パーソンです。 Manage Personsは、保存/取得/ .. Persons(=> CRUD操作)ですが、これを行うには、ユースケースがデータベースと通信する必要があります。しかし、それは依存関係ルールの違反になります

いいえ、ユースケースは必要なインターフェースを定義しているため、依存関係ルールに違反することはありません。 dbはそれを実装するだけです。

Mavenを使用してアプリケーションの依存関係を管理すると、db jarモジュールがユースケースに依存し、その逆ではないことがわかります。ただし、これらのユースケースインターフェースを独自のモジュールに抽出する方がよいでしょう。

次に、モジュールの依存関係は次のようになります

+-----+      +---------------+     +-----------+
|  db | -->  | use-cases-api | <-- | use cases |
+-----+      +---------------+     +-----------+

それは、さもなければこのように見える依存関係の反転です

+-----+      +-----------+
|  db | <--  | use cases |
+-----+      +-----------+

GET/person/{id}リクエストを受け取った場合、マイクロサービスはこのように処理する必要がありますか? enter image description here

はい。Webレイヤーがdbレイヤーにアクセスするため、違反になります。より良いアプローチは、Webレイヤーがコントローラーレイヤーにアクセスし、コントローラーレイヤーがユースケースレイヤーにアクセスすることなどです。

依存関係の逆転を維持するには、上記で示したようなインターフェイスを使用してレイヤーを分離する必要があります。

したがって、データを内部層に渡す場合は、必要なデータを取得して外部層に実装するメソッドを定義するインターフェースを内部層に導入する必要があります。

コントローラ層では、このようなインターフェースを指定します

public interface ControllerParams {
    public Long getPersonId();
}

webレイヤーでは、このようなサービスを実装する可能性があります

@Path("/person")
public PersonRestService {

    // Maybe injected using @Autowired if you are using spring
    private SomeController someController;

    @Get
    @Path("{id}")
    public void getPerson(PathParam("id") String id){
       try {
           Long personId = Long.valueOf(id);

           someController.someMethod(new ControllerParams(){
                public Long getPersonId(){
                    return personId;
                }
           });
       } catch (NumberFormatException e) {
           // handle it
       }
    }
}

一見すると定型コードのようです。ただし、残りのフレームワークに要求をJavaオブジェクトに逆シリアル化させることができます。そして、このオブジェクトは代わりにControllerParamsを実装する場合があります。

その結果、依存関係の反転ルールとクリーンなアーキテクチャに従う場合、内側のレイヤーに外側のレイヤーのクラスのインポートステートメントが表示されることはありません。

クリーンアーキテクチャの目的は、主要なビジネスクラスがテクノロジや環境に依存しないことです。依存関係は外層から内層を指しているため、外層が変化する唯一の理由は、内層の変化によるものです。または、外層の実装技術を交換する場合。例えば。残り-> SOAP

では、なぜこのような努力をする必要があるのでしょうか。

Robert C. Martinは、第5章「オブジェクト指向プログラミング」でそれを語っています。最後に、セクションの依存関係の逆転で彼は言う:

このアプローチにより、OO言語で記述されたシステムで作業するソフトウェアアーキテクトは、システム内のすべてのソースコードの依存関係の方向を完全に制御できます。これらの依存関係を制御のフローに合わせるように制限されていません。 。どのモジュールが呼び出しを行い、どのモジュールが呼び出されるかに関係なく、ソフトウェアアーキテクトはソースコードの依存関係をどちらの方向にもポイントできます。

それが力です!

開発者は、制御フローとソースコードの依存関係について混乱することがよくあります。制御フローは通常同じままですが、ソースコードの依存関係が逆になります。これにより、プラグインアーキテクチャを作成する機会が得られます。各インターフェースはプラグインするポイントです。そのため、交換することができます。技術上またはテスト上の理由から。

[〜#〜]編集[〜#〜]

ゲートウェイ層=インターフェースOrderRepository => OrderRepository-InterfaceはUseCases内にあるべきではありません。そのレベルでクラッド操作を使用する必要があるからですか?

OrderRepositoryをユースケースレイヤーに移動しても問題ないと思います。別のオプションは、ユースケースの入力ポートと出力ポートを使用することです。ユースケースの入力ポートには、リポジトリのようなメソッドがある場合があります。 findOrderById、これをOrderRepositoryに適合させます。永続化のために、出力ポートで定義したメソッドを使用できます。

public interface UseCaseInputPort {
    public Order findOrderById(Long id);
}

public interface UseCaseOutputPort {
    public void save(Order order);
}

OrderRepositoryだけを使用する場合との違いは、ユースケースのポートには、ユースケース固有のリポジ​​トリメソッドのみが含まれることです。したがって、ユースケースが変更された場合にのみ変更されます。したがって、それらには単一の責任があり、インターフェース分離の原則を尊重しました。

5
René Link

重要な要素は依存関係の逆転です。内層は外層への依存関係があってはなりません。したがって、たとえばユースケースレイヤーでデータベースリポジトリを呼び出す必要がある場合は、ユースケースレイヤー内にリポジトリインターフェース(インターフェースのみで、実装なし)を定義し、その実装をインターフェースアダプターレイヤーに配置する必要があります。

2
Adam Siemion