Excite × iXIT TechCon 2024 にてネイティブ実装アプリの Flutter 化事例について発表しました!

エキサイト株式会社の@mthiroshiです。

エキサイトHD主催の社内テックカンファレンス「Excite × iXIT TechCon 2024」にて、登壇発表をしました。

「Excite × iXIT TechCon 2024」の詳細については、下記記事をご参照ください。 tech.excite.co.jp

iOS / Android ネイティブ 実装アプリの Flutter 化事例

今回の社内テックカンファレンスでは、「iOS / Android ネイティブ 実装アプリの Flutter 化事例」の題目で登壇発表を行いました。 Flutter 化事例を基に、アプリ開発の理解を目的とした内容でお話しました。

speakerdeck.com

参加者の多くが非アプリエンジニアだったため、アプリ開発の基本的な部分からお話させて頂きました。アプリ開発の理解の助けになれば幸いです。

Java17 / Spring Boot2 を Java21 / Spring Boot3 にアップデートした話

こんにちは。 エキサイト株式会社の三浦です。

先日、サービスで使っているJavaやSpring Bootのメジャーバージョンをアップデートしました。

その際に何を行ったかについて説明していきます。

主目的のアップデート内容

今回は、主に以下のアップデートを行うことを目的としました。

Java

  • version 17 → 21
  • SDKMANを使用

Spring Boot

  • version 2.4.3 → 3.2.2
  • Gradleを使用

最大の目的は以上の2つのアップデートだったのですが、残念ながらそれだけでは完結しませんでした。

付随して、以下のような対応が必要となったので説明していきます。

付随した変更内容

Gradle自体のバージョンアップ

Gradleをバージョン・タスク管理システムとして使用している場合、Gradle自体も、バージョンが古ければアップデートが必要な場合があります。

各種ライブラリのバージョンアップ

Spring Bootで開発しているとはいえ、多くの場合Spring Boot以外のライブラリも使用していることでしょう。

それらの各種ライブラリも、場合によってはアップデートが必要になります。

特に今回はJava / Spring Bootのメジャーバージョンをアップデートするため、バージョンアップをしないと正しく動かないライブラリもそれなりに存在する可能性があります。

また、バージョンアップしたことによって今までの設定では動かなくなる場合もあります。

その際は、設定も修正しましょう。

javaxパッケージのimportを修正

Java17からJava21にアップデートすることで、今まで javax としてimportしていたパッケージが、 jakarta となります。

import javax.annotation.Nullable;

↓

import jakarta.annotation.Nullable;

基本的には名前が変わっただけで処理自体は変わらないので、機械的に対応していきましょう。

ただし、 javax.sql.DataSource など一部 javax のまま使われるものも存在するので、注意が必要です。

org.thymeleaf.spring5パッケージのimportを修正

Spring Bootのアップデートにより、 org.thymeleaf.spring5 パッケージは org.thymeleaf.spring6 パッケージへと変更になりました。

使っている場合は変更しましょう。

ConstructorBindingの削除

@ConstructorBinding アノテーションは、コンストラクタが複数あるなど特殊な状況でない場合は不要となります。

不要であれば削除しましょう。

トレイリングスラッシュの扱いの変更

@RestController@Controller を使ってAPIやWebページを作っている場合、これまではURLパスのトレイリングスラッシュあり・なしは明示的に指定しなくても両方含まれていました。

バージョンアップ後は、明示的に指定したパスのみが含まれるようになるので、注意しましょう。

非推奨ではありますが、これまでと同様にトレイリングスラッシュあり・なしを明示的指定なしで両方含ませたい場合は、以下の設定を追加することで実現できます。

package sample.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.setUseTrailingSlashMatch(true);
    }
}

Readme等のドキュメント更新

Readme等のドキュメントにバージョン情報を書いている場合は、そちらも更新しましょう。

CI / CD環境のJavaのバージョンアップ

CI / CDをしている場合、CI / CD環境にも開発環境とは別指定でJavaをインストールする処理が含まれる場合があります。

その場合は、そちらの環境のJavaのバージョンもアップデート後のバージョンに変更するのを忘れないようにしましょう。

最後に

今回挙げた以外にも、使っているSpring Boot系のライブラリによっては追加対応が必要な場合もあるでしょう。

また、コード内の処理次第ではさらなる変更が必要な場合もあるかもしれません。

メジャーバージョンアップの際は、付随的な変更もそれなりに必要になります。

面倒ではありますが、今後の開発に向け必要な作業なので、ぜひやってしまいましょう!

Figmaのライブラリ公開で問題発生!ローカルバリアブルがライブラリに反映されない原因とは?

はじめに

こんにちは、エキサイト株式会社3年目デザイナーの山﨑です。

今回は「Figmaでローカルバリアブルをライブラリで公開したのに、何故か反映されなかった話」について書きたいと思います!

事の発端

前述の通り、Figmaで登録したフォント・コンポーネントをライブラリで公開した際に、公開したはずのローカルバリアブルが別のFigmaデータで使えない事態が起こりました。

原因

アセットを選択し、未公開の変更を確認ボタンを選択します。

ライブラリをよく見てみると、公開したいローカルバリアブルが「非表示」になっていたことが原因でした。

非表示になっているということは、ライブラリで公開しても表示されないということ。

「バリアブルコレクション」を右クリックすると、「公開時に表示」というボタンが出てくるのでクリックします。

ライブラリでも公開されるようになりました。

原因は単純だったのですが、ライブラリでローカルバリアブルも公開/非公開を選択できる仕様をすっかり忘れてしまっていたので、原因究明に少し時間がかかってしまいました。

検索しても解決策が出てこなかったので、同じような原因で公開されない方がいた場合、お役に立てたら嬉しいです🙏

最後に

最後に、エキサイトではデザイナー、フロントエンジニア、バックエンドエンジニア、アプリエンジニアを絶賛募集しております!

興味があればぜひ連絡よろしくお願いいたします!🙇

www.wantedly.com

【インターン】広告バナーで試行錯誤して学んだこと

エキサイトでデザイナーインターンをしているさかいです! 大学ではデザインとエンジニアリングを幅広く学んでいます。アルバイトでwebサイトを作成したりUIデザインを改修しながら勉強しています! また、個人的には絵を描くことが趣味で、イベント主催なども行っています。

今回の実務型インターンで経験させていただいたことはこちらです。

  1. 相談サービス「お悩み相談室」のmeta広告のバナー制作
  2. 恋愛相談サービス「恋ラボ」のTOPページへ追加機能デザインと実装

今回の記事では、1つ目のお悩み相談室のmeta広告のバナー制作で学んだことについて書いていきます! デザイナーさんからのフィードバックや、マーケティングの先輩の分析を通してたくさんの学びがありました。

やったこと

以下の順序で進めていきました。

お悩みサービスの理解

広告制作の前に、まずはお悩み相談室のサービスの強みを考えました。 競合分析の視点からもアプローチし、ユーザーにとってどのような価値を提供できるかを考えました。

訴求ポイントから、デザインを作成する

訴求ポイントを整理し、デザインに反映させました。 訴求ポイントで言いたいことからずれないように作っていきます。

デザイナーさんからのレビューにより、細かい視点で改善することができました。 写真の視線を動線として活用したり、文字の装飾についてレビューをいただきました!

他のバナーでも、読みやすさや言葉の使い方などたくさんのレビューをいただき、ユーザーに届きやすいデザインになりました。 また、「健康にする」「治す」など、広告視点で使わないほうが良い言葉があると知り、言葉の使い方についても勉強になりました。

数字での解析を元に改善

結果
クリック率や登録率が他の広告と比べてよかったのはこちらの二つでした。 キレイにまとまったかも?と思っていたバナーがクリックされていなかったり、自分的には変化球の案がクリックされてたりして、広告の難しさと面白さを知りました。

クリック率などのデータだけでなく、マーケティング視点でアドバイスもいただき再度バナーを作成しました! 過去のA/Bテストの結果から、ポジティブな変化を連想させるようなデザインが好まれやすいということからこちらのデザインとして再度分析する予定です。

ブラッシュアップ

感想

今回のmeta広告のフローでは、デザインに効果があったのかどうかを調べて改善するプロセスを経験させていただきました。 ユーザーにクリックされるデザインと、自分が良いなと思ったデザインはまた違ったりするところに難しさと楽しさがありました。

デザインのフィードバックをくださったデザイナーさん、マーケティングの目線でアドバイスをくださった先輩、ありがとうございました! インターンの目標として、デザインがどうだったのかを定量的にフィードバックいただいて改善したい!と思っていたので、経験させていただきとても嬉しかったです。

業務や不明点などを親切にわかりやすく教えていただき、とても楽しく働いていた1ヶ月半でした。 メンターさん、一緒に仕事した方々、本当にありがとうございました!

【インターン】UI改修デザインから実装まで担当して学んだこと

エキサイトでデザイナーインターンをしているさかいです! インターンで学んだこと第2弾を書いていきます!

担当した業務としては大きくわけてこの2つです。

担当したこと

  1. 相談サービス「お悩み相談室」のmeta広告のバナー制作
  2. 恋愛相談サービス「恋ラボ」のTOPページへ追加機能デザインと実装

今回は、2つ目の業務である恋ラボのTOPページへ追加機能デザイン実装について書いていきます! デザイナーさんやディレクターさんからのフィードバックをいただき、デザインからコーディング、リリースまで経験して大変勉強になりました。

やったこと

以下の順序で進めていきました!

改修の目的を明らかにする

今回は、トップページに相談員の人数を表示するというものです。 新規でサイトを訪問したユーザーに、今相談できるということを知ってもらえれば、今すぐ相談したい人の目にも留まると考えました。

デザインを作成

恋ラボのユーザーボリューム層である20代が使用しているサービスを見て、どんなUIになっているのかを調べます。 ワイヤーフレームから、デザインをおこしていきました。

改善

レビューいただき、ボタンとテキストのデザインに決定しました!

実機で実際の画面でみたときに、上部にカルーセルがあり、ボタンにイラストも付けてしまうことによって逆に目立ちづらくなります。 今回のUI改修の目的を改めて考えて、シンプルなボタンとテキストに決定しました。

コーディング〜リリース

実際にコーディングし、リリースまで経験させていただきました。 プルリクエストを出した際に共通点が多く何度も書いてしまった部分に関して、アドバイスをいただきました!

自分で気づかなかったミスなどもあり反省しました。 細かくみていただきありがとうございます。

以前は、自分でweb制作をする際にバージョン管理としてgithubをなんとなく使用していました。 業務で作業することについてずっと緊張していたのですが、画面共有して一緒に見てくださりとても安心して進められました...!

また、stg環境を使用する際にワークフロー化されており、そういった働きやすさや工夫に触れられたところも良かったです。

感想

機能追加のデザインから実装まで経験できて、実際に動く画面がリリースされたときはすごく嬉しかったです! 1ヶ月半のインターンを通して、理由のあるデザインとはなにか、効果のあるデザインとはなにか、試行錯誤する流れを学びました。

マーケティング視点をもったり、コーディングの範囲まで勤務することができて、充実したインターンになりました。 楽しく勤務させていただきありがとうございます!

【Techcon】社内カンファレンス「Excite × iXIT TechCon」に登壇しました!

はじめに

こんにちは、エキサイト株式会社3年目デザイナーの山﨑です。 今回は、弊社の社内カンファレンス「Excite × iXIT TechCon」に登壇した話について書こうと思います。

テーマ

今年のTechconのテーマは「Restart」なので、初心に帰りデザインの基礎である4原則と色について解説を行いました。

「デザインの基礎知識」を題材に選んだ理由

この題材を選んだ理由は、「Restart」というカンファレンスのテーマに合わせた以外にもう1つあります。

そのもう1つの理由は、「Techconの参加層のほとんどの職種がエンジニアだから」です。

「Excite × iXIT TechCon 2024」の登壇者の23人中19人はエンジニア職と、約8割を占めています。

そのため、いきなりデザインの専門的な内容を発表するよりも、デザインの基本的な知識を解説した方がデザイナー以外の職種の方にもデザインの魅力が伝わるのではないかと思い、「デザインの基礎知識」を題材に選びました。

登壇内容

デザイン(今回指す"デザイン"は表層のデザインを指します)は、基本的に近接・整列・強弱・反復の4原則と色で構成されています。

まずは4原則の説明と、その原則が実際世の中に出ているデザインにどう使われているかを解説を行いました。(※後者については、他社のデザインを引用している部分もありますので掲載を控えさせていただきます。)

次に、非デザイナーにとってハードルが高くなりがちな配色について解説を行いました。

本来ならばフォントについての解説も行うべき所だったのですが、登壇時間が20分という枠の都合上入りきらなかった為、やむを得ず割愛しました。

結果

ありがたい事に、こちらのセッションは高評価を受け、「ベストセッション賞」を受賞しました👏

アンケートでは嬉しいお言葉も多数いただき、身が引き締まる思いです。

来年のTechconでも、より多くの方に役立つデザインの知識を発表できるように邁進していきたいと思います💪

紹介

▼他の登壇者の記事はこちら▼

tech.excite.co.jp

▼運営の記事はこちら▼

tech.excite.co.jp

最後に

最後に、エキサイトではデザイナー、フロントエンジニア、バックエンドエンジニア、アプリエンジニアを絶賛募集しております!

興味があればぜひ連絡よろしくお願いいたします!🙇

www.wantedly.com

TerraformをやめてCDKでReStartしたあと、 CDKをやめてCDK for TerraformでReStartした話

こんにちは。 エキサイト株式会社の三浦です。

2024年2月16日、エキサイトで第3回目となるTechConが開催されました! 詳細は以下の記事を御覧ください。

tech.excite.co.jp

今回私は、「TerraformをやめてCDKでReStartしたあと、 CDKをやめてCDK for TerraformでReStartした話」というタイトルで発表させてもらいました。

IaCを選ぶ際の、一つの参考になれば幸いです。

2024年も社内カンファレンス「Excite x iXIT TechCon」を開催しました

はじめに

Exciteメディア事業部でエンジニアをしている、taanatsuです。
今年もエキサイトHD(エキサイトとiXIT)主催の技術者向けの社内カンファレンス
「Excite × iXIT TechCon」を開催しましたのでご報告いたします。

去年の第一回 、第二回に開催されたTechCon についてはこちらをご覧ください。

TechCon のテーマ

毎年テーマを決めてTechConを開催しています。
1回目は「Beginning」
2回目は「Keep Going!」
3回目の今年は会社も再上場し、心機一転、社員の様々な転機を表す、
ReStart」がテーマとなりました。

リビルドなどのReStartだけではなく、新しい知見を得て自分の実力を新しく活かしたり、
考え方を変えたりなど様々なReStartを発表していただきました。

TechCon の概要

まずはカンファレンスの概要を説明しておきます。

  • 参加者
    • エキサイトHDの技術職全員 (技術職以外の社員は任意参加)
    • 新卒3年目までのエンジニア・デザイナーは発表必須
  • 開催時間
    • 10:10-18:30
  • 開催場所
    • 社内会議室およびカフェスペースを使ったオフライン会場と、Zoom のウェビナーを使ったオンラインのハイブリッド開催
  • 発表内容の選定方法
    • プロポーザル形式
  • 目的
    • エキサイトHD技術職の技術的成長
    • 外部カンファレンスに向けた練習の場

セッション内容

セッションタイトル
セッション1 相談系サービスをがんばってリビルドしている話
セッション2 phpで継続的にアーキテクチャを維持していく
セッション3 KUROTENのGoのパッケージ構成の失敗遍歴と現在を考える
セッション4 AWSのサーバーコストを10000ドル下げるために取り組んだこと
セッション5 TerraformをやめてCDKでReStartした後、
CDKをやめてCDK for TerraformでReStartした話
ランチタイム ききソースコード
セッション6 KUROTENのフロントエンドを支える技術
セッション7 iOS / Android ネイティブ実装アプリのFlutter化事例
セッション8 デザインの基礎知識
セッション9 HTTPセッションを完全に理解する20分
セッション10 認証技術のこれまでとこれから
LT1 抽象的なデザインの課題は言語化で解決!
LT2 ノーコードのすすめ
LT3 APIのエンドポイント設計をする時に考えていること
LT4 Github Copilotを使って開発生産性を上げる
LT5 OpenAPIを使った、アプリ用コードの自動生成(再)
LT6 開発体制を見直す
LT7 IPoE沼の淵に立ってみる
LT8 AI画像生成と著作権:基本原則と実践的ガイドライン
LT9 チーム内のUIデザインのコミュニケーションを円滑にする
Figmaの機能「Variants」をおさらい!
LT10 自動化による家族への思いやり
LT11 開発生産性を可視化してみた。
LT12 リビルドをしていく中で印象に残った実装3選
LT13 AItuberをやってみる

目的

開催目的は去年と変わりませんが改めて記載します。
キーワードは「成長」と「挑戦」です。

技術組織活性

本カンファレンスの参加者が技術的に成長することが目的の1つです。
日頃のインプットをアウトプットすることで、より自分に知識を浸透させることができるでしょう。

また、資料の作成&発表を行うことで説明する力を鍛えることもできます。
その発表を聞き、知らない知見を得ることができるようなカンファレンスになったと思います。

更に今年は発表者はオフライン発表、会場もオフライン開催の場を設けることで、他の事業部との交流もすることができました。
これにより、組織活性への貢献にもなっているのではないでしょうか。

外部カンファレンスへの登壇の練習

そして、もう1つの目的は「外部カンファレンスへの登壇の練習」です。

弊社はこれまで様々なカンファレンスに協賛させていただいてきました。
協賛させていただくことでカンファレンスの成功に貢献できることは光栄なことではありますが、やはりエキサイトから登壇者をもっと出したい(せめてプロポーザルを出したい)という思いがあります。
とは言え、人前で発表するのはとても緊張しますし、準備も大変です。
こんな発表でいいんだろうかと考えてしまったり、ネタが無いと思ってしまったり、そうして登壇まで至らないという現状です。

そこで、外部カンファレンスへの登壇の足がかりとして練習の場として、思いっきり社内を巻き込んでしまえというのがもう1つの目的です。
完全に身内だけなので外部カンファレンスほど緊張はしないですし、
内容もいつもの小規模なLT会ではなく大規模なカンファレンスっぽいものとすることで外部カンファレンス登壇のハードルを越えるお手伝いを目指しました。

工夫した点

オフライン開催

今年はオフラインをメインに開催しました。
去年、一昨年はオンラインメインで皆さん、Zoomでの参加がメインでしたが今年は発表会場を設けることによって、よりカンファレンスに近い形をとりました。

オフライン開催
※ プライバシーの観点から一部ぼかし処理を入れています

パブリックビューイング

オフライン会場に入りきれなかった場合、
弊社カフェスペースを利用しパブリックビューイングの形で発表を見れるようにセッティングしました。

パブリックビューイング
※ プライバシーの観点から一部ぼかし処理を入れています

ケータリングとお菓子

今年はオフライン参加メインと言うことで、ケータリングやお菓子、ジュースを用意しました。
ケータリングは一瞬で無くなる大好評。
お菓子は……ジュースのほうが売れたので来年も継続するのであればジュースを多めにしたいなと思いました。

また、お菓子とは別途、カンファレンスのロゴを入れたチロルチョコも準備。
可愛いですね!

チロルチョコとジュース

ケータリング

オンライン参加

今年もオンラインでの参加者を対象に、Zoomを用いてウェビナー形式の配信を行いました。
しかし、去年、一昨年と一味違うことがしたかったので、OBSを用いた配信にしました。
これにより、画面共有ではなくリッチな画面を使った配信を行うことができました!
視聴してくださった方からも絶賛のコメントをいただけたので良かったと思います。

配信画面

OBS配信画面

※ プライバシーの観点から一部ぼかし処理を入れています

ノベルティ

去年、一昨年はTシャツをメインのノベルティとしていましたが、
カンファレンスのTシャツは、カンファレンスが終わるとなかなか着る機会がないなと思い、今年は思い切ってアクリルキーホルダーとネックストラップにしてみました。

これですと、カンファレンスが終わっても社員証入れに飾ることができるのでいいなーと。
すでに何人か、社員証のネックストラップにアクリルキーホルダーをつけてくださっていました!
嬉しいですね。

ノベルティ

所感と今後の展望

ベースはこれまでのTechConにしつつ、今年は様々なことにチャレンジ(ReStart)できたのかなと思います。
発表してくださる方のレベルも年々上がってきていて、このまま社外も巻き込んだカンファレンスにできるといいなという願望もあります。
最後に、発表者の方、そして運営としてTechConを支えてくださった皆様に感謝の意を表し、今年のTechConのまとめとさせていただきます。

SpotBugsが可変オブジェクトでないものを可変オブジェクトと判定してしまう場合の対処法

こんにちは、エキサイト株式会社の平石です。

今回は、SpotBugsで可変でないオブジェクトが可変であると判定されてしまう問題に対処する方法をご紹介します。

はじめに

SpotBugsは、Javaのプログラムの中のバグや脆弱性(につながると考えられるもの)を検知してくれるプログラムです。

膨大なプログラムをレビューや手動で記述したテストで検証するのは大変ですので、自動で検証をおこなってくれるSpotBugsは有用なツールです。

しかしながら、時にSpotBugsは「バグではない箇所」を「バグを含んでいる」と判定して、警告を出すこともあります。
いわゆる「False Positive」と呼ばれるもので、「バグを正常であると判定して見逃してしまうよりは、怪しい箇所はバグとして判定して警告してしまった方が良い」という考え方から、そのような仕様になっているのだと考えられます。

コード例と発生する警告

今回は、以下のようなコードでSpotBugsが警告を発しました。

public interface NewsArticleService {
    void registerNewsArticle(String title, String body);
}
@Service
@RequiredArgsConstructor
public class NewsArticleServiceImpl implements NewsArticleService {
    private final NewsArticleRepository newsArticleRepository;

    @Override
    public void registerNewsArticle(final String title, final String body) {
        newsArticleRepository.insertNewsArticle(title, body);
    }
}
public interface NewsArticleRepository {
    void insertNewsArticle(String title, String body);
}
@Repository
public class NewsArticleRepositoryImpl implements NewsArticleRepository {

    @Override
    public void insertNewsArticle(final String title, final String body) {
        // ニュース記事をデータベースに登録する処理
    }
}

ニュース記事のService層で、データベースにニュース記事をInsertしにいく処理を呼び出しています。
その処理の詳細を記述したRepositoryはLombok@RequiredArgsConstructorで自動生成されるコンストラクタでDIしています。

このコードを含んだ状態でSpotBugsを実行すると以下のような警告が出ます。

要するに、「newsArticleRepositoryは可変オブジェクトであり、この参照をそのままNewsArticleServiceに取り込むことで外部からnewsArticleRepositoryを操作できてしまう」という警告です。

しかし、NewsArticleRepositoryクラスにはそのクラスの内部のデータを変更するような機構は定義していません。
ただデータベースにニュース記事を登録するメソッドがあるだけです。
これは、一体どういうことでしょうか?

原因

この問題の原因は、SpotBugsのソースコード内の以下の記述にあるようです。

    private static final List<String> SETTER_LIKE_PREFIXES = Arrays.asList(
            "set", "put", "add", "insert", "delete", "remove", "erase", "clear", "push", "pop",
            "enqueue", "dequeue", "write", "append", "replace");

    〜 略 〜

    public static boolean looksLikeASetter(String methodName, String classSig, String retSig) {
        if (Objects.equals(classSig, retSig)) {
            return false;
        }

        return SETTER_LIKE_PREFIXES.stream().anyMatch(name -> methodName.startsWith(name));
    }

looksLikeASetterメソッドは検証対象のクラス内のメソッドが、SETTER_LIKE_PREFIXESに含まれる文字列で始まっていればtrueを返します。
このメソッドが、オブジェクトが可変かどうかの判定に使われているようです。

確かに、NewsArticleRepositoryクラスはSETTER_LIKE_PREFIXES内に含まれるinsertという文字列で始まるメソッドinsertNewsArticleを持っています。
これが、newsArticleRepositoryが実際には可変オブジェクトではないにも関わらず、可変オブジェクトと判定された理由です。

対処法

前節から、この警告はFalse Positiveであり、実際には何も問題を引き起こさないことがわかりました。
特に何も対処せずに警告を無視することもできますが、毎回SpotBugsの実行でエラーが発生するのは鬱陶しいですし、その他の有用な警告が埋もれてしまう可能性もあります。
そのため、このような警告は抑制するように設定するのが良いでしょう。
その方法はいくつか存在します。

メソッド名を変更する

今回は、メソッド名がinsertという文字列で始まっていることが原因です。 そのため、例えばメソッド名をinsertNewsArticleからregisterNewsArticleに変更すれば、警告は発せられなくなります。

Lombokの設定で抑制する

これは、今回のケースでしか使えない方法ですが、Lombokの設定でLombokによって生成されたコードではSpotBugsの警告は発せられないようにすることができます。

lombok.configに以下のような記述を追加します。(lombok.configが存在しない場合には追加してください。)

lombok.extern.findbugs.addSuppressFBWarnings = true

アノテーションを付与する

Lombokを利用していない場合には、こちらの記事で紹介されている方法で一つ一つ抑止することもできます。

tech.excite.co.jp

@SuppressFBWarnings(value = {"EI_EXPOSE_REP2"})

SpotBugsの設定ファイルに記述する

一つ一つアノテーションを付与するのが面倒な場合には、SpotBugsの「フィルタファイル」というファイルに警告を抑止するような設定を記述することもできます。

フィルタファイル — spotbugs 4.8.3 ドキュメント

例えば、コンストラクタではEI_EXPOSE_REP2の警告を発しないようにするためには、適当な名前をつけたXMLファイルに以下のように記述します。

<?xml version="1.0" encoding="utf-8" ?>
<FindBugsFilter ...>
    <Match>
        <Method name="&lt;init&gt;"/>
        <Bug pattern="EI_EXPOSE_REP2" />
    </Match>
</FindBugsFilter>

次に、SpotBugsにこの設定ファイルを認識させます。
Gradleのプラグインを利用している場合には、以下のように記述します。

spotbugs {
    excludeFilter = rootProject.file("path/filename")
}

そもそも可変データの検出を行わないようにする

リスクはありますが、そもそも可変データの検出を行わないようにすることもできます。
社内の内部APIのように使用法を制御できるような場合には、このような設定を検討しても良いでしょう。

以下は、Gradleプラグインで設定する場合を利用している場合の例です。

spotbugs {
    omitVisitors = ["FindReturnRef"]
}

終わりに

今回は、SpotBugsで可変でないオブジェクトが可変であると判定されてしまう問題とその対処法をご紹介しました。

可変オブジェクトの判定の方法はやや乱暴に感じてしまう部分もありますが、GitHubのissueが立ってから2年以上Openのままであることから、良い判定方法は見つかっていないようです。

当分は、紹介したような警告を抑止する対処法でしのぐしかなさそうですね。

では、また次回。

参考文献

SpringBootでAWSのCredentialsを簡単に切り替える

こんにちは、エキサイト株式会社の平石です。

今回は、SpringBootでローカル環境からAWSサービスにアクセスする際にCredentialsを簡単に切り替える方法をご紹介します。

はじめに

Spring Bootでローカル環境からAWSサービスにアクセスすることは度々あるかと思います。

S3からファイルを読み込む、パラメータストアから機密性の高いデータを取得するなどなど.....。

このような場面で、AWSサービスにアクセスする際にはアクセスキーを利用することが多いでしょう。
アクセスキーをローカルのcredentailsファイルに保存しておき、その情報をSpring Bootに読み込ませます。
アクセスキーが一つしかない場合には、デフォルトに設定しておけば自動で読み取ってくれます。

しかし、一つのPCから複数のAWSアカウントを利用している場合や、権限ごとにIAMユーザーを切り替えている場合には、credentialsファイルに複数の設定やアクセスキーが必要になります。
その際に、利用場面ごとに使用するcredentialsを指定するにはどうすればよいのでしょうか。

私も、業務でプロジェクトごとに自動で認証情報を切り替える必要があったため、備忘録も兼ねて記事として残そうと思います。

準備

まずは、IAMユーザーのアクセスキーを生成し~/.aws/credentialsファイルと~/.aws/configファイルに認証情報と設定を記述します。

直接記述しても良いですが、AWS CLIの以下のコマンドを実行してプロンプトに沿って入力していくと楽です。
sample-profile1の部分はご自身が設定したいプロファイル名に変更してください。)

aws configure --profile sample-profile1

すると、~/.aws/credentialsファイルと~/.aws/configに以下のように設定されます。

[profile sample-profile1]
region = ap-northeast-1
output = json

[profile sample-profile2]
region = ap-northeast-1
output = json
[sample-profile1]
aws_access_key_id = {access_key1} # 設定した内容が記述されている
aws_secret_access_key = {secret_access_key1}

[sample-profile2]
aws_access_key_id = {access_key2}
aws_secret_access_key = {secret_access_key2}

次に、Java側でAWSを利用するための依存関係を追加します。
Gradleを利用する場合には、build.gradleに利用するSpringBootの依存関係に加えて以下のような依存関係を追加します。
バージョンは適宜変更してください。

dependencies {
    implementation platform("io.awspring.cloud:spring-cloud-aws-dependencies:3.1.0")
    implementation "io.awspring.cloud:spring-cloud-aws-starter-parameter-store"
}

今回は、"io.awspring.cloud:spring-cloud-aws-starter-parameter-store"を追加していますが、Starter Dependencyの一覧を参考にご自身が利用されるサービスに対応した依存関係を追加してください。

認証情報の設定

デフォルトでは、~/.aws/configファイルと~/.aws/crendentialsファイルで[profile default]と指定したプロファイルの認証情報を使って、AWSサービスにアクセスしにいきます。

[profile default]
region = ap-northeast-1
output = json
[default]
aws_access_key_id = {access_key_default} 
aws_secret_access_key = {secret_access_key_default}

したがって、別のアクセスキーを使いたい場合には、defaultのプロファイルを毎回切り替える必要があり面倒です。

そのような場面に対処するために、「~/.aws/crendentialsに記述したプロファイルの中の、どのプロファイルを利用するか」を指定するための設定が用意されています。

application.ymlなどの設定ファイルに以下の設定を追加します。

spring:
  cloud:
    aws:
      credentials:
        profile:
          name: sample-profile1  # aws configure コマンドの --profile オプションで指定した名前
          path: ~/.aws/credentials  # デフォルト値が`~/.aws/credentials`なので、変更していない場合は記述しなくてよい

このようにすれば、例えば同じGradleプロジェクト内の異なるサブプロジェクトで、別の認証情報を利用することも可能です。
そのサブプロジェクトの設定ファイルで利用するプロファイルをsample-profile2にすれば良いのです。

credentialsファイルのパスを変更している場合は、spring.cloud.aws.credentials.profile.pathにそのパスを記述すれば、そのファイルを読みにいくようになります。

なお、バージョン管理せずに完全にローカルだけで利用するのであれば、アクセスキーとシークレットアクセスキーを直接指定することもできるようです。

spring:
  cloud:
    aws:
      credentials:
        access-key: {access_key1}
        secret-key: {secret_access_key1}

あまり使う場面は思いつきませんが、一時的なテストの時には便利だったりするのでしょうか。

終わりに

今回は、SpringBootでAWSのCredentialsを簡単に切り替える方法をご紹介しました。

では、また次回。

参考文献

OpenAPI GeneratorでJavaのAPIクライアントを自動生成する

こんにちは、エキサイト株式会社の平石です。

今回は、OpenAPI Generatorを利用してJavaAPIクライアントを自動生成する方法をご紹介します。

はじめに

アプリケーションを開発していると、自社(自身)で開発したAPIを利用することがあります。

例えば、自社で利用している複数のサービスに共通のAPIがあり、実際のサービスや機能を実装するための処理でそのAPIを呼び出す場合が考えられます。

このような時、「HTTPクライアントを用意」、「エンドポイントと必要なパラメータを確認」、「パラメータ名を間違えないようにセット」などの面倒なことをAPIを呼び出す度に行わなければなりません。
また、前述のような共通のAPIを呼び出す場合には、同じような処理が複数のサービスや機能のソースコードに出現してしまいます。

そのため、APIクライアントのコードを自動生成できると開発の効率が良くなることが期待できます。

OpenAPI Generatorとは

OpenAPIAPIの仕様を記述するためのフォーマットのようなものです。
これに従って「必要なパラメータ」や「レスポンスの形式」「認証の方法」のようなAPIに関する情報を、YAMLJSON形式で記述することでドキュメントを作成することができます。

OpenAPI Generatorは、OpenAPIの仕様に則って作成されたドキュメントから、APIクライアントコードやAPIを実装するための雛形となるコードを自動生成してくれます。

様々な言語に対応していますが、今回はJavaAPIクライアントを自動生成していきます。

OpenAPIの仕様に則って作成されたドキュメントから自動生成するため、呼び出し先のAPIはどの言語で記述されていても問題ありません。

準備

環境

今回は以下のような環境を利用します。

使用するAPIドキュメント

今回は以下のようなYAML形式で記述されたAPIドキュメントからAPIクライアントを生成します。

openapi: 3.0.1
info:
  title: OpenAPI definition
  version: v0
servers:
- url: http://localhost:8190
  description: Generated server url
paths:
  /user:
    get:
      tags:
      - user
      summary: ユーザー取得
      operationId: getUser
      parameters:
      - name: userId
        in: query
        description: ユーザーID
        required: true
        schema:
          type: string
          description: ユーザーID
          example: 11111111
        example: 11111111
      responses:
        "200":
          description: 正常に処理が終了した場合
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserResponseDto'
components:
  schemas:
    UserResponseDto:
      title: ユーザーのレスポンス
      required:
      - email
      - userId
      - userName
      type: object
      properties:
        userId:
          title: ユーザーID
          type: string
          example: "11111111"
        userName:
          title: ユーザー名
          type: string
          example: 白金 高輪
        email:
          title: メールアドレス
          type: string
          example: takanawa.shirokane@example.com

http://localhost:8190/userに対してuserIdというクエリパラメータを指定すると、userId、userName、emailの3つを含むレスポンスが返ってくることを表しています。

このようなファイルを自力で記述するのは大変ですが、既に実装したAPIから自動生成することもできます。
JavaのSpring Bootでの方法については、以下の記事の「OpenAPI仕様のドキュメントの自動生成」の部分を参考にしてください。

tech.excite.co.jp

このファイルにtest-api-docs.yamlという名前をつけて、プロジェクトルート内のspecsディレクトリに置いておきます。

依存関係の追加と設定を行う

自動生成を行う方法はいくつかありますが、今回はGradleのプラグインを利用します。

build.gradleは以下の通りです。

buildscript {
    repositories {
        mavenCentral()
    }

    dependencies {
        // OpenAPI Generatorのプラグイン
        classpath "org.openapitools:openapi-generator-gradle-plugin:7.2.0"
    }
}

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.1'
    id 'io.spring.dependency-management' version '1.1.4'

}

apply plugin: 'org.openapi.generator'

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
    sourceCompatibility = '21'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
    useJUnitPlatform()
}

基本的には、OpenAPI Generatorのプラグインを入れているだけです。

ここに、生成の際の設定を追加することができます。
build.gradleに以下のブロックを追加してください。

openApiGenerate {
    generatorName.set("java")
    inputSpec.set("$rootDir/specs/test-api-docs.yaml")
    outputDir.set("$projectDir/clientgen")
    apiPackage.set("com.example.clientgen.api")
    invokerPackage.set("com.example.clientgen.invoker")
    modelPackage.set("com.example.clientgen.model")
    configOptions.set([
         groupId: "com.example",
         artifactVersion: "0.0.1-SNAPSHOT",
    ])
}

それぞれの設定項目を解説していきます。

generatorNameGeneratorのリストにある、Generator名のどれかを設定します。
今回は、JavaAPIクライアントを生成したいので、javaを設定しています。

inputSpecoutputDirはそれぞれ生成に利用するOpenAPIのフォーマットに沿ったファイルのパスと、生成するコードの出力ディレクトリを指定します。

apiPackage, invokerPackage, modelPackageには生成するコードを出力する具体的なパッケージを設定します。

これらはOpenAPI GeneratorのGradleプラグインの設定項目であり、その他の設定項目はOpenAPI GeneratorのGradleプラグインの公式ドキュメントにあります。

反対にconfigOptionsでは、OpenAPI GeneratorでJavaクライアントを生成する際の共通の設定を行なえます。

groupIdartifactVersionは、生成されるbuild.gradlepom.xmlファイル内に記述されるgroupIdversionに対応しています。

他にも様々な設定項目が用意されていますので、詳細は公式ドキュメントをご確認ください。

実際に生成してみる

ここまで記述できたら、Gradleプロジェクトのビルドを行います。

./gradlew build

ビルドが完了すると、OpenAPI GeneratorのGradleプラグインにより、openApiGenerateというタスクが実行可能になっています。
これを実行すると、outputDirに設定したディレクトリ(ここではclientgen)に自動生成ファイルが生成されます(ディレクトリが存在しない場合は作成される)。

./gradlew openApiGenerate

clientgenディレクトリには、主に自動生成されたクライアント自体の依存関係が記述されたbuild.gradlepom.xmlと、実際のコードが含まれるsrcディレクトリがあります。
srcディレクトリ内を辿ると、api, invoker, modelの3つがあり、それぞれ以下のようなコードが存在しています。

  • api : test-api-docs.yamlから生成されたAPI
  • invoker : HTTPクライアントや例外クラス、認証まわりなどの実際にAPIを呼び出すために必要なコード
  • model : APIのリクエストやレスポンスのためのモデル

なお、生成されるJavaコードやその依存するライブラリ等は、最新版よりやや古いものもあるため注意が必要です。

実際に使ってみる

生成したクライアントを最も手軽に利用する方法は、README.mdにあるようにMavenのローカルリポジトリにインストールする方法でしょう。
ローカルリポジトリMaven Centralのローカル版のようなものであり、デフォルトでは$HOME/.m2/repositoryにあります。

Mavenをインストールする必要があるので、まだインストールされていない方はこちらからインストールしてください。

outputDirに設定したディレクトリに移動し、以下のコマンドを実行してMavenのローカルリポジトリに生成したAPIクライアントをローカルリポジトリにインストールします。

mvn clean install 

build.gradleに以下を追記します。

〜〜 略 〜〜

repositories {
    mavenCentral()

    // ローカルリポジトリの設定
    mavenLocal()
}

〜〜 略 〜〜

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'

     // 依存関係を追加
    implementation "example.com:openapi-java-client:0.0.1-SNAPSHOT"
}

〜〜 略 〜〜

プロジェクトを再ビルドし、以下のようなコントローラーを作成します。

import com.example.clientgen.api.UserApi;
import com.example.clientgen.invoker.ApiException;
import com.example.clientgen.model.UserResponseDto;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {

    @GetMapping("user")
    public String getUser() {
        try {
            final UserResponseDto response = new UserApi().getUser("11111111");

            return String.format("Name: %s, Email: %s", response.getUserName(), response.getEmail());
        } catch (ApiException e) {
            throw new RuntimeException(e);
        }
    }
}

SpringApplicationを起動し、/userにアクセスすることで、内部でAPIクライアントがtest-api-docs.yamlで記述したAPIを呼んでくれます。

結果は、例えば以下のようになるでしょう。

終わりに

OpenAPI Generatorを利用することで、API呼び出しコードを自動で生成してくれます。
Java以外の言語でも生成できるので、ぜひ活用してみてください。

別のブログで、自動生成されたものを実際に活用する方法を執筆する予定です。

では、また次回。

参考文献

「potatotips #86 iOS/Android開発Tips共有会」に参加してきました!

エキサイト株式会社の@mthiroshiです。

potatotips #86 にてLT発表をしてきました。その内容をレポートします!

potatotips #86

potatotips は iOS / Android アプリ開発者向けの勉強会です。

potatotips.connpass.com

#86 は コネヒト株式会社 さん主催で、オンライン・オフラインのハイブリッド開催でした。

会場のコネヒトさんは、エキサイトと同じオフィスビルでしたので、個人的に参加しやすくて助かりました 😁

LTのセッションリストは、下記をご参照ください! github.com

Xでの盛り上がりは、下記のトゥギャッターをご覧ください! togetter.com

懇親会では、アルコール類やピザ等の軽食もご用意していただきました!楽しい会となりました! (写真を撮り忘れてしまったので、こちらのXのポストを引用させてもらいます。)

私のLTスライドはこちらです。Flutterの flutter_sercure_storage パッケージの紹介とAndroid バックアップ機能との利用について注意点を話しました。

感想

個人的には、ネイティブアプリ開発の最新動向を把握できていなかったため、両 OS について幅広く情報をキャッチアップできる機会となりました。特に、iOS のデータ永続化ライブラリである SwiftData に初めて触れることができました。以前に Realm を業務で使用した経験はありましたが、公式のライブラリ提供には安心感を覚えました。懇親会では、各プラットフォームの技術の違いや、他社で採用されている技術について話を聞くことができ、多くの学びがありました。

LT 発表では、久しぶりの発表機会であり、オフラインで雰囲気を共有することのやりやすさを改めて感じました。X でもリアクション頂けたことが嬉しかったです。

potatotips 運営チームの皆様、コネヒト株式会社の皆様、素敵な勉強会をありがとうございました!

採用情報

エキサイトではエンジニアを随時募集しております。ご興味ございましたら、下記の募集一覧ページをご覧ください!

www.wantedly.com

[Java]バーチャルスレッドを導入する方法 [SpringBoot]

はじめに

こんにちは、新卒1年目の岡崎です。Java19でプレビュー機能として提供されていたバーチャルスレッドが、Java21で正式機能として提供されました。今回は、このバーチャルスレッドをSpringBootに導入する方法を紹介します。

バーチャルスレッドについて詳しく知りたい人は、公式ドキュメントをご覧ください。

docs.oracle.com

環境

openjdk version "21.0.2" 2024-01-16 LTS
OpenJDK Runtime Environment Corretto-21.0.2.13.1 (build 21.0.2+13-LTS)
OpenJDK 64-Bit Server VM Corretto-21.0.2.13.1 (build 21.0.2+13-LTS, mixed mode, sharing)
  • Spring boot
.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.2.1)

設定方法

application.ymlに以下の実装を行います。

spring:
  threads:
    virtual:
      enabled: true

これだけでバーチャルスレッドを使用することができます。

確認方法

実際にバーチャルスレッドが使用できるかどうかを確認します。

@RestController
@RequiredArgsConstructor
public class TestController {
    @GetMapping("test")
    public String getThread() {
        return Thread.currentThread().toString();
    }
}

結果

VirtualThread[#97,tomcat-handler-0]/runnable@ForkJoinPool-1-worker-1

バーチャルスレッドが使われていたことが確認できました。

最後に

今回は、バーチャルスレッドをSpringBootに導入する方法を紹介しました。皆さんも使ってみてください。

最後に、エキサイトではデザイナー、フロントエンジニア、バックエンドエンジニア、アプリエンジニアを絶賛募集しております!

興味があればぜひぜひ連絡ください!

www.wantedly.com

AWSのCIDRの指定と、VPC/サブネットにどうIPアドレスが割り当てられるのかについて

エキサイト株式会社メディア事業部所属のエンジニアの岩藤です。

AWSで新しくサービスを作る時に、必ずVPCの作成とサブネットの作成を行うと思います。

CIDRの書き方と、実際にVPCとサブネットにどうIPが割り当てられるかが、今更ながらに理解できてなかったので備忘録です。

CIDRとは

CIDR(サイダー)とは IP アドレス割り当て方法です。

詳しくは下記を参考。

CIDR とは何ですか? - CIDR ブロックと表記の説明 - AWS

CIDR(サイダー)で指定されるIPアドレスの集まりを、CIDRブロックといいます。 CIDRブロックは、111.111.111.111/22のような形式で指定します。 これをプレフィックス表記といいます。

プレフィックスによって、幾つIPが割り当てられるのか

プレフィックス表記の、xxx.xxx.xxx.xxx/ここの数字によって割り当てられるIP数が変わってきます。

awsで固定で使うIPがあるようなので、プレフィックスが/22で1,024個となってもそれが全て使えるわけじゃない事だけ注意。

VPCに実際にどうIPが割り当てられるのか

例えばですが、VPCのCIDRが111.111.60.0/22の場合、下記のIPが割り当てられます。

サブネットのCIDR指定と、どうIPアドレスが割り当てられるかの例

今回は、下記の内容でサブネットを作成するとします。

作成する、アベイラビリティーゾーン(AZ)

AZに作成するprivateサブネットと、publicサブネットにそれぞれいくつIPを割当てるか

その場合、VPCが先ほどの111.111.60.0/22だった場合、CIDRと割当てられるIPアドレスは下記となります。

privateサブネット

publicサブネット

最後に

これさえ理解できていれば、新規サービスを担当した時に VPC/サブネットに対して、プレフィックスをいくつ申請すればいいのか、 どう割当てればいいのか迷わないと思います。

参考してもらえますと幸いです。

Spring BootにおけるRedisのPrimary/Replicaノード判定が自動だったという話

こんにちは。 エキサイト株式会社の三浦です。

以前、Spring Bootにおいて、RedisのPrimary(Master)とReplicaのノードをそれぞれ呼び分けて使うことができる、という記事を書きました。

tech.excite.co.jp

今回はそれに関連して、実はPrimaryノード・Replicaノードの判定が、「コードでの追加順」ではなく「自動」だったというお話をしていきます。

Spring Bootにおける、RedisのPrimary / Replicaノードの呼び分け

Spring Bootでは、Redisを使う際に Primary / Replicaノードを以下のコードで呼び分けることができます。

package sample;

import io.lettuce.core.ReadFrom;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStaticMasterReplicaConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;

@Configuration
@EnableCaching
public class RedisConfig {

    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {

        // Primaryノードの設定
        String primaryHost = "localhost";
        Integer primaryPort = 63791;

        // Replicaノードの設定
        String replicaHost = "localhost";
        Integer replicaPort = 63792;

        Integer database = 0;

        LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
                .readFrom(ReadFrom.REPLICA_PREFERRED)
                .build();

        RedisStaticMasterReplicaConfiguration serverConfig = new RedisStaticMasterReplicaConfiguration(primaryHost, primaryPort);
        serverConfig.addNode(replicaHost, replicaPort);
        serverConfig.setDatabase(database);

        return new LettuceConnectionFactory(serverConfig, clientConfig);
    }
}

詳しくは、以前書いた以下の記事を御覧ください。

tech.excite.co.jp

これを実行すると、実際の設定値は以下のようになっています。

想定通り、 port: 63791 のノードがPrimary、 port: 63792 のノードがReplicaとして判定されていることがわかります。

ここで一つの疑問が生じました。

このPrimaryとReplicaの判定は、コードでの追加順で決まるのでしょうか?

PrimaryとReplicaの判定は自動的に行われる

それでは今度は、追加順を逆にしてみましょう。

package sample;

import io.lettuce.core.ReadFrom;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStaticMasterReplicaConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;

@Configuration
@EnableCaching
public class RedisConfig {

    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {

        // Primaryノードの設定
        String primaryHost = "localhost";
        Integer primaryPort = 63791;

        // Replicaノードの設定
        String replicaHost = "localhost";
        Integer replicaPort = 63792;

        Integer database = 0;

        LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
                .readFrom(ReadFrom.REPLICA_PREFERRED)
                .build();

        // 追加順を逆にしてみる
        RedisStaticMasterReplicaConfiguration serverConfig = new RedisStaticMasterReplicaConfiguration(replicaHost, replicaPort);
        serverConfig.addNode(primaryHost, primaryPort);
        serverConfig.setDatabase(database);

        return new LettuceConnectionFactory(serverConfig, clientConfig);
    }
}

結果はこうなります。

ノードの追加順を変更したことでリストの要素の順番は変わっていますが、なんとPrimary / Replica判定は正しく行われています!

このことから、ノードの追加順でPrimary / Replicaが判定されているのではなく、実際にノードの内容から自動的にPrimary / Replicaの判定がされていることがわかりました。

最後に

自動的に判定が行われるということで、アプリケーション開発者としては気にしなければいけないことが一つ減り、良いことなのではないかなと思います。

負荷の分散という意味でノードを分けることは非常に効果的なので、ぜひやってみてください!