よくあるコネクタのアンチパターンは、ConnectionProvider が外部システムへのアクセスに使用するクライアントや実装を公開してしまう接続オブジェクトを生成することです。次に例を示します。
public class HttpConnection {
private HttpClient client;
public HttpClient getClient() {
return client;
}
}
操作では、このオブジェクトを次の疑似コードに示すように使用します。
public void createCustomer(@Connection HttpConnection connection, @Content InputStream content) {
connection.getClient().send(HttpRequest.builder()
.path(connection.getPath() + "/customer")
.method("POST")
.entity(content)
.addHeader("Accept", "application/json")
.build()
);
}
このコードにはいくつかの問題があります。最も明白な問題は、接続を使用する操作が接続オブジェクトの実装と強力に結合しているという点です。クライアント実装の変更が必要になった場合は、その接続を使用しているすべてのコンポーネントが影響を受けます。
別の問題は、接続オブジェクトをテストできないという点です。クライアントオブジェクトは公開されるため、接続オブジェクトのモックバージョンとやり取りするテストは複雑になりすぎてしまいます。
そして最大の問題は、接続オブジェクトを使用するすべてのコンポーネントの間で機能的な結合が発生し、サポート対象のすべての接続プロバイダーで機能が微妙に異なってしまうという点です。
たとえば、上の例が基本認証と OAuth 認証の両方のメカニズムをサポートするコネクタの一部であるとします。
基本認証接続で正しくないログイン情報が使用された場合には、要求の結果として HTTP 401 状況コードが返され、操作は失敗するべきです。
しかし、OAuth で保護された接続でこの状況コードが返された場合には、コネクタはトークンが期限切れかどうかを判断するためのロジックを実行する必要があるため、AccessTokenExpiredException がスローされるべきです。
接続種別やコンポーネントがさらに増えると、問題はますます悪化します。これは一例に過ぎません。HTTP クライアントだけではなく、他の接続種別でも同じ問題が発生します。
この問題を防止するためには、接続オブジェクトは内部の通信メカニズムとセキュリティスキーマをカプセル化する必要があり、パターンは次のようになります。
public void createCustomer(@Connection HttpConnection connection, @Content InputStream content) {
connection.createCustomer(content);
}
このアプローチなら、各 ConnectionProvider 実装がそれぞれに適した HttpConnection オブジェクト実装を提供できるため、問題の根本原因そのものが解消し、他のコンポーネントに影響することなく内部の接続メカニズムを変更できるようになります。
代替手段: コマンドパターンの活用
接続オブジェクトに、コンシュームするエンドポイントごとに 1 つのメソッドを与えるのではなく、より汎用的なコマンド設計パターンを実装することができます。
public void createCustomer(@Connection HttpConnection connection, @Content InputStream content) {
connection.request(HttpRequest.builder()
.path(connection.getPath() + "/customer")
.method("POST")
.entity(content)
);
}
このアプローチでは、接続オブジェクトは HttpRequestBuilder オブジェクトを受け取る 1 つの汎用要求メソッドのみを持ちます。ビルダーオブジェクトが build() コマンドを受け取ることはありません。接続オブジェクトの実装に応じて、追加のヘッダーを追加したり、要求を異なる方法で実行したり、それらに応じて応答を処理したりできます。