このガイドでは、マイクロサービス発見サービスであるNetflix Eurekaを利用して、Spring BootマイクロサービスとFlaskマイクロサービスを組み合わせ、全く異なるプログラミング言語とフレームワークで書かれたサービスの橋渡しをします。
エンドユーザ向けサービスは、エンドユーザ向けのSpring Bootサービスで、データを収集し、データアグリゲーションサービスに送信します。
ネットフリックス エウレカセリフディスカバリー
モノリスコードベースからマイクロサービス指向アーキテクチャに切り替えたとき、Netflix はアーキテクチャ全体をオーバーホールするのに役立つ多くのツールを構築しました。
そのうちの1つが、後に一般公開された「Eureka」です。
Netflix Eurekaは、サービス発見ツール(ルックアップサーバーまたはサービスレジストリとも呼ばれる)で、複数のマイクロサービスを登録し、それらの間のリクエストルーティングを処理することができる。
各サービスが登録される中心的なハブであり、各サービスはハブを介して他のサービスと通信する。
ホスト名やポートを使ってRESTコールを送るのではなく、Eurekaに委ね、ハブに登録されたサービス名を呼び出すだけでいいのです。
これを実現するために、典型的なアーキテクチャはいくつかの要素で構成されている。
Eureka Serverは、Eurekaラッパーがあればどの言語でもスピンオフできますが、最も自然なのはJavaで、Spring Bootを通じて行うことです。
各Eureka ServerはN個のEureka Clientsを登録することができ、各Eureka Clientsは通常個別のプロジェクトとなる。
Eureka Serverは、N個のEureka Clientを登録することができます。
ここでは、2つのクライアントを用意します。
- エンドユーザー・サービス(JavaベースのEurekaクライアント)
- データアグリゲーションサービス(PythonベースのEurekaクライアント)
EurekaはJavaベースのプロジェクトで、元々はSpring Bootソリューションのためのものなので、Python用の公式な実装はありません。
しかし、コミュニティ主導のPythonラッパーを利用することができます。
- Netflix Eureka
- PythonのEurekaクライアント
これを踏まえて、まずはEureka Serverを作成しましょう。
Eurekaサーバーの作成
Spring Boot を使って Eureka Server を作成・管理します。
まず、3つのプロジェクトを格納するディレクトリを作成し、その中にサーバー用のディレクトリを作成します。
$ mkdir eureka-microservices
$ cd eureka-microservices
$ mkdir eureka-server
$ cd eureka-server
eureka-server` ディレクトリが Eureka Server のルートディレクトリになります。
ここで、Spring BootのプロジェクトをCLIで起動します。
$ spring init -d=spring-cloud-starter-eureka-server
また、Spring Initializrを使用して、Eureka Serverの依存関係をインクルードすることもできます。
すでにプロジェクトがあり、新しい依存関係をインクルードしたいだけであれば、Mavenを使用している場合、追加します。
<dependency
<groupidorg.springframework.cloud</groupid
<artifactidspring-cloud-starter-eureka-server</artifactid
<version${version}</version
</dependency
また、Gradleを使っている場合は、次のようにします。
implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-eureka-server', version: ${version}
初期化のタイプに関わらず、Eureka Serverはサーバーとしてマークされるために、1つのアノテーションを必要とします。
EndUserApplicationファイルクラス (
@SpringBootApplicationアノテーションのエントリーポイント) に、
@EnableEurekaServer` を追加してください。
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
Eureka Servers のデフォルトポートは 8761
で、Spring Team もこれを推奨しています。
ただし、念のため application.properties
ファイルにも設定しておきましょう。
server.port=8761
これで、サーバーを動かす準備ができました。
このプロジェクトを実行すると、localhost:8761
にあるEureka Serverが起動します。
Note: サービスを登録しない場合、Eureka は UNKNOWN インスタンスが稼働していると誤認することがあります。
Spring BootでEurekaクライアント – エンドユーザーサービスを作成する
さて、サーバが起動し、サービスを登録できるようになったところで、Spring Bootでエンドユーザー・サービスを作ってみましょう。
Studentに関するJSONデータを受け取るエンドポイントを1つ用意します。
このデータは、成績の一般的な統計情報を計算するデータ集計サービスにJSONとして送信されます。
実際には、この操作はもっと手間のかかる操作に置き換えられ、専用のデータ処理ライブラリで行うのが理にかなっており、同じサービスで実行するよりも別のサービスを使用することが正当化されます。
ということで、戻ってEnd-User Serviceのためのディレクトリを作成しましょう。
$ cd..
$ mkdir end-user-service
$ cd end-user-service
ここで、CLIを使って新しいプロジェクトを開始し、spring-cloud-starter-netflix-eureka-client
依存関係を含めます。
また、このアプリケーションは実際にユーザーと対面することになるので、web
依存関係も追加します。
$ spring init -d=web, spring-cloud-starter-netflix-eureka-client
また、Spring Initializrを使用して、Eureka Discovery Clientの依存関係を含めることもできます。
すでにプロジェクトがあり、新しい依存関係をインクルードしたいだけであれば、Mavenを使用している場合、追加します。
<dependency
<groupidorg.springframework.cloud</groupid
<artifactidspring-cloud-starter-netflix-eureka-client</artifactid
<version${version}</version
</dependency
または、Gradleを使用している場合は、以下を追加します。
implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-netflix-eureka-client', version: ${version}
初期化のタイプに関係なく、このアプリケーションをEureka Clientとしてマークするには、メインクラスに @EnableEurekaClient
アノテーションを追加するだけです。
@SpringBootApplication
@EnableEurekaClient
public class EndUserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(EndUserServiceApplication.class, args);
}
@LoadBalanced
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
}
注:別の方法として、@EnableDiscoveryClient
アノテーションを使用することもできます。
これは、Eureka、Consul、またはZookeperを参照することができ、どのツールが使用されているかに依存する。
また、ここでは @Bean
を定義しているので、後でコントローラの中で RestTemplate
を @Autowire
することができます。
この RestTemplate
は、データ集計サービスに POST
リクエストを送信するために使用される。
LoadBalancedアノテーションは、
RestTeamplateがリクエストを送信する際に
RibbonLoadBalancerClient` を使用することを意味します。
このアプリケーションはEurekaクライアントなので、レジストリの名前を付けたいと思います。
他のサービスはこの名前を参照することになります。
この名前は application.properties
または application.yml
ファイルで定義します。
server.port = 8060
spring.application.name = end-user-service
eureka.client.serviceUrl.defaultZone = http://localhost:8761/eureka
server:
port: 8060
spring:
application:
name: end-user-service
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
ここで、アプリケーションのポートを設定しました。
Eurekaがリクエストをルーティングするために必要なポートです。
また、他のサービスから参照されるサービス名も指定しました。
このアプリケーションを実行することで、Eureka Server にサービスが登録されます。
INFO 3220 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8060 (http) with context path ''
INFO 3220 --- [ main] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 8060
INFO 3220 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_END-USER-SERVICE/DESKTOP-8HAKM3G:end-user-service:8060 - registration status: 204
INFO 3220 --- [ main] c.m.e.EndUserServiceApplication : Started EndUserServiceApplication in 1.978 seconds (JVM running for 2.276)
INFO 3220 --- [tbeatExecutor-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_END-USER-SERVICE/DESKTOP-8HAKM3G:end-user-service:8060 - Re-registering apps/END-USER-SERVICE
INFO 3220 --- [tbeatExecutor-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_END-USER-SERVICE/DESKTOP-8HAKM3G:end-user-service:8060: registering service...
INFO 3220 --- [tbeatExecutor-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_END-USER-SERVICE/DESKTOP-8HAKM3G:end-user-service:8060 - registration status: 204
これで、localhost:8761
にアクセスすると、サーバーに登録されていることが確認できます。
では、Student
モデルを定義してみましょう。
public class Student {
private String name;
private double mathGrade;
private double englishGrade;
private double historyGrade;
private double scienceGrade;
// Constructor, getters and setters and toString()
}
生徒について、成績の平均、最小、最大などの要約統計量を計算したいと思います。
これには Pandas を使用します – とても便利な DataFrame.describe()
関数を利用します。
また、データ集計サービスから返されたデータを保持する GradesResult
モデルも作成しましょう。
public class GradesResult {
private Map<string, double="" mathGrade;
private Map<string, double="" englishGrade;
private Map<string, double="" historyGrade;
private Map<string, double="" scienceGrade;
// Constructor, getters, setters and toString()
}
このモデルは POST
リクエストを受け取り、それを Student
にデシリアライズして、まだ作成していないデータ集計サービスに送ります。
@Autowired
private RestTemplate restTemplate;
@RestController
public class HomeController {
@PostMapping("/student")
public ResponseEntity<string student(@RequestBody Student student) {
GradesResult grades = restTemplate.getForObject("http://data-aggregation-service/calculateGrades", GradesResult.class);
return ResponseEntity
.status(HttpStatus.OK)
.body(String.format("Sent the Student to the Data Aggregation Service: %s
And got back:
%s", student.toString(), gradesResult.toString()));
}
}
この @RestController
は POST
リクエストを受け取り、そのボディを Student
オブジェクトにデシリアライズします。
これはまだ実装されておらず、Eureka に登録される予定です。
そして、その呼び出しの JSON 結果を GradesResult
オブジェクトにパックします。
注:シリアライザーが与えられた結果から GradesResult
オブジェクトを構築する際に問題がある場合、Jackson の ObjectMapper
を使用して手動で変換する必要があります。
String result = restTemplate.postForObject("http://data-aggregation-service/calculateGrades", student, String.class);
ObjectMapper objectMapper = new ObjectMapper();
GradesResult gradesResult = objectMapper.readValue(result, GradesResult.class);
最後に、送信した student
インスタンスと、結果から構築した grades
インスタンスを表示します。
それでは、データ集計サービスを作成してみましょう。
Eurekaクライアントの作成 – Flaskによるデータ集計サービス
StudentをJSON形式で受け取り、Pandasの DataFrame
に入力し、特定の処理を実行して結果を返す、データ集計サービスだけが必要なコンポーネントです。
プロジェクト用のディレクトリを作成し、仮想環境を立ち上げましょう。
$ cd..
$ mkdir data-aggregation-service
$ python3 -m venv flask-microservice
次に、仮想環境を有効にするために、activate
ファイルを実行します。
$ flask-microservice/Scripts/activate.bat
Linux/Macの場合
$ source flask-microservice/bin/activate
今回は簡単なFlaskアプリケーションを起動するので、FlaskとEurekaの依存関係を pip
経由で起動した環境にインストールしましょう。
(flask-microservice) $ pip install flask pandas py-eureka-client
そして、Flaskアプリケーションを作成します。
$ touch flask_app.py
次に、flask_app.py
を開いて、Flask、Pandas、Py-Eureka Client のライブラリをインポートします。
from flask import Flask, request
import pandas as pd
import py_eureka_client.eureka_client as eureka_client
Flask と request
を使って、受信したリクエストを処理し、レスポンスを返し、サーバーを起動します。
Pandasを使ってデータを集約し、py_eureka_client
を使ってFlaskアプリケーションを localhost:8761
上のEurekaサーバーに登録します。
それでは、このアプリケーションをEurekaクライアントとして設定し、生徒のデータを取得するための POST
リクエストハンドラを実装してみましょう。
rest_port = 8050
eureka_client.init(eureka_server="http://localhost:8761/eureka",
app_name="data-aggregation-service",
instance_port=rest_port)
app = Flask(__name__)
@app.route("/calculateGrades", methods=['POST'])
def hello():
data = request.json
df = pd.DataFrame(data, index=[0])
response = df.describe().to_json()
return response
if __name__ == "__main__":
app.run(host='0.0.0.0', port = rest_port)
Note: Flask が外部サービスとの接続を拒否しないように、ホストを 0.0.0.0
に設定する必要があります。
これは、@app.route()
が1つあるだけの、かなり最小限のFlaskアプリです。
受信した POST
リクエストの本文を request.json
で data
辞書に抽出し、そのデータで DataFrame
を作成しました。
この辞書はインデックスを持たないので、手動でインデックスを設定します。
最後に、describe()
関数の結果をJSONとして返します。
jsonifyは String ではなく、
Responseオブジェクトを返すので、ここでは使用していません。
Response オブジェクトを送り返すと、余分な `Caracter
が含まれます。
{"mathGrade":...}
vs
{"mathGrade":...}
これらは、デシリアライザーを混乱させないように、エスケープする必要があります。
eureka_clientの
init()` 関数では、Eureka Server の URL を設定し、アプリケーション/サービスの名前とアクセスするためのポートを設定しました。
これは、Spring Bootアプリケーションで提供した情報と同じものです。
では、このFlaskアプリケーションを実行してみましょう。
(flask-microservice) $ python flask_app.py
そして、localhost:8761
にあるEureka Serverを確認すると、登録されており、リクエストを受信できる状態になっています。
Eurekaを使ってSpring Boot ServiceからFlask Serviceを呼び出す
Eurekaに登録され、お互いに通信できるようになったので、エンドユーザー・サービスに POST
リクエストを送りましょう。
$ curl -X POST -H "Content-type: application/json" -d "{"name" : "David", "mathGrade" : "8", "englishGrade" : "10", "historyGrade" : "7", "scienceGrade" : "10"}" "http://localhost:8060/student"
この結果、サーバーからエンドユーザーへの応答が返されます。
Sent the Student to the Data Aggregation Service: Student{name='David', mathGrade=8.0, englishGrade=10.0, historyGrade=7.0, scienceGrade=10.0}
And got back:
GradesResult{mathGrade={count=1.0, mean=8.0, std=null, min=8.0, 25%=8.0, 50%=8.0, 75%=8.0, max=8.0}, englishGrade={count=1.0, mean=10.0, std=null, min=10.0, 25%=10.0, 50%=10.0, 75%=10.0, max=10.0}, historyGrade={count=1.0, mean=7.0, std=null, min=7.0, 25%=7.0, 50%=7.0, 75%=7.0, max=7.0}, scienceGrade={count=1.0, mean=10.0, std=null, min=10.0, 25%=10.0, 50%=10.0, 75%=10.0, max=10.0}}
結論
このガイドでは、あるサービスが別のサービスに依存しているマイクロサービス環境を作成し、Netflix Eureka を使用してそれらをフックアップしてきました。
これらのサービスは、異なるフレームワークと異なるプログラミング言語を使って構築されていますが、REST APIを通じて、これらのサービス間の通信は簡単で容易です。
Eureka Serverを含むこれら2つのサービスのソースコードは、Githubで公開されています。
,>,>,>,>。