マイクロサービスアーキテクチャでのコードの再利用-例としてのスプリングブート

こんにちは、Khabrovites! 本日は、マイクロサービスの尽きることのないトピックに関する別の興味深い投稿を提供します。今回はJava言語の著名人や初心者向けです。 読んで投票する!



ほとんどのマイクロサービスアーキテクチャでは、コードを共有する機会が多くあります。したがって、これを行う誘惑は素晴らしいものです。 この記事では、私自身の経験を共有します。コードを再利用するのが適切な場合と、回避する方が良い場合について説明します。 すべてのポイントは、Githubで入手可能な Spring Bootを使用した特別なプロジェクトの例で説明します



はじめに



コード共有とその背後にあるものについて話す前に、マイクロサービスアーキテクチャを使用して通常どのタスクが解決されるかを判断します。 マイクロサービスを実装する主な利点は次のとおりです。





当然、これらの利点の多くは、より良いシステムを構築するだけでなく、開発者の生活を促進し、彼の仕事をより感謝することも可能にします。 もちろん、好きなだけ議論することができますので、マイクロサービスが有用であることに同意しましょう(NetflixやNginxなどの大企業の経験によって確認されています)。 他のアーキテクチャと同様に、マイクロサービスには、克服する必要のある独自の欠点と困難があります。 最も重要なのは:





問題点



そのため、ここでは、ほとんどのチームがマイクロサービスを使用し始めているという問題に取り組みます。 マイクロサービスを使用する目的と、その実装に推奨される手法を考慮すると、次の問題に直面します。「弱く接続されたサービスが必要です。 したがって、サービスを利用するたびに、応答を処理するクラスを作成する必要があります。 しかし、DRY原則(Do Not Repeat)はどうでしょうか? どうする?」 この場合、2つのアンチパターンを簡単にヒットできます。





解決策



アーキテクチャの目的と問題の説明方法を明確に明確にすると、解決策が示唆されるようです。 サービスコードを完全に独立させる必要があるが、クライアントでかなり複雑な応答を使用する必要がある場合、クライアントはこのサービスを使用するために独自のライブラリを作成する必要があります。



このアプローチには次の利点があります。





このソリューションは完全に新しいものとは言えません-Sam Newmanの著書「 Creating Microservices 」で説明されているようなアプローチです(強くお勧めします)。 これらのアイデアの具体化は、多くの成功したマイクロサービスアーキテクチャに見られます。 この記事は主にサブジェクト領域でのコードの再利用に焦点を当てていますが、ここで説明する原則と矛盾しないため、一般的な接続性と情報交換を提供するコードにも同様の原則が当てはまります。



別の質問も考えられます。ドメインオブジェクトのリンクとクライアントライブラリとの接続について心配する価値はありますか。 主な質問に対する答えと同様に、この場合の最も重要な要因は、アーキテクチャ全体に対するそのような詳細の影響です。 クライアントライブラリに接続コードを含めると生産性が向上すると判断した場合、クライアントサービス間に強力なリンケージがないことを確認する必要があります。 このようなアーキテクチャの接続は通常、単純なREST呼び出しまたはメッセージキューを使用して提供されるため、このようなコードをクライアントライブラリに配置することはお勧めしません。 接続のコードに特別なものや複雑すぎるものがある場合(たとえば、SOAP要求を実行するためのクライアント証明書)、追加のライブラリを前に添付することをお勧めします。 このパスを選択する場合、クライアントライブラリの使用をオプションとして指定し、必須ではありません。 クライアントサービスは、コードを完全に所有する必要はありません(サービスプロバイダーが対応するクライアントライブラリを確実に更新することを義務付けることは不可能です)。



スプリングブートの例







それで、私は解決策を説明し、今度はコードでそれを実証します。 ところで、私のお気に入りのマイクロサービスライブラリであるSpring Bootをもう一度宣伝する機会があります。 この記事専用に作成されたGithubリポジトリからサンプル全体をダウンロードできます。



Spring Bootを使用すると、すぐにマイクロサービスを開発できます-はい、私は誇張していません。 Dropwizardが高速に見える場合、Spring Bootを使用するのがどれほど便利であるかに非常に驚くでしょう。 この例では、シミュレートされたUser



JSONオブジェクトを返す非常に単純なUser



サービスを開発しています。 将来、このサービスは通知サービスとテーブルサービスによって使用され、実際、データのさまざまな表現を構築します。 ただし、どちらの場合も、サービスはUser



オブジェクトを理解する必要があります。



ユーザーサービス



UserServiceApplication



にはmainメソッドが含まれます。 これはSpring Bootであるため、起動時に統合されたTomcatサーバーも含まれます。



 package com.example; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class UserServiceApplication { public static void main(String[] args) { SpringApplication.run(UserServiceApplication.class, args); } }
      
      





実際、これ以上簡単なことはありません! Spring Bootは非常にカテゴリー的なフレームワークなので、デフォルトが私たちに合っていれば、手動で入力することはほとんど何もありません。 ただし、1つ変更する必要があります。デフォルトのポート番号についてです。 application.properties



ファイルでこれがどのように行われるかを見てみましょう。



 server.port = 9001
      
      





シンプルで美しい。 JavaでRESTサービスを作成したことがある場合は、このためにController



が必要であることをご存じでしょう。 初めてこれを行う場合-心配しないで、Spring Bootでコントローラを書くのは非常に簡単です:



 package com.example; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class UserController { @RequestMapping("/user") public User getUser(@RequestParam(value="id", defaultValue="1") int id) { return new User(id); } }
      
      





それでは、ユーザーにエンドポイント/user?id=



へのリクエストを実行させるだけ/user?id=



、ここでid



は興味のあるユーザーと一致します。 これらのクラスがどれほど単純かを考えると、実際には、すべてのロジックは特定のUser



クラスにあるべきです。 このクラスは生データを生成し、 ジャクソン (Java用のJSONライブラリ)を使用してシリアル化されます。



 package com.example; import java.util.ArrayList; import java.util.List; public class User { private final long id; private final String forename; private final String surname; private final String organisation; private final List<String> notifications; private final long points; //        private final List<String> friends; public User(int id) { String[] forenames = {"Alice", "Manjula", "Bartosz", "Mack"}; String[] surnames = {"Smith", "Salvatore", "Jedrzejewski", "Scott"}; String[] organisations = {"ScottLogic", "UNICEF"}; forename = forenames[id%3]; surname = surnames[id%4]; organisation = organisations[id%2]; notifications= new ArrayList<>(); notifications.add("You have been promoted!"); notifications.add("Sorry, disregard the previous notifaction- wrong user"); points = id * 31 % 1000; //     friends = new ArrayList<>(); this.id = id; } //      … }
      
      





これが、ユーザーJSONの作成に必要なサービス全体です。 これは私たちが検討している最初のSpring Bootサービスであるため、 .pom



ファイルを調べても害はありません。



 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>user-service</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>user-service</name> <description>Demo user-service with Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.5.RELEASE</version> <relativePath/> <!--      --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.5.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
      
      





idが10のサービスを呼び出すと、次のJSON出力が表示されます。







クライアントライブラリ



このAPIを使用する2つのサービス(通知サービスと個人アカウント)があるとします。 現実的な例では、Userオブジェクトはもっと複雑になり、3つ以上のクライアントを持つことができます。 クライアントライブラリuser-client-libs



と呼ばれる単純なプロジェクトは、単一のクラスで構成されuser-client-libs







 @JsonIgnoreProperties(ignoreUnknown = true) public class UserView { private long id; private String forename; private String surname; private String organisation; private List<String> notifications; private long points; public UserView(){ } public long getId() { return id; } public String getForename() { return forename; } public String getSurname() { return surname; } public String getOrganisation() { return organisation; } public List<String> getNotifications() { return notifications; } public long getPoints() { return points; } }
      
      





ご覧のとおり、このクラスはよりシンプルです。ユーザーのシミュレーションに関連する詳細はなく、元のクラスでは望ましくないと認識されているフレンドリストはありません。 これらの詳細をお客様に非表示にします。 このような軽量の実装では、このAPIが返す可能性のある新しいフィールドも無視されます。 もちろん、現実的な例では、クライアントライブラリがはるかに複雑になることがあります。これにより、画面コードの入力にかかる時間が節約され、フィールド間の関係をよりよく理解できるようになります。



クライアント



この例は、2つの個別のクライアントサービスの実装を示しています。 1つは「ユーザーアカウント」を作成するために必要で、もう1つは「通知リスト」用です。 ユーザーインターフェイスコンポーネントを操作するための特殊なマイクロサービスと見なすことができます。



個人アカウントサービスコントローラーは次のとおりです。



 import com.example.user.dto.UserView; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController public class UserDashboardController { @RequestMapping("/dashboard") public String getUser(@RequestParam(value="id", defaultValue="1") int id) { RestTemplate restTemplate = new RestTemplate(); UserView user = restTemplate.getForObject("http://localhost:9001/user?id="+id, UserView.class); return "USER DASHBOARD <br>" + "Welcome " + user.getForename() +" "+user.getSurname()+"<br>"+ "You have " +user.getPoints() + " points! Good job!<br>"+ "<br>"+ "<br>"+user.getOrganisation(); } }
      
      





そして、これは個人通知サービスのコントローラーです。



 import com.example.user.dto.UserView; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController public class UserNotificationController { @RequestMapping("/notification") public String getUser(@RequestParam(value="id", defaultValue="1") int id) { RestTemplate restTemplate = new RestTemplate(); UserView user = restTemplate.getForObject("http://localhost:9001/user?id="+id, UserView.class); String response = "NOTIFICATIONS"; int number = 1; for(String notification : user.getNotifications()){ response += " Notification number "+(number++)+": "+notification; } return response; } }
      
      





ご覧のとおり、両方のクライアントは非常に単純であり、クライアントとサービス間の接続も簡単です。 もちろん、同時に、両方のサービスの.pomファイルに依存関係を追加する必要があります



 <dependency> <groupId>com.example</groupId> <artifactId>user-client-libs</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
      
      





この例で行うべきことは、ポート9001、9002、9003で3つのサービスをすべて開始して、出力を確認することだけです。



個人アカウント:







通知:







結論



この設計アプローチにより、マイクロサービスアーキテクチャでのコードの再利用に関する問題のほとんどを解決できると思います。 それは理解可能であり、他のアプローチに固有の欠点のほとんどを回避し、開発者の生活を簡素化します。 さらに、これは実際のプロジェクトでテストされたソリューションであり、十分に実証されています。



Spring Bootの例は、このアプローチがいかに便利かを明確に示しています。 さらに、マイクロサービスは見かけよりもはるかに単純であるように見えます。 このプロジェクトについて詳しく知りたい場合は、 Githubで私を見て、開発してみてください。



マイクロサービスの開発で頑張ってください!



PS-翻訳の著者から:



→これは、 Spring Bootに関する本です。

→これが春のマイクロサービスに関する本です



いくつか欲しいですか?



All Articles