見出し画像

【モジュール化されたシステム構築戦略】三つのポイント!(Actuator, Swagger, MicroService)Springboot3 第3回



はじめに


こんにちは! 今日はSpringbootでモジュール化されたシステムを構築するために使用される3つの戦略を勉強したいと思います。 1つ目はモニタリングのためのスプリングアクチュエータ、2つ目はAPI管理のための文書化であるOpenAPI Swagger、3つ目は単位サービスを細かく分けて独立性を維持するアーキテクチャであるマイクロサービスです。 3つの概念ともモダンな開発方式で多く使われており、特にマイクロサービスは大規模なサービスでよく使われるアーキテクチャです。

これまで小さなウェブアプリケーションだけを作ってみて、拡張性による大規模なシステムを考慮したことはありませんでしたが、どうやらシステムの規模が大きいと各モジュールに対するモニタリング、API文書化が必須のようです。 そうでなければ、一体その大きなシステムをどのように管理してメンテナンスするのか、見当がつきませんからね。 そのような観点から、マイクロサービスは大規模なシステム管理のためのアイデアの集合体だと考えればいいと思います。 上記の2つの方法は、個別ツールといえば、マイクロサービスは個別サービスを独立して設定する「アーキテクチャ」です。

【ポイント1】スプリングアクチュエータ(Spring Boot Actuator)


スプリングブート(Spring Boot)プロジェクトが提供する拡張機能の一つで、アプリケーションのランタイム状態と多様なメトリック(metric)およびモニタリング情報を簡単に露出·管理できる機能を提供します。 これにより、開発者と運営チームはアプリケーションの動作状態をリアルタイムで監視し、問題を診断するのに役立ちます。

スプリング アクチュエータは、アプリケーションの開発、配布、監視に大きく役立ち、アプリケーションの健康状態とパフォーマンスを把握するために不可欠な情報を簡単に入手できます。 スプリング ブート プロジェクトの開始時にデフォルトで含まれており、必要に応じてカスタマイズして使用できます。

百回聞くより一度作ってみた方が理解が早いでしょう! さあ、作ってみましょう!



1. 依存性の追加

<pom.xml>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>


依存性を追加してアプリケーションを実行すると、/actuatorでできたエンドポイントが1つあります。

<application.properties>

management.endpoints.web.exposure.include=*


設定値を入力して皆歌ってきてみましょう。

その後、アプリケーションを再び実行してみると以前に露出されたエンドポイントが1つだったのとは違い、13個に変わりましたね。


2. Actuatorのエンドポイントの種類


/actuator

基本的にどのようなエンドポイントがあるか全体的に見せてくれます。


/info

アプリケーションに関する追加情報を提供するエンドポイントです。 開発者が希望する追加情報を含め、カスタム情報を表示できます。

http://localhost:8080/actuator/info

<application.properties>

management.endpoints.web.exposure.include=*
management.info.env.enabled=true

info.app.name=Spring Boot Restful Web Services
info.app.description=Spring Boot Restful Web Services Demo
info.app.version=1.0.0

上の設定価格をさらに追加します。


/health

アプリケーションの健康状態を確認できるエンドポイントです。 このエンドポイントを通じて、アプリケーションの状態が「UP」または「DOWN」かどうかを確認することができ、ロードバランサーなどのインフラストラクチャで使用してアプリケーションの可用性を確認するのに便利です。


<pom.xml>

management.endpoint.health.show-details=always

設定値を追加します。


ステータスがUPのものが見えます。



/beans

スプリングアプリケーションコンテキストに登録されたビーン(bean)リストを確認できるエンドポイントです。アプリケーションのビーン構成を理解してデバッグするのに役立ちます。

/conditions

一般的に、特定の条件や状況に関する情報を提供するエンドポイントです。主にクライアントアプリケーションがサーバーに特定の条件に関する情報を要求し、サーバーはこれらの要求に対する応答を提供します。



/mappings

アプリケーションに宣言されたすべての @RequestMapping パスを表示します。どのコントローラメソッドによってどのリクエストパスが管理されているかを確認するのに便利です。


/configprops

@ConfigurationProperties , application.properties または YAML ファイルで定義されたすべての設定情報を提供します。


/metrics

メモリの空き容量、heapのサイズ、使用中のスレッドの数など、現在のアプリケーションのmetricsを表示します。

/env

ConfigurableEnvironment インターフェース(acitve profiles のリスト、applicaton properties、システム環境変数など)のすべての設定情報を表示します。


/threaddump

JVMスタックトレースと実行中のスレッドの詳細と一緒にアプリケーションのスレッドダンプを表示します。

Thread dumpとは、マルチスレッド環境で実行中のプログラムのスレッド状態と実行情報をスナップショット形式でキャプチャしたものを意味します。通常、デバッグ(デッドロックのようなマルチスレッドの問題に直面した時のスレッドダンプ分析利用)、パフォーマンス問題の解決(CPU使用率が高いスレッド、待機中のスレッド識別)、システムモニタリングのために使用されます。スレッドダンプを生成するためにはjstack, kill-3コマンドを使ってJavaアプリのスレッドダンプを生成することができます。


/loggers

実行中のアプリケーションのログレベルを確認して設定することができます。

ログレベルの確認方法

/loggers/{name}

ex) /actuator/loggers/net.javaguides.springboot


参考までによく設定されるログレベルはERROR, INFO, DEBUGです。サーバーで障害対応時にログレベルをDEBUGに変更した後、作業が終わった後、忘れてERRORや他のログレベルに戻さずに作業場を離れることがあります。このような場合、ログファイルの容量が大きくなり、ストレージ容量が不足することがあるので注意しましょう。


Postmanを使ってログレベルをINFOからDEBUGに変えてみました。

まず、Http MethodをPOSTに設定し、URLを/loggers/{name}に設定します。を設定し、リクエストのBODYをJSONに設定し、"configuredLevel"を変更します。すると、204コードが確認できます。204コードはリクエストは成功しましたが、何も応答を送らないことを意味します。

Spring Actuatorで確認すると、configuredLevelがDEBUGになっていることが確認できます。

もう一つやってみましょう。_org.springframeworkを探して上記と同じ作業をします。


同様にログレベルが変わった。



/shutdown

このエンドポイントは、アプリケーションをスムーズにシャットダウンする際に使用されます。デフォルトで設定されているエンドポイントではなく、application.propertiesファイルで設定する必要があります。下記のコードを追加してみましょう。

management.endpoint.shutdown.enabled=true

設定が終わったら、HTTP POSTリクエストを下記のように送ります。

http://localhost:8080/actuator/shutdown



このようにエクチューターを使ってアプリケーションを終了することができますが、セキュリティと安定性の面で問題が発生する可能性があるので慎重に検討する必要があります。むしろ使用しない方が良い場合もあるそうです。



【ポイント2】 Spring Doc OpenAPI Library - Swagger


springdoc-openapi javaライブラリはAPIドキュメントを自動生成するのに役立ちます。springdoc-openapi javaライブラリは spring-boot と swagger-ui の統合を提供します。自動的にJSON/YAMLとHTML形式のAPIからドキュメントを生成します。このライブラリはOpenAPI3, Spring-boot3(Java17, Jakarta EE9), Swagger-ui, Oauth2を提供します。コミュニティベースのプロジェクトであり、Spring Framewrok Contributorによって保守されているわけではありません。

では、Swaggerとは何でしょうか。 SwaggerはRESTが提供するAPIのドキュメント生成ツールです。これを利用してAPIを管理すれば、Postmanを使ってテストをする必要がありません。REST APIはURLだけで操作するため、処理が増えればAPIの数も増える。各APIの処理内容についてドキュメントを生成するのは面倒なので、ソースコードから自動的にドキュメントを生成してメンテナンスするためのツールが必要で、OpenAPI Swaggerが生まれました。



1.springdoc-openapi 依存関係の追加とテスト

Maven Repositoryに行き、SpringDoc OpenAPI Starter WebMVC UI の依存関係を取得し、pom.xmlに追加します!

<!-- https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-starter-webmvc-ui -->
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.1.0</version>
</dependency>


アプリケーションを実行して、urlに/swagger-ui/index.htmlを入力すると上のような画面が出ます!

ユーザー生成POSTでテストしてみましょう。


とても綺麗に成功しました。

情報修正もやってみよう。




残りのGET, DELETEも同じように試してみると正常に動作します。


2. アノテーションによるGeneral API情報定義(@OpenAPIDefinition)

@SpringBootApplication
@OpenAPIDefinition(
		info = @Info(
				title = "Spring Boot REST API Documentation",
				description = "Spring Boot REST API Documentation",
				version = "v1.0",
				contact = @Contact(
						name = "Sonsan",
						email = "Sonsan@gmail.com",
						url = "https://www.javaguides.net/license"
				),
				license = @License(
						name = "Apache 2.0",
						url = "https://www.javaguides.net/license"

				)
		),
		externalDocs = @ExternalDocumentation(
				description = "Spring Boot User Management Documentation",
				url = "https://www.javaguides.net/user_management.html"
		)
)
public class SpringbootRestfulWebservicesApplication {

	@Bean
	public ModelMapper modelMapper(){
		return new ModelMapper();
	}

	public static void main(String[] args) {
		SpringApplication.run(SpringbootRestfulWebservicesApplication.class, args);
	}

}

OpenAPIDefinitionをSpringBootApplicationファイルに貼り付けます。 その中にinfoとexternalDocs情報を入力します。この時、@Info、@Contact、@License、@ExternalDocumentationアノテーションが使われていますね。



3. アノテーションによるSwagger API Documentationのカスタマイズ( @Tag、@Operationと@ApiResponse)

@Tag(
        name = "CRUD REST APIs for User Resource",
        description = "CRUD REST APIs - Create User, Update User, Get User, Get All Users, Delete User"
)
@RestController
@AllArgsConstructor
@RequestMapping("api/users")
public class UserController {

    private UserService userService;

    @Operation(
            summary = "Create User REST API",
            description = "Create User REST API is used to save user in a database"
    )
    @ApiResponse(
            responseCode = "201",
            description = "HTTP Status 201 CREATED"
    )
    // build create User REST API
    @PostMapping
    public ResponseEntity<UserDto> createUser(@Valid @RequestBody UserDto user){
        UserDto savedUser = userService.createUser(user);
        return new ResponseEntity<>(savedUser, HttpStatus.CREATED);
    }


    @Operation(
            summary = "Get User By ID REST API",
            description = "Get User By ID REST API is used to get a single user from the database"
    )
    @ApiResponse(
            responseCode = "200",
            description = "HTTP Status 200 CREATED"
    )
    // build get user by id REST API
    // http://localhost:8080/api/users/1
    @GetMapping("{id}")
    public ResponseEntity<UserDto> getUserById(@PathVariable("id") Long userId){
        UserDto user = userService.getUserById(userId);
        return new ResponseEntity<>(user, HttpStatus.OK);
    }

    @Operation(
            summary = "Get All Users REST API",
            description = "Get All Users REST API is used to get all the users from the database"
    )
    @ApiResponse(
            responseCode = "200",
            description = "HTTP Status 200 CREATED"
    )
    // Build Get All Users REST API
    // http://localhost:8080/api/users
    @GetMapping
    public ResponseEntity<List<UserDto>> getAllUsers(){
        List<UserDto> users = userService.getAllUsers();
        return new ResponseEntity<>(users, HttpStatus.OK);
    }

    @Operation(
            summary = "Update User REST API",
            description = "Update User REST API is used to update a particular user in the database"
    )
    @ApiResponse(
            responseCode = "200",
            description = "HTTP Status 200 CREATED"
    )
    // Build Update User REST API
    @PutMapping("{id}")
    // http://localhost:8080/api/users/1
    public ResponseEntity<UserDto> updateUser(@PathVariable("id") Long userId,
                                           @RequestBody @Valid UserDto user){
        user.setId(userId);
        UserDto updatedUser = userService.updateUser(user);
        return new ResponseEntity<>(updatedUser, HttpStatus.OK);
    }

    @Operation(
            summary = "Delete User REST API",
            description = "Delete User REST API is used to delete a particular user from the database"
    )
    @ApiResponse(
            responseCode = "200",
            description = "HTTP Status 200 CREATED"
    )
    // Build Delete User REST API
    @DeleteMapping("{id}")
    public ResponseEntity<String> deleteUser(@PathVariable("id") Long userId){
        userService.deleteUser(userId);
        return new ResponseEntity<>("User successfully deleted!", HttpStatus.OK);
    }

コントローラクラスに@Tagアノテーションを付けて全体を概説するAPI情報を入力します。

その後、CRUDに該当するそれぞれのコントローラの上に@Operationと@ApiResponseを付けて各APIの詳細情報を記入します。

アプリケーションを動作させると、次のようにSwagger UIにAPIの詳細が表示されます。


4. アノテーションによるSwagger Models Documentationのカスタマイズ(@Schema )

@Schema(
        description = "UserDto Model Information"

)
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class UserDto {

    private Long id;

    @Schema(
            description = "User First Name"
    )
    // User first name should not be null or empty
    @NotEmpty(message = "User first name should not be null or empty")
    private String firstName;

    @Schema(
            description = "User Last Name"
    )
    // User last name should not be null or empty
    @NotEmpty(message = "User last name should not be null or empty")
    private String lastName;

    @Schema(
            description = "User Email Address"
    )
    // User email should not be null or empty
    // Email address should be valid
    @NotEmpty(message = "User email should not be null or empty")
    @Email(message = "Email address should be valid")
    private String email;
}

UserDtoクラスに@Schemaアノテーションを付けます。各フィールドの上に@Schemaアノテーションを付けてdescriptに詳細情報を入力します。


アプリケーションを再起動すると、UserDtoの詳細が入力されたことが確認できます。


【ポイント3】 マイクロサービス(Microservice)


マイクロサービスアーキテクチャは、ソフトウェアシステムを複数の小さな独立したサービスに分割するアーキテクチャパターンであり、各サービスは特定の機能を実行し、独立して配布、拡張、管理することができ、分散システムを構築し、柔軟性、拡張性、管理性を向上させることに重点を置きます。

マイクロサービスはモノリシックアーキテクチャの問題点から生まれました。


1.モノリシックアーキテクチャの特徴と問題点?

  1. すべてのコンポーネントが一つのユニットの一部である。

  2. 開発、配布、拡張が一つのユニットで行われるアプリは一つの技術スタックで構成されなければなりません。

  3. チームは他のチームの作業に影響を与えないように注意しなければなりません。

  4. 一つのartifact(WAR file)であるため、アップデートごとにアプリケーション全体を再配布する必要があります。

  5. 他の部分との結合度が高い

  6. 特定のサービスの一部を拡張する代わりに、アプリ全体を拡張する必要があります。これはインフラコストの増加につながります。

  7. 他の依存性バージョンが必要な場合、対応が難しい。

  8. デプロイに時間がかかる。一つの変更に対してアプリ全体をテストしなければならず、アプリ全体がビルド/デプロイされなければなりません。

  9.  一つのモジュールで発見されたバグはアプリ全体を壊す可能性がある。


モノリシックの問題点として様々なことが挙げられますが、「一つに組み合わされているのでメンテナンスが難しい」というのがポイントです。



2.マイクロサービスの特徴とメリット


マイクロサービスはモノリシックの反対と考えればいい。

  1. 各コンポーネントが各単位で存在する。

  2.  開発、配布、拡張が各ユニットで行われ、それぞれの技術スタックで構成することができる。

  3.  部分が他の部分との結合度が低い

  4.  特定のサービスの一部分を拡張することができ、これはインフラコストの減少につながります。

  5.  他の依存性バージョンが必要な場合に対応できる。

  6.  展開過程が少ない。


3. マイクロサービスを実装するために解決すべき主な問題点

(1) どのようにアプリケーションを分割するか?

(2) コードはどこに行くのか

(3) どのくらいのサービスを作るべきか。

(4) サービスのサイズはどうなるのか

(5)サービス間のコミュニケーションはどうすべきか?

同期的コミュニケーション(Synchronous Communication) : HTTPリクエストに対する応答が来るまで待ちます。ex) RestTemplate, WebClient and Spring Coud Open Feign library

非同期的コミュニケーション(Asynchronous Communication): HTTPリクエストに対する応答を待たずに自分の仕事をします(メッセージブローカーを使ってメッセージをキューに溜めておく) ex) RabbitMQ or Apache Kafka


マイクロサービスを分ける基準はコンポーネント別、ビジネス機能別であり、一つの作業に対して一つのサービスを担当するという「単一責任の原則(SRP - Single Responsibility Principle)」に従います。

4.マイクロサービス実装のためのいくつかの技術


マイクロサービス構成図例
  • API Gateway: 最新のマイクロサービスアーキテクチャと分散システムで重要な役割を果たす集中型コンポーネントです。ロードバランシング、ルーティング、フィルタリング、キャッシュ、API管理、ロギングとモニタリングを通じてパフォーマンスを最適化します。

  • スプリングクラウド(Spring Cloud): Spring Cloudはマイクロサービスアーキテクチャを構築・管理するためのJavaベースのフレームワークとライブラリのコレクションです。サービスディスカバリー、ロードバランシング、サーキットブレーキング、分散構成と分散追跡、APIゲートウェイ、サービストポロジービューなどの重要な機能が含まれています。

  • Config Server: Spring Cloud Config Serverは分散システムで設定を一元化して管理するためのツールです。 これにより、各マイクロサービスはConfig Serverから設定情報を動的に取得して使用することができ、設定変更時にサービスを再起動せずに更新された設定を適用することができます。

  • Service Registry: サービスレジストリは、マイクロサービスアーキテクチャで各サービスの位置や状態情報を登録して管理する集中化されたシステムです。Spring Cloud Netflix EurekaやConsulのようなツールを使用してサービスレジストリを実装し、これによりサービス間の通信や検索が容易になります。

  • Spring Cloud Sleuth: Spring Cloud Sleuthは分散システムでロギングと追跡を管理するためのツールで、各サービス間のリクエスト追跡と関連ログ情報を生成して収集します。これにより、サービス間の問題のデバッグやパフォーマンスの最適化が容易になります。

  • Zipkin: Zipkinは分散システムでのリクエスト追跡とモニタリングのためのオープンソースツールで、Spring Cloud Sleuthと統合して使用することができます。Zipkinは、リクエストのライフサイクル全体を視覚的に表現し、各サービス間の依存関係を視覚化することで、分散システムのパフォーマンス問題を特定し、解決するのに役立ちます。


最後に


今日から言葉だけよく聞くマイクロサービスを実際に構築してみようと思います。Department Serviceから始めてEmployment Service、Organization Serviceを順番に作ってみます。 では、どうやってこんなことができるのでしょうか?Port Mappingというものが使われます。 つまり、各サービスごとにポート番号を違って設定するんです。 下記のようにです。 初めて挑戦するマイクロサービス構築ですが、簡単ではない挑戦になりそうです!!!

API-Gateway 9191
Department-Service 8080, 8082
Employer-Service 8081
Config-Server 8888
Service-Registry 8761
Organization-Service 8083
React-Frontend 3000
Zipkin Server 9411


エンジニアファーストの会社 株式会社CRE-CO
ソンさん



【参考】


  • [Udemy] Building Microservices with Spring Boot & Spring Cloud

  • IT用語図鑑 エンジニア編 開発・web制作で知っておきたい頻出キーワード256

この記事が気に入ったらサポートをしてみませんか?