ほとんどのマイクロサービスアーキテクチャでは、コードを共有する機会が多くあります。したがって、これを行う誘惑は素晴らしいものです。 この記事では、私自身の経験を共有します。コードを再利用するのが適切な場合と、回避する方が良い場合について説明します。 すべてのポイントは、Githubで入手可能な Spring Bootを使用した特別なプロジェクトの例で説明します 。
はじめに
コード共有とその背後にあるものについて話す前に、マイクロサービスアーキテクチャを使用して通常どのタスクが解決されるかを判断します。 マイクロサービスを実装する主な利点は次のとおりです。
- スケーリングの改善-アプリケーションのさまざまな部分が独立してスケーリングする
- システムのさまざまな部分間の強力な接続を効果的に削除することは常に望ましいことですが、マイクロサービスを使用するのが最適です。
- システムの信頼性が向上します-1つのサービスが失敗しても、他のサービスは動作し続けます。
- テクノロジーの選択の自由-各サービスは、この場合に最適なテクノロジーを使用して実装できます。
- コンポーネントの再利用性の向上-サービス(既にデプロイされているものも含む)を異なるプロジェクトで共有できます
- 特定のアーキテクチャまたは解決する問題に応じて、他にも多くの利点があります。
当然、これらの利点の多くは、より良いシステムを構築するだけでなく、開発者の生活を促進し、彼の仕事をより感謝することも可能にします。 もちろん、好きなだけ議論することができますので、マイクロサービスが有用であることに同意しましょう(NetflixやNginxなどの大企業の経験によって確認されています)。 他のアーキテクチャと同様に、マイクロサービスには、克服する必要のある独自の欠点と困難があります。 最も重要なのは:
- 展開の複雑さの増加-展開プロセスは1つまたは複数の段階ではなく、数十段階以上で構成されます
- より多くの統合コード-多くの場合、サービスは互いに情報を交換する必要があります。 正しく整理する方法は、別の記事を書く価値があります
- このサブジェクトエリアで動作するには、分散システムでコードをアクティブにコピーする必要がありますか?
問題点
そのため、ここでは、ほとんどのチームがマイクロサービスを使用し始めているという問題に取り組みます。 マイクロサービスを使用する目的と、その実装に推奨される手法を考慮すると、次の問題に直面します。「弱く接続されたサービスが必要です。 したがって、サービスを利用するたびに、応答を処理するクラスを作成する必要があります。 しかし、DRY原則(Do Not Repeat)はどうでしょうか? どうする?」 この場合、2つのアンチパターンを簡単にヒットできます。
- サービスを互いに依存させましょう! つまり、これは弱いバインディングを忘れることができることを意味し(ここでは絶対に達成できません)、テクノロジの選択の自由も失われます。ロジックがコード全体に散らばり、サブジェクト領域が過度に複雑になります。
- コードをコピーして貼り付けましょう! これはそれほど悪いことではありません。少なくとも、弱いバインディングを維持でき、ドメインがロジックで過飽和になるのを防ぐことができるからです。 クライアントはサービスコードに依存できません。 ただし、正直になります。 この下劣なユーザーサービスを使用する予定があるときはいつでも、どこにでも同じクラスをコピーアンドペーストし、大量のスクリーン印刷されたコードを書きたくはありません。 寿司コードの原則は、理由のためのマントラになっています!
解決策
アーキテクチャの目的と問題の説明方法を明確に明確にすると、解決策が示唆されるようです。 サービスコードを完全に独立させる必要があるが、クライアントでかなり複雑な応答を使用する必要がある場合、クライアントはこのサービスを使用するために独自のライブラリを作成する必要があります。
このアプローチには次の利点があります。
- サービスはクライアントから完全に分離されており、特定のサービスは互いに独立しています。ライブラリは自律的でクライアント固有です。 一度に複数のテクノロジーを使用すれば、特定のテクノロジーに対してさらに研ぎ澄まされます。
- 新しいクライアントバージョンのリリースは、クライアントに依存しません。 後方互換性がある場合、クライアントはリリースに気付かない場合があります。これは、ライブラリサポートを提供するのはクライアントであるためです。
- 現在、クライアントは乾燥しています-冗長コードはコピーされません
- サービスとの統合は加速されますが、同時に、マイクロサービスアーキテクチャの利点は失われません。
このソリューションは完全に新しいものとは言えません-Sam Newmanの著書「 Creating Microservices 」で説明されているようなアプローチです(強くお勧めします)。 これらのアイデアの具体化は、多くの成功したマイクロサービスアーキテクチャに見られます。 この記事は主にサブジェクト領域でのコードの再利用に焦点を当てていますが、ここで説明する原則と矛盾しないため、一般的な接続性と情報交換を提供するコードにも同様の原則が当てはまります。
別の質問も考えられます。ドメインオブジェクトのリンクとクライアントライブラリとの接続について心配する価値はありますか。 主な質問に対する答えと同様に、この場合の最も重要な要因は、アーキテクチャ全体に対するそのような詳細の影響です。 クライアントライブラリに接続コードを含めると生産性が向上すると判断した場合、クライアントサービス間に強力なリンケージがないことを確認する必要があります。 このようなアーキテクチャの接続は通常、単純なREST呼び出しまたはメッセージキューを使用して提供されるため、このようなコードをクライアントライブラリに配置することはお勧めしません。 接続のコードに特別なものや複雑すぎるものがある場合(たとえば、SOAP要求を実行するためのクライアント証明書)、追加のライブラリを前に添付することをお勧めします。 このパスを選択する場合、クライアントライブラリの使用をオプションとして指定し、必須ではありません。 クライアントサービスは、コードを完全に所有する必要はありません(サービスプロバイダーが対応するクライアントライブラリを確実に更新することを義務付けることは不可能です)。
スプリングブートの例
![](https://habrastorage.org/files/637/fa8/ec1/637fa8ec1a8343e4bd5d5cae0bd48a11.png)
それで、私は解決策を説明し、今度はコードでそれを実証します。 ところで、私のお気に入りのマイクロサービスライブラリである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出力が表示されます。
![](https://habrastorage.org/files/064/2ee/d2f/0642eed2ff4e4e9a86913ef58b964346.png)
クライアントライブラリ
この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つのサービスをすべて開始して、出力を確認することだけです。
個人アカウント:
![](https://habrastorage.org/files/200/4c7/b7d/2004c7b7d1e440b7a07f383d01cac2dd.png)
通知:
![](https://habrastorage.org/files/1cd/16a/f24/1cd16af24cc548118141f03e83847e3f.png)
結論
この設計アプローチにより、マイクロサービスアーキテクチャでのコードの再利用に関する問題のほとんどを解決できると思います。 それは理解可能であり、他のアプローチに固有の欠点のほとんどを回避し、開発者の生活を簡素化します。 さらに、これは実際のプロジェクトでテストされたソリューションであり、十分に実証されています。
Spring Bootの例は、このアプローチがいかに便利かを明確に示しています。 さらに、マイクロサービスは見かけよりもはるかに単純であるように見えます。 このプロジェクトについて詳しく知りたい場合は、 Githubで私を見て、開発してみてください。
マイクロサービスの開発で頑張ってください!
PS-翻訳の著者から:
→これは、 Spring Bootに関する本です。
→これが春のマイクロサービスに関する本です
いくつか欲しいですか?