春
アノテーション @SpringBootTestを最適化しよう (feat. @DataJpaTest, @WebMvcTest)
入社初めてテスト時間が非常に長くて悩んだ。(約28分程度) 余談だが、チームのメンバーたちは普段これくらいの時間がかかっていたので、テスト時間が長いとは感じていなかったが、前の会社では2〜3分程度の時間しかかからなかったので、これだけの時間がかかるのは非常に我慢が難しかった 🤣 当時はTestcontainersのようなコンテナを起動するツールは使用せず、すべてMockitoベースのユニットテストだけだったが、なぜこんなにテスト時間が長くかかるのか理解できなかった。 Repositoryや統合テストのためにTestcontainersのようなツールを使用する場合、テスト時にコンテナを起動しサービスと接続する時間が追加でかかるため、ある程度時間がかかることがある。 そこで今回はサービスでどのようにテストを最適化できたのか、何が問題だったのかについて原因を把握し、最適化した過程を共有しようと思う。 原因把握 1. 依存性の問題 まず依存性で疑問に思う部分があった。私が作成したテスト対象はHomeController(仮称)だったが、このコントローラの依存性はHomeServiceだけだった。 しかし、ProductService(仮称)がないとエラーが出るのではないか。 Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.demo.service.ProductService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)} もちろん次のように依存性を追加するだけで問題はない。
2025年1月20日
Spring BootでRedis Cacheを適用する
少し前ですが、Locustというツールを通じてAPI負荷テストを行ったことがあります。その際発生した特定のAPIの応答遅延を解決するためにRedis Cacheを適用した経験を共有しようと思います。 とは? キャッシュとは、データや値を事前にコピーしておいて速やかにアクセスできるようにするメモリ領域を指します。 基本的にRDBはディスクに保存されているため、単にメモリに保存されているRedisとは比べ物にならない速さで取得できます。 もちろんキャッシュの戦略によって詳細な流れは異なることもありますが、一般的なキャッシュ戦略であるLook-Aside Cacheを基準に説明すると以下の通りです。 Cache Hit Cache Hit、つまりキャッシュにデータが存在する場合を指します。この場合、キャッシュに保存されたデータを取り出して使用するため、データを取得するのにかかる時間が非常に速いです。 Cache Miss Cache Miss、つまりキャッシュにデータが存在しない場合を指します。この場合、データを取得するためにデータベースや他のストレージからデータを取り出し、Redisでデータをチェックし、DBからデータを取得し、応答を再びRedisに保存する過程を経ます。 そのため、この場合は逆にキャッシュがない場合よりも遅くなることがあります。 Redisをキャッシュに使用する理由 RedisはRemote Dictionary Serverの略で、メモリベースのKey-Value構造を持っているオープンソースデータベースです。 つまりデータベースと見ればよいです。 一般的にRDBからデータを取得する時でもインデックスがうまく構築されていれば、データを速やかに取得することに大きな問題はありません。 ただ、一般的に検索に対する結果値、ページングなどクエリによる結果値などはかなり膨大な演算を要求するため、そのような結果をキャッシュとして保存しておき、再利用するのです。 Spring BootでRedis Cacheを適用する 1. 依存性の追加 まずRedisを使用するための依存性を追加する必要があります。 build.gradleimplementation 'org.springframework.boot:spring-boot-starter-data-redis' 2. Redis設定の追加 Redisに接続するための設定を追加する必要があります。
2025年1月19日
SpringでTestcontainerを利用してQueryDSLをテストしてみよう
業務でQueryDSLを使用していると、実際にそのクエリが正常に動作するかどうかを知るのが難しい場合があります。 このとき、Testcontainerを利用すると、コンテナ環境で実際の環境と同じDBを起動し、そのクエリを実行して結果を対照することでテストを進めることができます。 なぜQueryDSLのテストを行うのか? 実際DBにアクセスしてクエリを実行することは、QueryDSLだけでなく、JPA、MyBatisなどさまざまなORMフレームワークでも可能です。 ただし、JPAのクエリメソッドの場合、使い方が非常に簡単であるため、また既にテストがされていると仮定するため、実際のSQLを扱うQueryDSLや、MyBatisでテストを行う方が多いです。 Testcontainerとは? TestcontainerはDockerを利用してテスト環境を構築できるようにするライブラリです。 H2のような組み込みDBのDialectを使用しているDBに合わせてテストを行うこともできますが、実際に使用するDBと同じ環境ではないため、意図しないエラーが発生する可能性があります。 Testcontainerを利用すると、実際に使用しているDBをDockerで起動してテストを行うことができるため、設定さえ注意して行えば、実際の環境と同じ環境でテストを行うことができます。(ただし、H2のような軽量な組み込みDBに比べ、テスト速度が遅い場合があります。) Testcontainerとの連携 Testcontainerを使用するためには、build.gradleに以下のような依存性を追加します。 私はMySQLを使用しているので、MySQLを使用したテスト依存性を追加しました。 build.gradledependencies { // ... testImplementation 'org.testcontainers:junit-jupiter:1.20.0' testImplementation 'org.testcontainers:mysql:1.20.0' // ... } QueryDslTestConfigの定義 QueryDslテストで使用するQueryDslTestConfigを定義します。
2024年8月19日
Spring @Configuration と @Component アノテーションの違いを見てみよう
Springで依存性(Beanクラスとも呼ばれる)を登録する方法は大きく2つある。すぐに説明する@Configurationと@Componentである。 Beanは以前の投稿で説明したように、Spring IoC Containerで管理されるオブジェクトを指す。 @Configurationと@ComponentはそのBeanを登録する方法である。この2つの違いについて調べてみよう。 @Componentを使う @ComponentはSpring IoC ContainerにBeanを登録する方法の1つである。 以下に3つのコンポーネントを登録する方法を見てみよう。 Computer.java@Component public class Computer { MainBoard mainBoard; CPU cpu; public Computer(MainBoard mainboard, CPU cpu) { this.cpu = cpu; this.mainBoard = mainBoard; } void printComputerInfo() { System.out.println("CPU Name:" + cpu.name); System.out.println("CPU Manufacturer:" + cpu.manufacturer); System.out.println("Main board Name:" + mainBoard.name); System.out.println("Main board Manufacturer:" + mainBoard.manufacturer); } } MainBoard.java@Component public class MainBoard { String name; // 製品名 String manufacturer; // 製造業者 // コンストラクタ関数 public MainBoard() { this.name = "B760M Pro RS D5"; this.manufacturer = "ASRock"; } } CPU.java@Component class CPU { String name; // 製品名 String manufacturer; // 製造業者 // コンストラクタ関数 public CPU() { this.name = "Ryzen7 PRO 8700GE"; this.manufacturer = "AMD"; } } このようにすると、IoCコンテナにそれぞれComputer、MainBoard、CPUクラスが個別のBeanとして登録される。
2024年7月9日
SpringでMicrometerを活用してTrace IDとSpan IDをログに記録する
デバッグのためにログを記録する際、特に分散システムでは、トランザクションの流れをログで把握するのが難しい場合があります。 特にプロダクション環境では、デバッグのために様々な段階で正常なリクエストのパラメータや返り値の情報をログに記録することがありますが、これらのログは同時に複数のリクエストが入ると混在し、特定のログがどのリクエストに関するものか把握しづらい場合があります。 また、エラー発生時にはエラーが発生したリクエストのログを把握しにくい場合があります。 このような場合、Trace IDとSpan IDを活用してログを記録することで、ログの区分や検索面でトランザクションの流れを把握しやすくなります。 事前知識 このパートでは、ログ記録用のライブラリなどは扱いません。 Springでのログ記録方法やJSONフィールドの追加方法、環境ごとのログフォーマットの分離に関しては前回の投稿 “Spring BootでLogbackを利用したパターンログおよびJSONログの出力” を参照してください。 このパートではLogbackを使用し、Logbackの設定はlogback-spring.xmlファイルを使用します。 Trace ID vs Span ID Trace ID: 全体のトランザクションを追跡するためのIDで、マイクロサービス間でも同一のIDを維持します。 Span ID: トランザクション内の各ユニットを追跡するためのIDで、マイクロサービス内でのみ維持されます。 Micrometer Micrometerは、アプリケーションのパフォーマンスを測定するためのメトリクスを収集するライブラリです。MicrometerはSpring BootやSpring Cloudで使用可能で、Zipkin、Prometheus、Graphiteなど様々なモニタリングシステムと連携できます。 実際にはもっと複雑で多様な機能を持っていますが、ここではMicrometerがリクエストごとにTrace ID、Span IDを付与する機能のみを利用してこれをログに活用します。 Zipkinと連携してトレースする方法は後日他の投稿で扱います。 依存関係の設定 複数のトレースツールを使用したい場合はさらに多くの依存関係を追加する必要がありますが、Trace IDとSpan IDを利用するためには以下の2つの依存関係を追加するだけです。 build.gradledependencies { // micrometer implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'io.micrometer:micrometer-tracing-bridge-brave' } ここでspring-boot-starter-actuatorはSpring Boot Actuatorを使用するための依存関係であり、アプリケーションの動作方法をモニタリングおよび管理する機能を提供します。
2024年6月24日
Spring BootでLogbackを利用したパターンログとJSONログの出力
APIサーバーを運用していると、運用状況についてログを残したり、デバッグのためにログを残すことが多い。 伝統的にSpringではログを残す時にフォーマットが存在するが、Raw Text形式で残すことが多く、このような場合、筆者としては可視性の面ではJSON形式でログを残すよりも良いと考える。 しかし、ログを検索し、フィルタリングする場合には話が異なり、AWS CloudWatchのようにログを検索できるプラットフォームの場合、Raw Text形式でロギングするとどの関数でどのデータが出力されたのかフィルタリングが難しい。 特に複合的に複数の条件で検索しなければならない場合はさらに難しい。 もちろん通常、ログに特定のキーワードを追加してそのキーワードと一緒に検索すれば問題はないが、JSON形式でログを残せば形式的に検索がより容易になる。 (詳細な実装は分からないが、おそらくインデキシングのような部分も設定すれば利点があるのではないかと思う。) これを実現するために、ローカルではより可視性の高いRaw Text形式で、デプロイ環境ではJSON形式でログを分けて残す方法を見てみよう。 Logback LogbackはSLF4Jの実装であり、Spring Bootで基本的に使用されているロギングライブラリである。 基本的にspring-boot-starter-web依存関係を追加するとLogbackが自動的に追加されるため、特別な依存関係の追加は必要ない。 Slf4j Slf4jはSimple Logging Facade for Javaの略称で、Javaのロギングライブラリを抽象化したインターフェイスである。 文字通りインターフェイスであるため、実際の実装はLogback、Log4j、Log4j2、JULなど様々なロギングライブラリを使用できる。 このように設定する理由は、特定のロギングライブラリで脆弱性が発見された場合や、他のロギングライブラリに変更が必要な場合に、実装だけを変更すれば済むためだと考えられる。 Logback設定 基本的にLogbackの設定はlogback-spring.xmlファイルを生成して設定できる。 この設定は大きく、Appender、Logger、Encoderで構成される。 Appender Appenderはログをどこに出力するかを決定する役割をする。 基本的にConsoleAppender、FileAppender、RollingFileAppender、SyslogAppenderなど様々なAppenderが存在する。 Logger Loggerはログを残す対象を決定する役割をする。 Loggerは名前を持ち、その名前を持つLoggerだけにログを残す。 Encoder Encoderはログをどのような形式で出力するかを決定する役割をする。 基本的にPatternLayoutEncoderを使用するとRaw Text形式でログを残すことができ、JsonEncoderを使用するとJSON形式でログを残すことができる。 筆者はローカルではPatternLayoutEncoderを使用し、デプロイ環境ではJsonLayoutを使用してログを残す方法を案内する。 JsonLayout JsonLayoutはLogbackが提供するLayoutで、ログをJSON形式で出力できるようにする。 JsonLayoutを使用するためにはlogback-json-classicとlogback-jackson依存関係を追加する必要がある。
2024年6月17日
(JAVA) Springでサービス内でstaticメソッドをMockingする方法
一般的に、Springはレイヤードアーキテクチャを採用しており、サービス層で主にビジネスロジックを処理することになります。 この時、staticメソッドを使用することがありますが、一般的なコンポーネントの場合、ランタイム時に動的にバインディングされるため、Mocking時にそのインターフェースを実装したオブジェクトをMockingオブジェクトで置き換えてテストを進めることができます。 しかし、staticメソッドの場合は、コンパイル時にメソッドが静的にバインディングされるため、Mockingが難しくなります。 では、どうすれば良いでしょうか? 1. ラッパークラス 伝統的な方法としては、Staticメソッドをコンポーネント化することができます。 staticメソッドを通常のメソッド内で利用して、一度ラッパークラスで囲むことです。 例 例えば次のようなコードがあると仮定してみましょう。 @Service @RequiredArgsConstructor public class ProductService { private final ProductRepository productRepository; private final EventNumberPicker eventNumberPicker; public List<ProductDto> listProducts() { return productRepository .findAll() .stream() .map(product -> new ProductDto(product, eventNumberPicker.pick(1, 1000))) .toList(); } } Productエンティティは次のように構成されます。
2024年6月15日
(JAVA) Springで同じサービス内のメソッドをモック化する
一般的にSpringはレイヤードアーキテクチャを採用しており、サービスで主にビジネスロジックを処理します。 サービスのコードが増えれば増えるほど、似たコードが多くなることが一般的です。このような場合、共通ロジックを処理する別のレイヤーを設けて分離するケースもありますが、通常はサービス内に共通メソッドを宣言して処理することが多いでしょう。 例えば、次のようにします。 @Service public class SomeService { public void methodA() { // do something } public void methodB() { methodA(); // do something } public void methodC() { methodA(); // do something } } 上記のコードでは、methodAはmethodB、methodCで共通に使用されるメソッドです。
2024年5月30日
Autowired アノテーションについて知ろう
前回は Spring の DI について学びましたね? “依存性注入” の記事を見に行く 今日は DI と深く関係するアノテーション @Autowired について見てみましょう。 まず、どのメソッドやアノテーションでも名前には非常に重要な意味があります。 Autowired? どんな感じですか?「自動的に接続された」みたいな感じですよね? 何だか分からないけれど、見てみましょう。 @Autowired? 自動で接続されているという意味を持つこのアノテーションは、Springにおいて依存性を注入する役割を担っています。概念的に学んだ依存性注入を実際に実現しているものと言ってもいいでしょう。 上の注釈を通じて定義を確認してみましょう。 コンストラクタ、フィールド、Setterメソッド、または Configメソッドを Spring の依存性注入機能によって自動で使用できるように表すアノテーションです、と記されています。 前回依存性注入についての定義を説明しましたので、@Autowired の役割について簡単に説明します。 前回は依存性を注入するためには Student オブジェクトを利用するクラスが別途必要と説明しました。 しかし、該当オブジェクトを最終的に自分で作成して使用するならば、注入の過程でまた別の依存性が発生するかもしれませんよね? そのような問題を解決するために、@Autowired はもう new というキーワードが必要ないようにします。 class StudentService{ @Autowired private StudentRepository studentRepository; // 名前で学生を探すメソッド public Student findByStdName(string name){ return studentRepository.findByName(name); } } ここでおかしなもの (repository や findByName() など) が多く出てきましたが、ここでは二つだけを見ればいいです。
2022年2月24日
Spring Beanについて知ろう
今日は、Spring MVCについて説明しようと思いましたが、その前にMVCを理解するために知っておくべきSpringの基本知識をまずご紹介しようと思います。 Spring Beanとは? Spring Beanは、Springコンテナが管理するオブジェクトを指します。 でも、なんか変ですね。Springで管理するオブジェクト?ライブラリの中にあるSpringが定義したオブジェクトでしょうか? 答えを言うと、違います。もちろん、ライブラリの中にもBeanとして定義されているオブジェクトが存在するかもしれませんが、ユーザーが作成したけどSpringで管理されるオブジェクトをBeanと定義します。 本来はユーザーがオブジェクトを管理する必要がありますが、このようにフレームワークで管理されることを制御の逆転、IOCと表現します。 Tip 勉強されている方々は信じられないかもしれませんが、Springは自身が作成したオブジェクトや説明に関して非常に詳細に説明しています。もちろんSpringのドキュメントを見てもいいですが、コードの中から直接探すこともできます。 それでは、Springで定義されたオブジェクトを一度見てみましょう。 上の写真はSpringのBeanアノテーションのインターフェースをキャプチャした写真です。上を見ると、かなり多くのコメントがありますね?最初に見たときは理解しづらいかもしれませんが、途中でコードを通じて最大限理解しやすく説明しようとしているのがわかります。 一番上で彼らがどうBeanを表現しているのか見てみましょう。 “このアノテーションが付いたメソッドはSpringコンテナによってBeanとして管理される"ということです。 それでは、さらに下のコメント部分の要約を見てみましょう。 “この注釈の付いたメソッドはSpring Lagacyで使用されたXMLのに定義されたオブジェクトと類似して動作します。“と説明しつつ、さらにコードを通じて例を示していますね。 Beanの登録 @Bean public MyBean myBean() { // instantiate and configure MyBean obj return obj; } このようにmyBeanメソッドの前に@Beanというアノテーションを通じてbeanとして登録できるのを示していますね。
2022年2月24日
依存性注入 (Dependency Injection, DI)について学ぼう
この投稿では、Spring Frameworkの重要な概念の一つである依存性注入(Dependency Injection: DI)について取り扱います。 DIはSpringの非常に重要な概念の一つで、会社の面接でもよく出てくるので、必ず知っておくと良いです。 まず依存性について学びましょう。 依存性 AオブジェクトがBオブジェクトに依存しているということは、包含関係と非常に密接な関係があります。 一度コードで見てみましょう。 Korean.javaclass Korean { int score; // 科目の点数 string content; // 科目の内容 } Student.javaclass Student { private Korean korean; public Student() { korean = new Korean(); } } 上記のようにStudentクラスの場合、国語の点数scoreと科目の内容contentを持つKoreanクラスを含んでいます。 包含関係と呼ぶこともできますが、
2022年2月24日