マイクロサービスを作ってみよう!
はじめに
前回は、マイクロサービスの概要と、マイクロサービスを実現する際に使われるソフトウェアについて紹介しました。
今回は、具体的なマイクロサービスの開発方法を解説していきます。マイクロサービスは様々なランタイムソフトウェアを使うことで実現できますが、ここではJavaによるマイクロサービス実装で最もポピュラーな「Spring Boot」を使用します。
今回作成するサービス
マイクロサービスではAPIでインタフェースを実装します。一般的にはREST APIがよく使用されますが、RESTで使用されるHTTPメソッドは、次のようにCRUD(作成、読み込み、更新、削除)の操作メソッドに対応させることが望ましいとされています。
API操作 | HTTPメソッド |
---|---|
作成(C) | Post |
読み込み(R) | Get |
更新(U) | Put |
削除(D) | Delete |
今回は、作成(C)に相当する、データベースにデータを登録するシンプルなサービスを例に開発方法を解説していきます。
開発環境の準備
はじめに開発環境を準備します。Spring Bootによるマイクロサービスの開発には、Spring Tools Suite(STS)の使用がおすすめです。STSはJava開発者に親しまれているEclipseベースのIDEで、GUIを使ってSpring Bootの雛形プロジェクトを作成できます。
Spring Tools Suiteはこちらからダウンロードしてください。
プロジェクトの作成
Spring Tools Suiteを起動して、「Springスターター・プロジェクト」を作成します。Springスターター・プロジェクト」を作成すると、Spring Bootで使用できる最低限の雛形ソースやpom.xmlが生成されます。この雛形プロジェクトを開発するサービス用にカスタマイズしていきます。
生成されるpom.xmlには、あらかじめSpringコミュニティが提供するSpring Bootを利用するために必要な基本的な情報が定義されています。SpringコミュニティのSpring Bootではなく、Red Hat Runtimesに含まれるSpring Bootを使用する場合は定義を変更する必要があります。以下のように、モジュールのバージョン情報(BOM)やMavenリポジトリの参照先を変更します。
<properties> … <!-- Spring BootとSpring Boot Mavenプラグインのversionを指定 --> <spring-boot.version>2.1.13.RELEASE</spring-boot.version> <spring-boot-maven-plugin.version>2.1.13.RELEASE</spring-boot-maven-plugin.version> </properties> <dependencyManagement> <dependencies> <!-- Spring Bootのspring-boot-bomを設定 --> <dependency> <groupId>me.snowdrop</groupId> <artifactId>spring-boot-bom</artifactId> <version>2.1.13.Final-redhat-00001</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> … <!-- Spring Bootコンポーネントを含むRed Hatのリポジトリを指定 --> <repositories> <repository> <id>redhat-ga</id> <name>Red Hat GA Repository</name> <url>https://maven.repository.redhat.com/ga/</url> </repository> </repositories> <!-- Spring Boot Mavenプラグインを含むRed Hatのリポジトリを指定 --> <pluginRepositories> <pluginRepository> <id>redhat-ga</id> <name>Red Hat GA Repository</name> <url>https://maven.repository.redhat.com/ga/</url> </pluginRepository> </pluginRepositories>
次に、Spring Bootで開発したサービスをビルドするための設定をします。pom.xmlのSpring Boot Mavenプラグインにrepackageゴールを設定し、ビルド時に既存のJARが再パッケージ化されるようにします。
<build> … <plugins> … <!-- spring-boot-maven-pluginをプラグインに追加 --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>${spring-boot-maven-plugin.version}</version> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> <configuration> <redeploy>true</redeploy> </configuration> </plugin> … </plugins> … </build>
最後にpom.xmlを編集し、サービスで利用するモジュールの依存関係を追加します。マイクロサービスでは、APIを持ったサービスを開発する必要があるため、最低限Spring MVCを利用するためのspring-boot-starter-webを追加する必要があります。
それ以外のモジュールは、プログラムで利用するものを必要に応じて追加します。作成するサービスではLombok、Spring Data JPA、ModelMapperを利用するので、これらのモジュールの依存関係を追加します。
<dependencies> <!-- spring-boot-starter-webの依存関係を設定 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Lombokの依存関係を設定 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> <!-- Spring Data JPAの依存関係を設定 --> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-jpa</artifactId> </dependency> <!-- ModelMapperの依存関係を設定 --> <dependency> <groupId>org.modelmapper</groupId> <artifactId>modelmapper</artifactId> <version>2.3.0</version> </dependency> … </dependencies>
サービスの作成
開発環境が準備できたので、サービスを作成していきましょう。サービスはSpring MVCで実装します。もともとSpring MVCはWebアプリケーションをModel層、View層、Controller層の階層で実装するためのフレームワークですが、このフレームワークのModel層、Controller層をサービスの実装に使います。ただし、APIをインタフェースとするためView層は実装せず、代わりにRestControllerを使ってREST APIを実装します。
作成するサービスの構成は下図の通りです。
Model層の実装
APIのリクエスト/レスポンスに対応するModelやService層内で利用するModelをPOJOで実装します。なお、今回のサービスではPOJOの実装にLombokを利用し、実装をシンプルにしています。
APIのリクエストに対応するModelを作成します。APIの電文であるJSONとModelの変換は暗黙的にSpring Bootで行われますが、フィールドにJacksonアノテーション(@JsonPropertyなど)を付与することで、このJSONへの変換時のマッピングや挙動をカスタマイズできます。
@Data public class RequestResource { @JsonProperty("user_id") private Integer userId; @Size(min = 1, max = 255) @JsonProperty("user_name") private String userName; @JsonProperty("store_id") private Integer storeId; @Size(min = 1, max = 255) @JsonProperty("store_name") private String storeName; }
このサンプルコードでは@JsonPropertyを指定して、Modelに対応する電文のプロパティ名を変更しています。同様に、APIのレスポンスに対応するModelを作成します。
@Data @JsonPropertyOrder({ "user_id", "error_code", "error_message" }) @JsonInclude(JsonInclude.Include.NON_NULL) public class ResponseResource { @Size(min = Integer.MIN_VALUE, max = Integer.MAX_VALUE) @JsonProperty("user_id") private Integer id; @Size(min = Integer.MIN_VALUE, max = Integer.MAX_VALUE) @JsonProperty("error_code") private Integer errorCode; @NotNull @Size(min = 0, max = 255) @JsonProperty("error_message") private String errorMessage; }
Service層のインタフェースで利用するModelも同様に実装します。Controller層からService層へのリクエストのモデルを作成します。
@Data public class ServiceRequest { private Integer userId; private String userName; }
同様に、Service層からController層へのレスポンスのモデルを作成します。
@Data public class ServiceResponse { private Integer errorCode; private String errorMessage; private Integer id; }
Controller層の実装
プロジェクトにController層の実装クラスを追加し、APIのリクエストやレスポンスをハンドリングする処理を実装します。
REST APIの実装にはRestControllerを使用します。RestControllerではJSONファイルとModelの変換などのREST APIの実装に必要な細かな部分を隠ぺいしており、簡単にAPIを実装できます。RestControllerを使うクラスには@RestControllerを設定します。
/* @RestControllerの設定 */ @RestController @RequestMapping("/api/sample/apply") public class SampleController { … }
実際の業務処理をController層に実装していきます。実装方法は、基本的に通常のSpring Bootを使ったWebアプリケーションのController層の実装方法とほとんど変わりません。Controller層では業務処理そのものは実装せず、Service層のクラスを新たに作成し、そのクラスに処理を委譲するようにします。
/* POSTメソッドのオペレーションを@PostMappingアノテーションで実装 */ @PostMapping @ResponseStatus(HttpStatus.CREATED) ResponseResource insertSampleEntity(@Validated @RequestBody RequestResource requestResource) { try { /* モデルの変換 */ ServiceRequest serviceRequest = modelMapper.map(requestResource, ServiceRequest.class); /* Service層の呼び出し */ ServiceResponse serviceResponse = sampleService.apply(serviceRequest); /* モデルの変換 */ return modelMapper.map(serviceResponse, ResponseResource.class); } catch (Exception e) { /* エラー処理 */ ResponseResource responseResource = new ResponseResource(); if(e instanceof SampleServiceException) { responseResource.setErrorCode(ServiceResponse.SERVICE_ERROR); } else { responseResource.setErrorCode(ResponseResource.CONTROLLER_ERROR); } return responseResource; } }
業務を実装するService層のクラスを新たに作成し、業務処理の実態はService層のクラスに実装します。
/* @Serviceの設定 */ @Service public class SampleService { … @Transactional(rollbackFor = Exception.class) public ServiceResponse apply(ServiceRequest serviceRequest) throws SampleServiceException { try { /* モデルの変換 */ SampleEntity sampleEntity = modelMapper.map(serviceRequest, SampleEntity.class); /* DBの書き込み */ sampleEntity = sampleRepository.save(sampleEntity); /* モデルの変換 */ return modelMapper.map(sampleEntity, ServiceResponse.class); } catch (Exception e) { /* エラー処理 */ SampleServiceException ex = new SampleServiceException(); ex.setErrorCode(ServiceResponse.SERVICE_ERROR); throw ex; } } }
サービスの稼働
これで、サービスの開発は終了です。早速、開発したサービスをコンテナ上で稼働させてみましょう。
前回でも紹介したように、コンテナ管理基盤ソフトウェアにはKubernetesやOpenShiftがあります。通常、コンテナ上にサービスをデプロイするには、コンテナ管理基盤ソフトウェアの専用コマンド(例えばOpenShiftならocコマンド)を使う必要がありますが、fabric8を使うことで専用コマンドを意識せずにデプロイできます。
今回は、fabric8を使ってOpenShift上でサービスを稼働させる手順を紹介します。
fabric8を使うための準備
Mavenからfabric8を使用するには、プロジェクトにfabric8 Mavenプラグインを組み込む必要があります。プロジェクトのpom.xmlにfabric8-maven-pluginのプラグイン設定を追加します。
<properties> … <!-- fabric8 Maven プラグインの設定追加 --> <fabric8-maven-plugin.version>4.4.1</fabric8-maven-plugin.version> </properties> <build> <pluginManagement> <plugins> … <!-- fabric8 Maven プラグインの設定追加 --> <plugin> <groupId>io.fabric8</groupId> <artifactId>fabric8-maven-plugin</artifactId> <version>${fabric8-maven-plugin.version}</version> </plugin> </plugins> </pluginManagement> </build>
次に、pom.xmlにOpenShift用のプロファイルを追加します。
<!-- プロファイル追加 --> <profiles> <profile> <id>openshift</id> <build> <plugins> <plugin> <groupId>io.fabric8</groupId> <artifactId>fabric8-maven-plugin</artifactId> <executions> <execution> <id>fmp</id> <goals> <goal>resource</goal> <goal>build</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </profile> </profiles>
デプロイのための設定
サービスは、OSなどのソフトウェアがあらかじめ構築されたベースのコンテナイメージに組み込んでデプロイされます。サービスを組み込むコンテナイメージをpom.xmlで指定します。OpenShiftではOSとJDKがあらかじめコンテナイメージで提供されているため、そのコンテナイメージを指定します。
<properties> … <!-- コンテナイメージの設定追加 --> <fabric8.generator.from>registry.access.redhat.com/redhat-openjdk-18/openjdk18-openshift:latest</fabric8.generator.from> … </properties>
OpenShiftでは、コンテナイメージ以外にもデプロイ設定ファイルやルート設定ファイルなど、複数の設定ファイルを設定する必要があります。fabric8では、これらの設定ファイルを自動生成してくれるため、その設定ファイルで問題がなければ特にファイルを作成する必要はありません。自動生成ファイルではなく、カスタマイズした設定ファイルを使用したい場合はプロジェクトのsrc/main/fabric8ディレクトリ下にファイルを配置します。
sample/
│─src/
│ │─main/
│ │ │─fabric8/
│ │ │ │─sample-deployment.yml
│ │ │ │─sample-route.yml
│ │ │ └─sample-service.yml
サービスのデプロイ
それでは、サービスをデプロイしてみましょう。今回はOpenShiftにサービスをデプロイします。fabric8はOpenShiftの専用コマンドを隠ぺいしてデプロイを実行してくれますが、前提としてOpenShiftプロジェクトにログインした状態で実行する必要があります。OpenShiftプロジェクトを作成した上で、mvnコマンドを使ってクリーンデプロイを実行します。
oc new-project <OpenShiftプロジェクト名> mvn clean fabric8:deploy -P<プロファイル名>
プロファイル名にはpom.xmlに定義したプロファイル名を指定します。デプロイが完了すると、OpenShift上にサービスのコンテナ(Pod)が起動します。
起動したコンテナはoc get podsコマンドで確認します。Podの名前はサービスを開発した際に「Spring スターター・プロジェクト」に付けたプロジェクト名が先頭に付きます。対応するPodが存在するかとSTATUSがRunningになっているかを確認します。
oc get pods NAME READY STATUS RESTARTS AGE sample-10-7pzs8 1/1 Running 0 117m …
サービスの実行
デプロイもできたので、いよいよサービスを実行してみましょう。サービスのREST APIを呼び出す方法は様々ありますが、単純な動作確認のための実行であればcurlコマンドを使う方法がおすすめです。
今回作成したサービスのAPIはHTTP POSTで実現しているため、APIのURLにHTTP POSTリクエストを送信します。送信するデータには、APIの入力電文に相当するJSON形式の文字列を指定します。
curl http://sample-sample-project.apps.xxxx-xxxxx-cluster.xxxxx.xxxxx.com/api/sample/apply \ -v -X POST -H "Content-Type:application/json" -d "{\"user_name\":\"Taro\", \"store_id\":1111, \"store_name\":\"Shop_A\"}" …
これでサービスが実行され、JSON形式の応答電文が出力されます。
{"user_id":1}
おわりに
今回はSpring Bootを使用してマイクロサービスを開発する基本的な手順を説明しました。今回は単独のサービスを作成しましたが、実際にマイクロサービスを活用したシステムでは複数のサービスを開発し、それらを連携させる必要があります。次回は、サービスのAPI公開やサービス間連携のやり方を紹介します。