kazokmr's Blog

試したこと、読んだこと、見たこと、聴いたことを書きたくなったら書くブログ

JJUG CCC 2021 Spring 参加レポート

前回の参加が、2019 Spring だったので2年振りです。
簡単に聴いたセッションの感想を書きます。

ccc2021spring.java-users.jp

フロントエンド・バックエンドの分離の道のり

fortee.jp

既存システムで JSP + カスタムタグ + JavaScript で開発していたフロントエンドを React に変更して、フロントエンドとバックエンドの開発を明確に分離するまでのお話。

JSPで書いていたものを全てReactで書くのは難しく「画面の共通フレームの部分はJSPのまま、コンテンツ部分をReactで書く。」「初回のリクエストはJSPで返し、以後はReactで非同期で対応する。」などの過渡期の部分の話が聞けたのが面白かった。

フロントエンドの方が採用技術が大きく変わったのでこちらよりの話が多い感じがしたので、バックエンド側で行った話ももう少し聞いてみたかった。

今どき?の Java における例外処理についての考察

fortee.jp

Javaの例外の基本から始まり、従来の例外処理とコーディングでの課題を挙げ、他の言語だとどのような書き方ができるかを紹介して、最後は Java16 そして Java17 になるとどのように例外処理が書けるかというお話。

最近Kotlinを勉強したというのもあったので関数型プログラミングでの書き方についても大体理解しながら聴くことができた。

次のLTSになる Java17 だと Switch式, record, sealed class などを駆使すると、どのように例外処理が書けるかを見たら、早く使ってみたいって気持ちになった。

OpenID Connect 1.0 with Spring Security

fortee.jp

ちょっと前に自社で、SAML, OAuth2.0, OIDC を調べていて、Spring Securityのリファレンスページ や Auth屋さんの本を読んでいたので、答え合わせをするようなつもりで聴いてみました。

アクセストークンを「切符」で例えて説明されたり、重要な言葉を説明する時に強弱をつけて説明されるので多田さんの話はいつも聴きやすいですね。(この辺は自分も参考にしたいです。)

(OAuth認証の問題の部分については、私自身も他人に説明できるほど理解できていないですが、
本来は認証が成功することでアクセスが許可されるログインユーザーの認証情報に、OAuthの認可の仕組みでアクセスできるようにすることで認証と同等の結果が得られるのが、OAuth認証かなと思っています。
この使い方には明確な規約が無いので、正しく取り扱わないとなりすましログインなどの脆弱性を引き起こすので、OAuthに認証情報を取り扱うときのルールを定めたのがOIDCなのかなと雑に理解しています。)

またSpring Securityだと、OAuth 2.0 Login の機能に OIDCも実装されているので、リファレンスを読んでもOIDC自体のことが分かりにくいんですよね。逆にいえばSpring Security だとOAuth認証とOIDCの違いを意識しなくても使いやすくなっているということかもしれませんが。

サーバーレスAPIをKotlinで開発してみよう

fortee.jp

サーバーレスアプリケーションを構築する機会は無いのですが、Kotlinを勉強したばかりというのもあり聴いてみました。

SAMはAWSのサーバーレスアプリケーション開発をサポートするツールなのでAWSでサーバレスアプリをデプロイするまでを簡単に行える感じなのが面白そうでした。

また普段はそこまで気をつけていない、リソース量や初期化時間を考えて設計が大事なこと、テストもLambdaに依存する所と依存しない部分に分けて行うなどの説明がわかりやすく、機会があれば触ってみたいなと感じるセッション内容でした。

JFR などのツールを用いて FullGC や OOME の原因を特定する流れ

fortee.jp

JFR や JMC、Memory Analyzerを使った問題解析・原因分析を行う手順を紹介するセッション

前職だと、JConsole、Memory Analyzer は使ったことあったのだけれど、JFR、JMCはやっぱりいいなと思いました。 今の仕事でもここまで原因調査を行う機会が無いのだけれども、いつか必要になるときに触れておきたいですね。

あとJFRは、本番稼働中は常時有効にしておくものなのかがわからなかったので、その辺を聞けばよかったです。

モダンな技術をエンタープライズ開発の現場にインストールした話

fortee.jp

エンタープライズ開発の現場で、DDD、CQS、CI/CD を取り入れた話。 自分が行っていることに近い所も多かったので興味深く聴きました。

最初のDDDについてはアーキテクチャ寄りの話が多く、せっかくのドメイン駆動なのでもっと顧客の業務に対してどのように寄り添ってコードに落とし込んだのかということをもっと聴いてみたかった。

次のjOOQにしても、知識の幅を広げるという意味で「Mybatisは使ったことあるからjOOQを採用した。」という考えはわかるのだけども、顧客の業務フローに対してどのようにマッチしているのかみたいなことも聞けたらよかったと思いました。

最後のCI/CDは、Bitbucket Pipelines を使った事例の話でした。Bitbucket Pipelinesは仕事上よく触るし今回のようなECR、ECSと連携する処理も使ったことがあるけど、どうしてもGitHubGitHub Actionsと比べるとシェアが少ないので情報が少なかったのでこのような事例を発表していたけのがとても良かったです。
ちなみにUIテスト(E2Eテスト)は自動化しようとしたか?は個人的に聞いてみたかったです。(Selenium GridをBitbucket Pipelinesで動かそうとして上手く実行できなかったこともあったので。)

Plug-in Architectures with the Java Module System

fortee.jp

最後はAndresさんの英語セッション。セッション名は変わったとの話がありましたがタイトルは忘れてしまいました。

Javaのモジュールシステムのことはあまり知らなかったので聴いてみましたが、やはり英語を聞き取ることが中々に難しかったのと、Layrryの紹介がメインだったこともあり、正直ついていくことができませんでした。

ただ、Layrryに触れてみれば分かることもありそうな感じはしたので、今度機会があれば使ってみたい。

あとQAの時に進行役の杉山さんが「グローバルだと、モジュールシステムを意識して開発していることが多いのか?」みたいなことを質問されていたと思いますが(間違っていたらすみません。)、ちょうど Andres さんが回答した時に家族に声を掛けられて聞けなかったのが悔しかった。

全体として

まずは運営の方々やスタッフの皆様、ありがとうございました。去年の秋は参加できずオンライン参加は今回が初めてでしたが、大きなトラブルもなくセッションを聴くことができました。

ここ最近のCCCは土曜日開催だったと思うのですが、土曜日は家族のイベントが多くオンラインだと集中して見れないので難しかったので、日曜開催だったのは個人的にとても助かりました。

スポンサーブース(Spatial Chat)についてですが、本当なら参加してお話ししたりするべきだとは思ったのですが、セッションを聴いた後の合間の10分の休憩で家のことをやったりしていたので、参加できませんでした。すいません。

次回はオンとオフの両方で参加ができるようになるのかもしれないですが、多分オンラインでの参加になるかなと思います。

BDD in Action 2nd Edition 第8章メモ

第8章を読みました。この本の目次を見ると付録を除いて全15章となっているので、折り返し地点です。 現時点で9章はまだ公開されていませんが。

www.manning.com

kazokmr.hatenablog.com

8章もCucumberでテストを書くときのテクニックの説明です。
読みやすく、メンテナンスしやすく、信頼性が高いテストコードを書くために、レイヤー構造の概念を用いています。

この章のボリュームは他と比べると多くはありませんが、Cucumberを用いた 振る舞い の一部がよくわかるような内容だと思います。

レイヤー構造でテストを書く

ここで説明しているレイヤーは大きく次の3つに分かれ、上からピラミッド型で表されていました。

  1. ビジネスルール・レイヤー : フィーチャーファイルやシナリオ

  2. ビジネスフロー・レイヤー : グルーコード

  3. テクニカル・レイヤー : 実際にテスト対象アプリのUIを操作したり、REST API を送信するコード

これは「テストピラミッド」の最上位に当たる UIテスト の三角形の中を分解しているように思えた。
(図を見せて説明できないので、イメージしづらいですが。)

ビジネスルール・レイヤー

これまでの章で見てきた、Gherkin形式で書かれたシナリオを指す。
何をテストするのか、あるいは、何を期待しているのかを ビジネス側のユーザーがわかる用語や表現で記述した物。

シナリオの内容は、ここまでに行ったアクションで、ビジネス側と合意が取れているはずなので、Cucumberのテスト結果から、アプリケーションがシナリオを満たしているかどうかが把握できる。

ビジネスフロー・レイヤー

前の7章で出てきた、シナリオとCucumberでのテストをつなぐ、ステップ定義ファイルを指す。
ここで Given, When, Then の各ステップごとに定義メソッドで、受け入れテストのための準備、実行、検証を行う。

このレイヤーのポイントは ユースケース図のように アクターやアクターが操作するモノをオブジェクト化して、振る舞いを表現すること。 だと感じた。

どういうことかというと、テストコード上に アクターそのもの、そのアクターが操作するクライアントツール(WebアプリのUIならブラウザ)などを テスト専用のクラスとして定義しオブジェクトにすることで、グルーコードの中で、実際に振る舞いを表現して、シナリオを実演することができる。

つまり、テスト対象アプリケーションを利用する現実世界をモデリングして、グルーコードでシナリオを表現できるようにする。

  1. 「アクターは、ブラウザで、○○ページを開く」

  2. 「アクターは、ブラウザで、テキストに ***と入力する」

  3. 「アクターは、ブラウザで、送信ボタンをクリックする」

  4. 「ブラウザに・・・・・と出力される。」
    -> この出力内容がシナリオで期待値と合っているか検証する

グルーコードで表現するオブジェクトは、抽象的に書くことが望ましい。
そうすることで、オブジェクトの汎用性、再利用性を高め、アプリケーションを変更した時の影響を小さくすることができる。

テクニカル・レイヤー

上位のビジネスフロー・レイヤー (グルーコード)で、定義した振る舞いから、テスト対象アプリケーションのUI、あるいはREST APIなどの操作を行う。

本の中ではこのレイヤーの中も2つのレイヤーに分かれていて、上位の方は ページ、テキスト、ボタンのような抽象的なオブジェクトを用意して表現し、下位のレイヤーでそれらのオブジェクトがUI上で、どこに配置され、id属性やname属性などのプロパティを具象化する。

レイヤー化のメリット

このようにCucumberのテストコードをレイヤー構造にすることで次のようなメリットがあると思った。

変更に強い

アプリケーションの変更がどのような内容かによって、修正が必要なテストコードの影響範囲を小さくできる。

例えば、ビジネスの要件は変わらず、UI のレイアウト や REST APIのパラメータ名称だけが 変わるだけなら、最下層のレイヤーだけを修正すればいいかもしれない。

または、アクターとして表現するべき登場人物が増えるのなら、アクターのオブジェクトの種類を増やし、その分だけきぞんのテストの実行を追加するだけで良いかもしれない。

再利用しやすい

新しいビジネス要件を実装する際に、作成済みのアクターなどのオブジェクトを流用して、シナリオを具現化しやすくなる。

アプリケーションのUIが作られる前に、テストコードが書き始められる

シナリオはもちろんグルーコードやテクニカルレイヤーの一部は、アプリケーションのUIの詳細情報と疎結になるので、UIを作る前からテストを書き始めることができる。
これは、受け入れテストやエンドツーエンドテストの早期実行に繋げることができると思う。

ペルソナを使う

前述のアクターについて、TypeSafe Config ライブラリ を用いて、HOCON形式で作成した属性情報を取り込み、ペルソナとして具象化する方法が紹介されていた。

TypeSafe Config や HOCONファイルは使ったことないけど、本文中のサンプルを見る限り、単純なプロパティを羅列化したオブジェクトを用意したい場合に便利そうだと思った。

GitHub - lightbend/config: configuration library for JVM languages using HOCON files

所感

BDDやCucumberがカバーしているのは、シナリオを用いた受け入れテストやエンドツーエンドテストになるからか、テストコードを手続き的に書いてしまうことが多い。
これだと、コードが長くなって読みづらくなったり、テスト対象アプリケーションへの依存度が高くなりがちなので、レイヤー構造を採用することで、そういった欠点が解消できそうだと思った。

テストコードで書くことが、テスト対象システムの外側、つまり実際にシステムを使うユーザー自身やユーザーの振る舞い、システムのインターフェースに対する操作である点に「振る舞い駆動開発」と呼ばれる理由の1つを見た気がした。

テストコードの中で、アプリケーションを利用する現実世界を表現して書くのは、正にユースケース図の振る舞いのイメージに重なると感じた。

またこの方法だとプロダクトコードと並行してテストコードが書けることが多いので、受け入れテストの早期実行が実現できる。という点にとても共感できた。

このようなユーザー(文章中だとアクター)中心のコーディングスタイルを「スクリーンプレイ」と呼ぶそうで、これは著者が開発している、Serenityにも取り入れられている。スクリーンプレイの詳細は今後公開される章で説明があるようなので楽しみ。だけど、いつ公開されるのかな??

BDD in Action, 2nd Edition 第7章 を読んだ感想メモ

この本はまだMEAPで全部が公開されていないわけだけど、 6章の公開が去年の6月末で、7章は去年の大晦日でした。
まぁ自分の感想は、そこから更に5ヶ月近くかかっていますが。

www.manning.com

6章までを軽くおさらいすると、このような流れでシナリオをfeatureファイルに書き出しました。

  • ステークホルダーやQAエンジニアと協力して、

  • 要件を深掘りして詳細化し、

  • 実例を出して話し合いながら、

  • (テスト)実行可能なシナリオを作る。

  • シナリオはfeatureファイルに、Gherkinフォーマット(Given, When, Then) で書く。

そしてこの7章から少しずつ テストコード (受け入れテストコードと言った方がいいかも。)を書き始めるので、これまでよりもCucumberの使うためのテクニックの説明に入ります。
ですが7章と8章は、テストコードを書き方というよりは、準備やテストコード設計のための原則を説明しています。
中でも7章は、6章までで見てきたシナリオ と ステップ定義を紐づけるときのテクニック、ステップ定義の文法、Gherkinの主なキーワードの説明のような、Cucumberの基本的な使い方の説明が主な内容となっている。

尚、本文ではJavaJavaScript の2種類のパターンで説明していますが、本ブログは基本的にJavaで書くことを前提とした感想になっている。

ステップ定義 (グルーコード)

ステップ定義 (StepDefinition)は、featureファイルに書いたシナリオから、ステップ別にメソッドを定義する。
ステップとは Gherkinの Given, When, Then を指し、ステップをAnd や But で繋げている場合は、それらも1つのステップ定義としてメソッドにする。

ステップ定義メソッドには、シナリオのどのステップに対応しているかを明示するために、 アノテーション(@Given, @When, @Then) を付け、値にシナリオのステップ文を入力する。

ステップ定義は、Givenメソッドがテストの前提条件、Whenメソッドがテストの入力条件、そして Thenメソッドが結果の出力と検証(assertion) を書く。
Cucumberが シナリオのテストを実行する時は、書かれているステップの順番でメソッドを実行するみたい。

ステップ定義は、Cucumberに対して、(featureファイルの)シナリオと対応するテストコードを結びつける役割を持つので、グルーコード (glue code: glue は接着剤)と呼ばれる。

Step Definitions - Cucumber Documentation

Cucumber Test Runner クラス

Cucumber は専用のランナークラスを作成して、テストを実行する。 JavaJUnitを利用する場合は、JUnitの @RunWithアノテーションを利用して、ランナークラスを定義する。

またこのランナークラスの内部では、featureファイルのルートディレクトリ、ステップ定義のルートパッケージなどの設定も行える。

Cucumber Reference - Cucumber Documentation

ステップ定義パラメータ

シナリオのステップ文に書かれた値は、ステップ定義メソッドのパラメータに渡すことができる。

パラメータとして渡す値は、アノテーションの値に記載したステップ文で {} で囲むことでメソッドが参照することができる。{} の中は定数だけでなく、Cucumber Expression(Cucumber独自の正規表現)と 一般的な正規表現が使えるのでテストデータの値を変えたい場合はステップの文が変わらない限り、featureファイル側のシナリオの値を変えるだけで対応できる。
そのため、featureファイルのシナリオ側は、値をリストやテーブル形式で定義することができ、ステップ定義のパラメータはList型オブジェクトで受け取ることも可能。

ステップ定義のパラメータは、@ParameterTypeアノテーションを付けたカスタムクラスを利用することもできる。
シナリオの値を内包した、アクター(ペルソナ)や ビジネスの関心事(ドメイン)として、テスト用のオブジェクト用意することもできます。
Cucumberのテストに、この ペルソナやドメイン などの概念を取り入れて、テストコードをレイヤー化する話は次の8章で詳しく説明することになる。

Cucumber Expressions - Cucumber Documentation

Cucumber Reference - Cucumber Documentation

Background と Hook

Background と Hook(@Befor, @After, など) は、複数のシナリオ、あるいは フィーチャーを横断して、前述のオブジェクトを共有したり、テスト実行前の前処理あるいは後処理の工程を定義することができる。

Backgroundは、主にFeatureファイル内の複数のシナリオで共用するデータテーブルのことを指す。 このデータテーブルをステップ定義で、@DataTableTypeアノテーションを付与したメソッド内でデータオブジェクト生成し、ステップ定義のメソッドで参照できるようになる。

Backgroudは featureファイルで宣言して記載することから、このデータテーブル自体もビジネス側の関心事の1つであることを意識する。

Gherkin Reference - Cucumber Documentation

Hookは、開発/テストチームがテスト実行のために必要な前処理及び後処理を定義するときに使う。
使用事例としては、「テスト開始前にブラウザを開き、終了後にブラウザを閉じる。」といった操作、「テストの整合性のためにテスト実行時にデータ初期化する。あるいはテスト終了後に破棄する」など。

要するに「適切にテストするためには必要な工程だが、ビジネス側の関心事ではない。」ような、シナリオには現れない工程に対して宣言するのが主な使い方になる。

尚、どのシナリオでどのHookを使うかは、シナリオに任意のタグを設定し、ステップ定義メソッドのHook側のアノテーション内にタグを指定すれば、タグに紐づくシナリオを実行する時だけHook処理を行う。といった条件が設定できる。

Cucumber Reference - Cucumber Documentation

テスト環境の準備

章の最後は、テスト環境の構築についての話。

BDDが対象としているテストは、主に統合テスト、受け入れテスト、およびエンドツーエンドテストとなる。このためテストを行う際はテスト対象アプリと連携する他のシステムの構築が必要になることが多い。

データベースであれば、H2などのインメモリータイプを利用すれば実現しやすいが、本番で使うデータベースと異なったり、データベース以外にも連携するコンポーネントがあると、本番と同等環境でテストを行うことが大事になってくる。

そういった点で、テスト環境としてできる限り仮想化環境、特に最近ならDockerでコンテナ化したコンポーネントでテストを実行しようというお話でした。

この章の最後のセクションでは、テスト用のDockerコンテナを管理しやすくする、TestConteinersというオープンソースライブラリが紹介されています。

www.testcontainers.org

TestContainersは私自身も初めて聞いた気がしますが、PostgreSQLなどのRDBMS以外にも、NoSQL、Kafka、Elasticsearch といったコンポーネントを Dockerコンテナで起動し、更にそれらをJavaオブジェクトとしてテストコードで扱うことができるようです。

TestContainersを使えば、テストコードで連携するコンポーネントやテストデータを管理できるので、便利な気がします。

所感

  • ステップ定義メソッドはできるだけシンプルにしたいが、冗長化を避けて汎用的にしようとすると Cucumber Expressionなり、正規表現を多用してしまいそうな気がした。

  • 逆にステップ定義のglue code が複雑になりそうなら、シナリオを見直すことも考えておいた方が良さそう。

  • TestContainersは、連携サービスをJavaオブジェクトで一括管理できそうな気がしていて、個別にdockerfileを用意しなくても良さそうなのが便利な気がするので、今度使ってみたい。

次の8章も公開されているので、続けて読んでいきます。

JetBrains Academyの実施記録

去年の8月頃から流れでJetBrains Academyを取り組んでいた。

kazokmr.hatenablog.com

無料期間を過ぎてもキリが悪かったのでそのまま続けていて、満足いく所まで終えられたので2021年5月末でサブスクを一旦解除するので、やったことの記録をまとめておく。

注)内容が宣伝っぽくなってしまったり、未経験者の勉強のまとめみたいになっているが、今の会社に転職してからはコードを書くことが減ってしまったが、前職で10年ほど開発でガリガリ書いていました。

Jetbrains Academyは主に、未経験者向けのサービスだと思うので、正直場違いな感じで続けていましたけど、受けてみて初めて知ることも多かったので、毎日楽しく学ぶことができました。

プロフィール

hyperskill.org

去年のお盆の頃に始めて、毎日最低1日は解いていた。(毎日1問ずつ問題が出るのでそれは必ずやるようにした。)

トラック

トラックは専攻とかコースのようなもの。それぞれに決められたトピックとプロジェクトを進めていき達成していく。

今回自分がクリアしたものは、Java Developer, Kotlin Developer, Frontend Developer, Preparing for the AP Computer Science (Java), Java Desktop Application Developer の5つ

最後の2つのトラックは特に意識していなくて、Javaのトピックを全て進めていたら達成していた。

f:id:kazokmr:20210513052102p:plain
Tracks

プロジェクト

達成したプロジェクトはJava, Kotlin, Frontend(HTML, CSS, Javascript)合わせて33
難易度の内訳は、Easy : 7, Medium : 5, Hard : 9, Challenging : 12

達成したプロジェクトのコードはポートフォリオとして公開しても問題無いみたいなので、GitHubで公開している。

GitHub - kazokmr/Jetbrains-academy-Java: Jetbrains Academy Java Developerコースでクリアしたプロジェクトです

GitHub - kazokmr/Jetbrains-Academy-Kotlin: Jetbrains Academy でクリアしたKotlinプロジェクトです

GitHub - kazokmr/Jetbrains-Academy-Frontend: Jetbrains Academy の Frontendjj Developerコースでクリアしたプロジェクトです

トピック (ナレッジマップ)

トピックは1つの学習テーマで、セオリーと呼ぶ説明ページを読んだ後、プラクティスとして幾つかの問題に正解することで進めていく。

ラクティスは、選択式もあれば、実際にコードを書いてテストするタイプもある。間違っても特にペナルティは無いが、プロジェクトを達成することでもらえるGemを使ってヒントを得たり、答えを知ることもできる。

コードを書く問題の場合は、正解後に自分のコードを公開したり、他の人がアップしたコードを見ることができる。

さらには受講者同士でコメント欄で、意見や質問、ヒントを出し合ったりもできる。

自分も実際にコードをアップして、イイねといったリアクションをもらったり、「何でこんな書き方をしているの?」って質問をもらって説明したりした。

以下は、達成したトピックをカテゴリ別にマッピングしたもの

Java

Java11 の内容がベースで体系的に200近くのトピックがある。現在の

f:id:kazokmr:20210513060406p:plain
Java

Kotlin

まだβ版扱いだったのでJavaほど網羅されてはいない。スコープ関数も無かったけど進めていくうちに他のサイトを見て覚えた。
Kotlinを本格的に触るのはこれが初めてだったけど、Javaより使いやすい点もあると思ったし好きになった。

f:id:kazokmr:20210513060559p:plain
Kotlin

JavaScript

Javaコースに含まれている課題もあったので他にも行ってみた。 JavaScriptはここまで体系的に学んだことが無かったのでやってみてよかった。

f:id:kazokmr:20210513060735p:plain
JavaScript

Scala

Scalaもβ版でトピックも8つしか無かったので理解したとは言い難いが、前述のKotlinをクリアした後だったので、Kotlinの雰囲気で学ぶことはできた。

f:id:kazokmr:20210513060823p:plain
Scala

Backend / Android / Java Swing

どれも前職で関わったプロジェクトで1回以上は触ったことがあったけど、JavaScriptと同じで体系的に学んだことが無かったので良かった。これらを理解した上でもう一度あの時にプロジェクトを進められたら面白そうだと思った。

f:id:kazokmr:20210513060925p:plain

Frontend

利用期間が終了するまでに日数があったので最後にプロジェクトも含め進めた。前職の時からそうだったのだけど、CSS使って、画面のレイアウトを考えたりするのは苦手だなと改めて感じた。(大きさや位置を調整したり、どんな色がいいか考えたりする所)

HTML4 や CSS2 の頃のことしか知らなかったので、その頃より書きやすくなった気はするし、Javascriptと合わせて動的な制御を行うのは面白い。

f:id:kazokmr:20210513061005p:plain

Fundamentals

情報工学の基礎やアルゴリズムが学べ、その他にもビルドツールやGit、IDEなどの開発系ツール、データベース・SQLについて学習できる。

アルゴリズムは、大学の専攻が情報系では無かった(電気工学)だったので、一から学ぶことができて知識の底上げになった。

f:id:kazokmr:20210513061106p:plain

Math

プロジェクトを達成するために必要なものトピックだけを実施した。

英語で書かれているのもあるが、数学は大学以降はほとんど勉強していなかったから結構難しかった。
学生時代は得意科目の一つだったと思っていたけど、かなり衰えてしまっていた。

全部のトピックを取り組みたい気持ちもあったけど、それはまた別の機会にする。

f:id:kazokmr:20210513061409p:plain

まとめ

プログラミングだけでなくて、情報工学の基礎的なことも体系的に学べるので、個人的にも学生時代の復習ができた気がしてチャレンジして良かった。

セオリーも問題も全て英語なので、説明がわからないことや問題文の意味を取り違えてしまうことも多かったけど、DeepLを使って続けることができた。

今回、大体の内容をクリアできたし他に学びたいこともあるので、一旦やめるが、トピックが増えて復習したいと思ったら再開すると思う。

Dockerで起動しているHAProxyにmkcertで生成した証明書をインポートしてSSLオフロードを行う

前回、ローカル環境でHAProxyを使った分散環境が用意できたので、HTTPS接続にも対応させてみる

kazokmr.hatenablog.com

mkcert で自己証明書を生成する

mkcert というツールを使い、ローカルCA証明書を作成・インポートして、サーバー証明書を作成します。 手順は、mkcert のGitHubページに従って進めます。

GitHub - FiloSottile/mkcert: A simple zero-config tool to make locally trusted development certificates with any names you'd like.

mkcert のインストール

brewでインストールします。nssはFireforx からアクセスする場合に必要です。

brew install mkcert
brew install nss

ローカル CA 証明書を作り 端末にインポートする

下記のコマンドを実行することでインポートまで行われます。途中でrootパスワードを求められたら、端末の管理者パスワードを入力します。

mkcert -install
Created a new local CA 💥
Sudo password:
The local CA is now installed in the system trust store! ⚡️
The local CA is now installed in the Firefox trust store (requires browser restart)! 🦊
The local CA is now installed in Java's trust store! ☕️

証明書を確認します。Macの場合 「Keychain Access」を開き左側のメニューで「System」を選び、「Certificates」タブを選ぶと、"mkcert ....." というルートCA証明書がインポートされました。

証明書を生成する

localhost に対する証明書を作成します。mkcert localhost とコマンド実行すると、localhost.pem と localhost-key.pem が生成されます。

mkcert localhost

Created a new certificate valid for the following names 📜
 - "localhost"

The certificate is at "./localhost.pem" and the key at "./localhost-key.pem" ✅

It will expire on 23 April 2023 🗓

他にも、ワイルドカード (*.example.com) や ループバックアドレス (127.0.0.1) などでも作成できる。

HAProxyにSSLオフロード設定を行う

証明書をHAProxyにインポートする

HAProxyにインポートする証明書ファイル(.pem)は、1つのファイルにまとめる必要があるため、先程作成したキーペアを1つにまとめておきます。

HAProxy version 2.3.10 - Configuration Manual

cat localhost.pem localhost-key.pem > cert.localhost.pem

作成したPemファイルをDockerコンテナで利用するようにDockerfileを修正します

FROM haproxy:2.3
COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg
COPY cert.localhost.pem /usr/local/etc/haproxy/cert.localhost.pem

設定ファイルを編集する

haproxy.cfg に 証明書のパス、httpヘッダーへのx-forwardedの追加、httpからhttpsへのリダイレクトを追加します。

...
frontend http-in
    bind *:80
    default_backend servers

    # サーバー証明書のパスを指定
    bind :443 ssl crt /usr/local/etc/haproxy/cert.localhost.pem

    # x-forwarded-for を有効にし、プロトコルとポートもheader にセットする
    option forwardfor
    http-request set-header x-forwarded-proto https
    http-request set-header x-forwarded-port 443

    # httpからのアクセスをhttpsにリダイレクトする
    redirect scheme https if !{ ssl_fc }
...

最後にHAProxyのコンテナイメージを再ビルドして実行します。 またホスト側から443ポートでアクセスできるようにすること。

...
services:
  haproxy:
    build: ./haproxy
    depends_on:
      - app01
      - app02
      - app03
    ports:
      - 80:80
      - 443:443
...

動作確認

ブラウザから https でアクセスできました。httpでアクセスした場合もhttpsにリダイレクトします。 またSpringBootアプリのセッション管理も正常でした。

f:id:kazokmr:20210123110855p:plain
httpsでアクセス

ローカルでSSLやLBの動作確認できるの便利。 今日の設定内容は証明書を除いてGitHubリポジトリにあります。

github.com

参考記事

多機能なロードバランサとして使える多機能プロクシサーバー「HAProxy」入門 | さくらのナレッジ

多機能プロクシサーバー「HAProxy」のさまざまな設定例 | さくらのナレッジ

HAProxy version 2.3.10 - Configuration Manual

Redis を使ったHTTPセッション管理をローカル環境で動作できるようにした

今更な話ではありますが、AWSのECS Fargate と ALBを使った簡単なデモアプリの環境構築をTerraformで進めていて、セッション管理について調べていたら「ALB側でパーシステンス (スティッキーセッション)を実装するより、ErastiCashなどをセッションストアとして使う」というのを知りました。

個人的にHA構成の経験がほとんどなくRedisも使ったことが無かったことと、またローカル環境でHA構成の動作確認が行えると便利だと思ったので作ってみました。

構成

ソースコードGitHubで公開しています。

github.com

ローカル環境で動作確認をしたかったので、Docker-compose を使って以下のような構成を用意します。

  • ロードバランサ: HAProxy
  • アプリケーションコンテナ: Spring Boot
  • セッションストア: Redis

Spring Boot アプリ

まずはSpring Boot (Spring MVC)を使って、セッションの状態を確認できる簡単なアプリを作ります。

こちらはセッション管理するBean

package net.kazokmr.study.hasession;

import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.SessionScope;

import java.io.Serializable;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Component
@SessionScope
public class Reception implements Serializable {

    private static final long serialVersionUID = -3101986789734320497L;

    private String host;
    private LocalDateTime receptionAt;
    private String message;

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public String getReceptionAt() {
        return receptionAt == null ? null : DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss").format(receptionAt);
    }

    public void setReceptionAt(LocalDateTime receptionAt) {
        this.receptionAt = receptionAt;
    }
}

コントローラではこのBeanに「状態」「ホスト名」「実行日時」を登録するメソッドとセッションを破棄するメソッドを用意してリクエストを受け付けます。

package net.kazokmr.study.hasession;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpSession;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.time.LocalDateTime;

@RestController
public class ReceptionController {

    private final Reception reception;

    @Autowired
    public ReceptionController(Reception reception) {
        this.reception = reception;
    }

    @GetMapping("/hello")
    public String hello() throws UnknownHostException {
        if (reception.getReceptionAt() == null) {
            reception.setHost(InetAddress.getLocalHost().getHostName());
            reception.setReceptionAt(LocalDateTime.now());
            reception.setMessage("セッション開始");
        } else {
            reception.setMessage("セッション中");
        }
        return String.format("%s,  ", reception.getMessage()) +
                String.format("Host: %s,  ", reception.getHost()) +
                String.format("受付日時: %s", reception.getReceptionAt());
    }

    @GetMapping("/bye")
    public String goodbye(HttpSession session) {
        session.invalidate();
        return "セッション切断";
    }
}

これをIntelliJから起動しChromeからアクセスすると以下のように出力します。

f:id:kazokmr:20210121143633p:plain
初回アクセス

f:id:kazokmr:20210121143712p:plain
リロード時

f:id:kazokmr:20210121143745p:plain
セッション切断

Spring Boot アプリをDockerコンテナで起動する

このアプリケーションをDockerコンテナから起動してアクセスしてみます。尚、ホスト名をわかりやすくするために起動時のコマンドに -h オプションを付けます。

docker run --name <コンテナ名> -p 8080:8080 -h docker-container-1 <イメージ>

f:id:kazokmr:20210121144301p:plain
docker コンテナで起動したアプリにアクセス

HAProxy をDockerで起動する

HAProxyのDockerイメージは公式に記載されている通り、公式イメージに haproxy.cfg を上書きしたイメージをビルドして起動します。

Docker Hub

FROM haproxy:2.3
COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg

haproxy.cfg はアプリケーションコンテナを3つ用意し、checkで死活監視とラウンドロビンで振り分けているだけとなります。

global
    daemon
    maxconn 256

defaults
    mode http
    timeout connect 5000ms
    timeout client  50000ms
    timeout server  50000ms

frontend http-in
    bind *:80
    default_backend servers

backend servers
    balance roundrobin

    server app01 app01:8080 check
    server app02 app02:8080 check
    server app03 app03:8080 check

このhaproxyコンテナとSpring Bootアプリケーションコンテナを docker-compose で起動します。

app01 - 03 は予めDockerイメージを作成しています。またhostnameオプションを使って、どのコンテナにアクセスしているわかりやすくしました。

version: '3'

services:
  haproxy:
    build: ./haproxy
    ports:
      - 80:80

  app01:
    image: sessiontest:latest
    hostname: docker-container-1

  app02:
    image: sessiontest:latest
    hostname: docker-container-2

  app03:
    image: sessiontest:latest
    hostname: docker-container-3

結果

haproxy ではパーシステンスを行っていないためセッションが保持されません。なのでブラウザをリロードする度に、ラウンドロビン方式によって別のアプリコンテナに対して新規接続を行ってしまいます。

f:id:kazokmr:20210121163725p:plain
haproxy経由でアクセス

HAProxyでパーシステンスを実現する場合

以下のブログの内容を参考にhaproxy.cfg のbackend セクションで、JSESSIONID に振り分け先のserver追加してcookie にセットすることで2回目以降のアクセスも同じアプリケーションサーバーに接続するようにします。

Load Balancing, Affinity, Persistence, Sticky Sessions: What You Need to Know - HAProxy Technologies

HAProxyでHTTPロードバランシング - CLOVER🍀

backend servers
    balance roundrobin

    cookie JSESSIONID prefix nocache

    server app01 app01:8080 check cookie app01
    server app02 app02:8080 check cookie app02
    server app03 app03:8080 check cookie app03

haproxyのDockerイメージを再ビルドして起動したところ、セッションが継続されました。

f:id:kazokmr:20210121173718p:plain
haproxyでCookieにサーバー情報を埋め込んだ場合

Redis でセッション管理を行う

本題はこちらなので、HAProxyは、再度Cookie設定を戻してセッションが継続されない状態に戻します。

アプリをRedisに対応させる

Reidsをキャッシュストアとして利用するように Spring Boot アプリを変更します。

はじめに開発用にRedisのDockerコンテナを起動します。 Docker Hub

docker run --name redis -p 6379:6379 -d redis

Spring Boot アプリケーション側の設定変更はこちらのチュートリアルを参考にしました。 Spring Session - Spring Boot

RedisをSessionキャッシュにするために Spring Session Data Redis を依存関係に追加します。

更にアプリケーションからRedisに接続すためのクライアントとしてJedis も追加します。 Jedisは前述のチュートリアルでは含まれていませんが、これを入れないとSpring Boot起動時にRedisに接続できずにエラーとなってしまいます。

<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
</dependency>

最後に application.properties で sessionの保存先をredis に変えます。

spring.session.store-type=redis

これだけでHttpSessionの保存先をRedisに変えるための springSessionRepositoryFilter beanを生成してくれるようです。 またRedisの接続先やSessionタイムアウトの管理なども application.properties に追加してカスタマイズできますが、ローカルのRedisに接続するだけであればこれだけでOKです。

詳細は前述のチュートリアルをご覧ください。

Redis に保存されているセッション情報を見る

Docker Composeで環境を作る前に、IntelliJからアプリケーションを起動してRedisにどのように格納されているか見るため、アプリにアクセスしていない状態から確認します。データの検索はRedisのクライアントツールであるRedis-cli を使用します。このツールは Redisにバンドルされているので、Redisコンテナの中に入り利用してみます。

docker exec -it redis /bin/bash
root@redis:/data# redis-cli
127.0.0.1:6379>

接続したら keys * と実行すると保存されているKeyの一覧が表示されます。まだアプリケーションにアクセスしていないので (empty array) と出力されます。

127.0.0.1:6379> keys *
(empty array)

今度はブラウザからアクセスしてから検索します。

127.0.0.1:6379> keys *
1) "spring:session:sessions:expires:7f57263c-0743-471f-b4cc-5e3d41b60e3d"
2) "spring:session:sessions:7f57263c-0743-471f-b4cc-5e3d41b60e3d"
3) "spring:session:expirations:1611233700000"

"spring:session:" から始まるKeyが3つ出力されました。この中で2)にセッションデータが入っているようです。 type <key> でデータの形式を確認するとhash と出力されたので hgetall <key> で検索します。

127.0.0.1:6379> hgetall spring:session:sessions:7f57263c-0743-471f-b4cc-5e3d41b60e3d
1) "sessionAttr:scopedTarget.reception"
2) "\xac\xed\x00\x05sr\x00%net.kazokmr.study.hasession.Reception\xd4\xf3\x87s#\xeev\x8f\x02\x00\x03L\x00\x04hostt\x00\x12Ljava/lang/String;L\x00\amessageq\x00~\x00\x01L\x00\x0breceptionAtt\x00\x19Ljava/time/LocalDateTime;xpt\x00\x11MacBook-Pro.localt\x00\x15\xe3\x82\xbb\xe3\x83\x83\xe3\x82\xb7\xe3\x83\xa7\xe3\x83\xb3\xe9\x96\x8b\xe5\xa7\x8bsr\x00\rjava.time.Ser\x95]\x84\xba\x1b\"H\xb2\x0c\x00\x00xpw\x0e\x05\x00\x00\a\xe5\x01\x15\x15\x18\x02:|FXx"
3) "creationTime"
4) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01w$\xe6\xd1\xf6"
5) "maxInactiveInterval"
6) "\xac\xed\x00\x05sr\x00\x11java.lang.Integer\x12\xe2\xa0\xa4\xf7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\a\b"
7) "lastAccessedTime"
8) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01w$\xe6\xd1\xf6"

所々にbeanのデータが読めるのでそれっぽいですがわかりづらいですね。そこで下記の記事に書かれているようにセッションデータのシリアライズ方法をJava標準のものからJson形式にしてみます。

Spring Boot+Spring SessionでスケーラブルなステートフルWebアプリが簡単につくれるよ〜 - Qiita

余談でSpring Securiy も使うなら以下の記事のようにSpring Securityの拡張モジュールの適用もした方が良いと思います。

Spring Security 4.2 主な変更点 - Qiita

package net.kazokmr.study.hasession;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;

@Configuration
public class HttpSessionConfig {
    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }
}

(注意) JSON形式に変更した結果、LocalDateTimeで持っていて受付日時のシリアライズで例外が発生してしまいました。本来なら対応するべきですが今回はString型で取得した現在時刻を保持するように変えました。

アプリを起動する前にRedisに残っているデータを全て削除して、再起動しセッションデータを検索します。

127.0.0.1:6379> hgetall spring:session:sessions:a3b43f71-3c5b-48e6-a700-807d7bcb48c5
"sessionAttr:scopedTarget.reception"
"{\"@class\":\"net.kazokmr.study.hasession.Reception\",\"host\":\"MacBook-Pro.local\",\"receptionAt\":\"2021/01/21 21:50:27\",\"message\":\"\xe3\x82\xbb\xe3\x83\x83\xe3\x82\xb7\xe3\x83\xa7\xe3\x83\xb3\xe9\x96\x8b\xe5\xa7\x8b\"}"
"creationTime"
"1611233427618"
"maxInactiveInterval"
"1800"
"lastAccessedTime"
"1611233427618"

セッション情報がJSON形式で出力され、生成日時や最終アクセス日時なども見やすくなりました。

とこでRedisに保管されているSESSIONキーですが、これはJSESSIONIDではなく、SESSIONという専用のCookieを使っているようです。ですが、Redisで検索するとUUIDの形式で出力されますが、Chromeの開発者モードで見るとUUIDでは出力されていません。なのでChormeのCokkieからSESSION情報を検索するのは難しそうです。

Redis を追加したDocker Composeでアプリを起動する

docker-compose.yml にはRedisを追加し、Redis > app01-03 > HAProxy の順番に起動するように依存性を追加しました。

version: '3'

services:
  haproxy:
    build: ./haproxy
    depends_on:
      - app01
      - app02
      - app03
    ports:
      - 80:80

  redis:
    container_name: redis
    image: redis

  app01:
    image: sessiontest:latest
    hostname: docker-container-1
    depends_on:
      - redis

  app02:
    image: sessiontest:latest
    hostname: docker-container-2
    depends_on:
      - redis

  app03:
    image: sessiontest:latest
    hostname: docker-container-3
    depends_on:
      - redis

また Spring BootアプリのコンテナもDocker Compose のRedisと接続できるように以下の設定を変更しました。

  1. Docker Compose 専用の application-docker.properties を追加し、compose上のcontainer_name: redis に接続するようにホスト名を追加しました。
spring.redis.host=redis
  1. アプリケーションコンテナをビルドする前に、Maven で clean package を実行するようにしました。これは maven のTestフェーズでRedisとの接続が必要なため、ローカル環境のRedisコンテナにアクセスしてMavenビルドを実行したかったためです。

  2. アプリケーションコンテナ用のDockerfile の内容を変更してDockerイメージを再ビルドしました。 上記のNo.1と2に対応する形で、マルチステージビルドを止め、ENDPOINTでjarを起動時にapplication-docker.properties を参照するようにprofileのactivate 設定オプションを追加しました。

FROM openjdk:11-jre
COPY ./target/*.jar .
EXPOSE 8080
ENTRYPOINT ["java","-jar","ha-session-0.0.1-SNAPSHOT.jar","--spring.profiles.active=docker"]

f:id:kazokmr:20210121232109p:plain
Redisを使ったセッションストアでアクセスした時

感想

新しい技術では無く沢山の情報もあったことやRedisのセッション管理もSpring Session Data Redisが良い感じで行ってくれるので難しくは無かった。

ただ Redisでセッション管理するにはアプリケーション側の方で設定を行うことになるので利用しているフレームワークなどによっては導入コストが掛かる場合もあると思うので、その場合はロードバランサーのパーシステンスの採用を検討した方が良いかもしれない。

これでローカルの開発環境で冗長化構成の動作確認を簡単に行うテンプレートを用意することができた。これに、HTTPS接続とRDBMSの接続も追加しておくと個人的にはより便利になりそう。

JetBrains Academy で Java Developer コースを進めてみた

今週末で無料で受けられる期間が終了するので、使ってみた感想を書きます。

www.jetbrains.com

動機

8月に Twitterか何かで 見かけて「最近コードを書いていないなー」って思ったので、鉛切った身体を鍛え直すつもりで、最大で2ヶ月ちょっとは無料で受けられるので使ってみました。

進め方

ログインするとまずはTrack と呼ばれる、言語ごとのコース選択があります。私は Java Developer を選択しました。 他にはPython Developer があり、Kotlin Developer や Web Developer などがβ版として選択ができます。

次にプロジェクトを選びます。プロジェクトはこの学習のなかで完成させるアプリケーションとなっていて、プロジェクトの中で5−6のステージに別れています。各ステージではトピックと呼ばれる特定のスキルに対する説明と演習からなる学習が並んでおり、トピックを順番にこなすとステージの最後にプロジェクトの課題が提示され、これまでのトピックで学んだことを活用しながら、課題をクリアします。

トピックの演習とステージの課題は、IntelliJ IDEAと連動しておりIntelliJ でコードを書いて評価します。 ちなみに間違えても特にペナルティはなく、何度もでも挑戦できますし、クリアしないと次に進めない仕組みになっています。 また各演習については参加している他のメンバーがコメントしたりヒントを提示していたり、クリアすると他の人のコードを見たり、自分のコードを公開したりできます。 海外のプログラマーのコードと自分のコードを比較するのも勉強になることもあったり、あまり変わらないなーと思ったり、なんでそんな書き方しているんだろ?って思ったりと面白かったです。

プロジェクトとトピック

プロジェクトはJava Developerだと32種類あり、Easy, Medium, Hard, Challenging の4つの難易度になっています。 トピックは、Javaに関する様々なスキルを学ぶことができ、Java以外にもデータベースやフロントエンドに関するトピックスや、ソフトウェア工学アルゴリズム、代数・幾何と言ったジャンルもあり、全部で403のトピックがあり、知らないことも多かったりしてとても勉強になりました。

Java Developer – JetBrains Academy

JetBrains Academy - Learn programming by building your own apps

進捗

2ヶ月続けて、11プロジェクト(Easy:1, Medium: 3, Hard:5 , Challenging:2 )完了し、トピックは 221/403 (55%) でした。 Hard位だと割とスムーズにクリアできるのですが、Challengingは時間がかかりました。平日は朝と夜に少しずつ進めて、休日に家にいるときに進めていましたが、子供の面倒や外出などもあって、まとまって進める時間はあまり取れませんでした。 f:id:kazokmr:20201022220609p:plain (一応、クリアしたプロジェクトはGitHubに上げているのですが、課題の回答などにもなり規約とかも有りそうなのでPrivateにしてあります。)

感想

Javaについて言えば、色々な範囲を細かくカバーして演習が出来たのでとても勉強になった。特にLambda や StreamAPI などは前職のJavaのバージョン的な都合もあって、あまり触れる機会がなくて、IDEの予測機能などを駆使したパワープレイ的な感じで書くことが多く、いまいち理解できてなかったのですが、関数型インターフェースの学習から始まり、StreamAPIの様々な使い方をトピックとして学べたので力がついたと思います。

またアルゴリズムの勉強や代数・幾何の連立方程式行列式などの計算プログラムを作るプロジェクトもあり、学生時代を思い出しながら復習できたこと、あるいはブロックチェーン の基本的な仕組みが理解できるプロジェクトなどに取り組むことで新しいことも覚えられたのがとても良かったです。

またコースは全て英語になっているので、細かいニュアンスや専門用語にわからない部分が多かったりしました。問題文を見ても何を求められているのかがわからなかったりする部分が多く、DeepLは欠かせなかったです。 海外の人が「この問題の説明はよくわからないな」「英語というのは時折、正確に伝えないことがある。」みたいなことをコメントしていることもあり、自分の英語スキルの問題だけではなさそうでしたが。

またプロジェクトの課題についても、難易度に関係なく説明が大雑把だったりして、どうしたらいいのかを探りながら進めたり、初期の頃は「このプロジェクトのステージやトピックでやったことを使ってクリアしないと」という気持ちが強すぎて、シンプルにコードを書くことができなかったりしました。

今後

無料期間が過ぎたら受講料が発生するのですが、現時点で中途半端な進捗なのとまだ学習してみたいトピックもあるので、多分継続することになると思います。最初のEasyコースが簡単だったので2ヶ月あれば終わるんじゃないかなと思っていたのですが甘過ぎました。

と、JetBrains Academyがベストかは分かりませんが、IntelliJを使いながらプログラミングスキルを上げつつ、英語の勉強にもなるので自分にはあっているなとは感じました。