Feignに基づいて強く型付けされたインターフェースを実装する
Sanfeng soft Zhang Sanfeng
強い型と弱い型の言語
ご存知のように、プログラミング言語は強いタイプと弱いタイプに分けられ、動的なタイプと静的なタイプにも分けられます。たとえば、JavaとC#は強く型付けされた静的言語であり、JavascriptとPHPは弱い型付けの動的言語です。
強く型付けされた静的言語は、しばしば型安全言語と呼ばれます。型チェックは通常、事前にいくつかの型エラーを回避するためにコンパイル中に実施されます。弱い型の動的言語にも型の概念がありますが、それらは比較的緩くて柔軟性があり、それらのほとんどはインタープリター型言語です。通常、必須の型チェックはなく、型の問題は通常、実行時に明らかになります。
強い言語と弱い言語には、それぞれ長所と短所があり、互いに補完し合い、それぞれに適用可能なシナリオがあります。たとえば、サーバー側の開発では強い型を使用することが多く、フロントエンドWebインターフェイスではJavascriptのような弱い型の言語を使用することがよくあります。
強い型と弱い型のAPI
サービスAPIには強いタイプと弱いタイプもあります。従来のRPCサービスは一般に強いタイプです。RPCは通常、カスタマイズされたバイナリプロトコルを使用してメッセージをエンコードおよびデコードし、TCPを使用してメッセージを送信します。RPCサービスには通常、厳密なコントラクトがあります。サーバーの開発前にIDL(インターフェイス定義言語)を定義する必要があります。IDLを使用してコントラクトを定義すると、厳密に型指定されたサーバーおよびクライアントインターフェイスがコントラクトを通じて自動的に生成されます。サービスが呼び出されると、強く型付けされたクライアントが直接使用され、メッセージを手動でエンコードおよびデコードする必要はありません。gRPCとApache Thriftは、現在2つの主流のRPCフレームワークです。
最近のほとんどのRestfulサービスは通常弱いタイプです。Restは通常、送信メッセージとしてJsonを使用し、送信プロトコルとしてHTTPを使用します。Restfulサービスは通常、厳密な契約概念を持っていません。通常のHTTPクライアントを使用して呼び出すことができますが、呼び出し元は通常必要です。 Jsonメッセージを手動でエンコードおよびデコードします。現実の世界では、ほとんどのサービスフレームワークは弱いタイプのRestfulサービスフレームワークです。たとえば、JavaエコシステムのSpringBootは、現在の主流の弱いタイプのRestfulフレームワークの1つと見なすことができます。
もちろん、上記の区別は業界標準ではなく、個人的な経験に基づく区別の方法です。
APIの長所と短所
強く型付けされたサービスインターフェイスの利点は、インターフェイスの仕様、自動コード生成、自動エンコードとデコード、およびコンパイル中の自動型チェックです。強く型付けされたインターフェースの利点には、欠点もあります。まず、クライアントとサーバーが強力に結合されています。どちらかの側でアップグレードや変更を行うと、もう一方が破損する可能性があります。さらに、自動コード生成にはツールのサポートと開発コストが必要です。これらのツールは比較的高いです。第二に、強い型のインターフェースの開発とテストはあまり友好的ではありません。一般的なブラウザーやPostmanのようなツールは、強い型のインターフェースに直接アクセスすることはできません。
ウィークタイプのサービスインターフェイスの利点は、クライアントとサーバーが強力に結合されておらず、特別なコード生成ツールを開発する必要がないことです。開発とテストに適した一般的なHTTPクライアントを呼び出すことができ、さまざまなブラウザやPostmanから簡単にアクセスできます。弱い型のサービスインターフェイスの欠点は、呼び出し元がメッセージを手動でエンコードおよびデコードする必要があること、自動コード生成がないこと、コンパイラインターフェイスの型チェックがないこと、コードの標準化が容易でないこと、開発効率が比較的低いことです。ランタイムエラーが発生しやすいです。
強力なサービスインターフェイスと弱いサービスインターフェイスの欠点を回避しながら、それらの利点を組み合わせる方法はありますか?
私たちのアプローチは、Spring Feignがサポートするストロングタイプインターフェイスの特性を利用して、スプリングレストウィークタイプインターフェイスに基づくストロングタイプレストインターフェイスの呼び出しメカニズムを実装すると同時に、ストロングタイプインターフェイスとウィークタイプインターフェイスの利点を活用することです。
まず、Spring Feignを紹介しましょう。SpringFeignは本質的に動的プロキシメカニズム(動的プロキシ)です。RestfulAPIに対応するJavaインターフェイスを提供するだけで、実行時に対応するインターフェイスの強力なタイプを動的にアセンブルできます。クライアント。アセンブルされたクライアントの構造と要求応答プロセスを次の図に示します。
1.クライアントアプリケーションはリクエストを開始し、リクエストBeanを渡します。このリクエストは最初にJavaインターフェイスを介してダイナミックプロキシによってインターセプトされ
ます。2。対応するエンコーダー(エンコーダー)によってエンコードされてリクエストJsonになり
ます。3 。リクエストJsonはいくつかの処理を受けることができます。必要に応じてインターセプトさらに処理するためのインターセプター
4.処理後、HTTPクライアントに渡され、HTTPクライアントはHTTPプロトコルを介して要求Jsonをサーバーに送信します。5。
サーバーが応答すると、対応する応答Jsonは次のようになります。 HTTPクライアントによって受信されます。6
。一部のインターセプターが応答処理を行った後
7.デコーダー(デコーダー)に転送してResponse Bean
8にデコードします。最後に、ResponseBeanがJavaインターフェイスを介して呼び出し元に返されます
。リクエスト応答プロセス全体は、Javaオブジェクトであるエンコードとデコードであり、Jsonメッセージの交換です。このプロセスは、シリアル化と逆シリアル化とも呼ばれ、もう1つは「Jsonオブジェクトバインディング」と呼ばれます。サービスフレームワークの場合、シリアル化と逆シリアル化のパフォーマンスがサービスフレームワークのパフォーマンスに最も大きな影響を与えます。つまり、デコーダーとエンコーダーを考慮して、サービスフレームワークの全体的なパフォーマンスを決定できます。
私たちが開発したサービスは弱い型のRestfulサービスですが、Spring Feignのサポートにより、強い型のJava APIインターフェイスを提供するだけで、強い型のクライアントを自動的に取得できます。つまり、SpringFeignを使用できます。強い型と弱い型の両方の利点(コンパイラによる自動型チェック、手動のエンコードとデコードの必要がない、コード生成ツールを開発する必要がない、クライアントとサーバーが強く結合されていない)、コードを標準化できます同時にスタイルを設定し、開発とテストの効率を向上させます。
プロジェクト内のマイクロサービスごとに2つのモジュールを提供できます。1つはAPIインターフェイスモジュール(mail-apiなど)で、もう1つはサービス実装モジュール(mail-svcなど)です。APIインターフェイスモジュールは、厳密に型指定されたJava APIインターフェイス(要求と応答のDTOを含む)であり、Spring Feignによって直接参照され、厳密に型指定されたクライアントに動的にアセンブルされます。
プロジェクトの構成は次のとおりです。
.
├── README.md
├── account
│ ├── Dockerfile
│ ├── pom.xml
│ └── src
│ ├── main
│ └── test
├── account-api
│ ├── pom.xml
│ └── src
│ └── main
├── mail
│ ├── Dockerfile
│ ├── mail.iml
│ ├── pom.xml
│ └── src
│ ├── main
│ └── test
├── mail-api
│ ├── pom.xml
│ └── src
│ └── main
└── pom.xml
注:ここではxxx-apiとxx-svcの命名方法を使用しませんでした。直接、xxx-apiはAPIクライアントモジュールを表し、xxxはサービス実装モジュールです。
ユーザーが登録した後に通知メールを送信することで、簡単な例を記述します。送信メールクライアントは次のように定義されています。
// mail-api/src/main/java/com/demo/mail/client/MailClient.java
import com.demo.common.api.dto.Response;
import com.demo.mail.dto.EmailSendDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import javax.validation.Valid;
@FeignClient(name = "mail")
public interface MailClient {
@PostMapping(path = "/send")
Response<Boolean> send(@RequestBody @Valid EmailSendDTO mailSendDTO);
}
応答は次のように定義されます。
@Data
public class Response<T> {
private int code = 0;
private String message = "OK";
private T data;
}
メーリングAPIを呼び出すユーザーサービスの実装は次のとおりです。
// account/src/main/java/com/demo/account/service/UserService.java
public class UserService {
@Autowired
private final UserRepository userRepository;
@Autowired
private MailClient mailClient;
public boolean register(UserDTO userVO) {
// 忽略参数验证部分代码...
User user= userRepository.save(UserDTOConvert.convertTo(userVO));
EmailSendDTO mail = EmailSendDTO.builder()
.to("user.getEmail()")
.subject("welcome!")
.htmlBody("hello," + user.getName()).build();
try {
Response<Boolean> response = mailClient.send(mail);
} catch (Exception e) {
log.error(e.getMessage());
throw new AppException(SysErrorEnum.SYSTEM_ERROR);
}
if (response.getCode() != 0) {
throw new ServiceException(response.getCode(), response.getMessage());
} else if (!response.getData()) {
throw new ServiceException(AccountErrorEnum.MAIL_SEND_ERROR);
}
return true;
}
}
例外処理プロセスを反映するために、上記のコードはデモンストレーションにのみ使用されます。本番環境で送信される電子メールは、送信結果を確認せずに非同期で処理する必要があります。サービスにグローバル例外処理を追加したので、それを上向きにスローします。
最後に、業界のRestful APIの設計では、通常HTTPプロトコルステータスコードを使用してエラーセマンティクスを伝達および表現しますが、私たちの設計では、エラーコードを通常のJsonメッセージ、つまりResponseと呼ばれるものにパッケージ化しています。カプセル化されたメッセージ+ピギーバックデザインパターン。このデザインの目標は、エラー処理を簡素化および標準化しながら、厳密に型指定されたクライアントをサポートすることです。HTTPプロトコルステータスコードを借用してエラーセマンティクスを伝達および表現する場合は、対応する厳密に型指定されたクライアントを開発することもできます。ただし、内部の呼処理ロジックはより複雑になり、さまざまなHTTPエラーコードを処理する必要があり、開発コストは比較的高くなります。