HttpClient
Web サーバーとの間で情報を送受信するために使用するクライアント ライブラリを作成しようとしています。このライブラリの中核となるのは、次のWebClient
クラスです。
public class WebClient {
public String send(String apiUrl) {
// Use HttpClient to send a message to 'apiUrl', such as:
// http://example.com?a=1&response=xml
//
// Wait for a response, and extract the HTTP response's body out
// as raw text string. Return the body as a string.
}
}
繰り返しますが、これは私が書いているライブラリmylib.jar
です ( )。さまざまなサービスで構成されるライブラリ。各サービスには、API 開発者がサーバーへのデータの読み取り/書き込みに使用できる 1 つ以上のメソッドがあります。したがって、サービスは次のようになります。
WidgetService
getAllWidgets
getMostExpensiveWidget
getLastWidgetUpdated
etc...
FizzService
getFizzById
getFizziestFizz
etc...
BuzzService
etc...
...
各サービス メソッドは、1 つ以上の Java プリミティブまたは 1 つ以上のモデル インスタンス (エンティティ、値オブジェクトなど) を取ります。すべてのサービス メソッドは、そのようなモデル オブジェクトを 1 つ返します。たとえば、次のようになります。
public class FizzService {
public Fizz getFizzById(Long fizzId) {
// ...
}
public Fizz getFizzByBuzz(Buzz buzz) {
// ...
}
}
これは、 が受信した HTTP 応答本文をWebClient#send()
最終的に Java オブジェクトにマッピングし直す必要があることを意味するため、注意が必要です。
API 開発者の観点から言えば、開発者が各サービス インスタンスをインスタンス化し、それをWebClient
内部で使用するために渡し、引数と API URL の間、および HTTP 応答本文とモデル/エンティティの間のすべてのマッピングをサービスに行わせるようにするだけです。
たとえば、次のようになります。
public class FizzService {
private WebClient webClient;
private FizzApiBuilder fizzApiBuilder;
// Getters & setters for all properties...
public Fizz getFizzByBuzz(Buzz buzz) {
// apiCall == "http://example.com/fizz-service/getFizzByBuzz?buzz_id=93ud94i49&response=json"
String apiCall = fizzApiBuilder.build(buzz).toString();
// We asked API to send back a Fizz as JSON, so responseBody is a JSON String representing
// the correct Fizz.
String responseBody = webClient.send(apiCall);
if(apiCall.contains("json"))
return JsonFizzMapper.toFizz(reponseBody);
else
return XmlFizzMapper.toFizz(responseBody);
}
}
// What the API developer writes:
WebClient webClient = WebClientFactory.newWebClient();
FizzService fizzSvc = new FizzService(webClient);
Buzz b = getBuzz();
Fizz f = fizzSvc.getFizzByBuzz(b);
これまでのところ、私はこの設定が好きです。ただし、すべてのサービスにわたって、すべてのサービスメソッドに対して同じ「ボイラープレート」コードが必要になります。
String apiCall = someBuilder.build(...)
String responseBody = webClient.send(apiCall)
if(apiCall.contains("json"))
return JsonMapper.toSomething(responseBody)
else
return XmlMapper.toSomething(responseBody)
これは、抽象化の主要なユースケースのような匂いがし始めています。理想的には、このボイラープレート コードをすべて に入れ、AbstractService
各サービスにその抽象クラスを拡張させたいと考えています。1つだけ問題があります:
public abstract class AbstractService {
private WebClient webClient;
private ServiceApiBuilder apiBuilder;
// Getters & setters for all properties...
public Object invoke(Object... params) {
// apiCall == "http://example.com/fizz-service/getFizzByBuzz?buzz_id=93ud94i49&response=json"
// The injected apiBuilder knows how to validate params and use them to build the api url.
String apiCall = apiBuilder.build(params).toString();
String responseBody = webClient.send(apiCall);
if(apiCall.contains("json"))
return jsonMapper.to???(reponseBody);
else
return xmlMapper.to???(responseBody);
}
}
public class FizzService extends AbstractService { ... }
問題は、機能を に抽象化すると、API 呼び出しを作成するのが面倒AbstractService
になることです(ただし、不可能ではありません)。さらに悪いことに、どのモデル/バックをマップするエンティティ。responseBody
これはすべて悪臭を放ち始めています。私のコードが悪臭を放つとき、私は解決策を探します。解決策が見つからない場合は、ここに来ます。だから私は尋ねます:私は根本的に間違ったアプローチをとっていますか?もしそうなら、私のアプローチはどうあるべきですか?そして、私が近い場合、どうすれば正しいマッパーを挿入し、このコードの匂いを良くすることができますか? API 開発者にモデル/サービスを実際に使用してもらいたい方法を示す私のコード スニペットを覚えておいてください。これがここでの最終的な目標です。前もって感謝します。