この記事は、現在進行中のブログシリーズの一部になります。このシリーズでは、これまでに以下のようなトピックを扱ってきました。


既存のコードベースをモダナイズする場合、コードを完全に書き直す (基本的にまったく新しいアプリケーションを作成する) か、既存のコードおよび設定のリファクタリングに取り組むかのいずれかを行うことになります。

コードを完全に書き直す決定は、大がかりなものとなります。これは、予算、人材、および時間によるところが大きいオプションのため、企業により異なってきます。書き直しが必要な場合は、このシリーズの別の記事で、関心のある製品開発を推進する上で役に立つフィードバックループと独断的なワークフローについて説明しますので、そちらを参照してください。

この記事では、既存のコードベースのリファクタリングに焦点を当てています。

これまでに、モダナイゼーションの取り組みを推進する上で、望ましい未来の状態が特定されました。たとえば、より安価なアプリケーションサーバーへの移行や、非推奨のバージョンの Java からの移行などが考えられます。

コードが変更される場合は、望ましい未来の状態に関係なく、以下の 2 つのストラテジーによって多くの利点が得られます。

  1. テストしやすいコードを作成し、テストを記述する

  2. コードと設定を更新して、よりコンテナフレンドリーにする

これらの順番は前後することがあります。自明でノーブレークであることが、よりコンテナフレンドリーなコードを作成することになる場合があります。逆に、コードは簡単にテストカバレッジを追加できない状態にある可能性があります (手動でテストしている可能性があります)。この場合、フィードバックループを改善するためにコンテナ環境でコードを実行してから、よりテストしやすいものにすることが理にかなっているかもしれません。

これらのストラテジーの大まかな目標は以下のとおりです

  1. 機能を損なうことなく、コードをより安全に変更できるようにする

  2. イノベーションを推進するために、アプリケーションをより「フィードバックループ」しやすいものにする (これは基本的に、デプロイを容易にし、フィードバック (ログとメトリクス) を取得しやすくすることを意味する)

これらの点をもう少し詳しく見てみましょう。コンテナを初めて使う方も、以下に説明がありますので、ご安心ください。また、モダナイゼーションの目標がコードをコンテナに移動することである場合は、うまくいけば、ここでの話のポイントが役に立つ可能性があるでしょう (または、少なくともプロジェクトの目標を強化するでしょう)。

リファクタリング:現状を改善するための手段

このテーマに関するガイドの決定版が、Martin Fowler 著 『リファクタリング―既存のコードを安全に改善する―』 です。このテーマに興味のある方に、この本をお勧めします。

Fowler 氏は、リファクタリングを「ソフトウェアの内部構造を変更して、観察可能な動作を変更することなく、理解しやすくし、変更を安価にすること」と定義しています。

コードベースをよりテストしやすく、よりコンテナフレンドリーなものに変更すると、コードが理解しやすくなり、変更コストも低くなります。現在の状態から有益な未来の状態へ移行する (モダナイズする) ことは決定済みなので、これは非常に望ましいことです。

コードをよりテストしやすくしようとする動きが、よりクリーンでモジュール化されたコードを作成することになります。つまり、変更しやすく、新しい開発者にも理解しやすいコードが作成されます。もちろん、テストカバレッジがあるということは、コストのかかる手動でのテストチームを必要とせずに、変更を加え、その変更によって何かが壊れていないかどうかについて、低レベルの環境で迅速にフィードバックを得ることができることを意味します。

コンテナフレンドリーなコードを作成するには、コードを読みやすく、デプロイしやすくするためのベストプラクティスも必要です (下記の Twelve-Factor App のプラクティスを参照)。コードがコンテナフレンドリーになれば、アプリケーションもコンテナ・プラットフォーム上で動作するようになります。これにより、いくつかの興味深い操作上の可能性 (詳細は後述) やフィードバックループの改善への扉が開かれます。

テストの重要性

モダナイゼーションは変更を意味し、変更を加える対象が既存のアプリケーションであることから、加えられた変更により何かが壊れていないかを検証する必要があります。

アプローチの 1 つとして、リファクタリングしたアプリケーションを低レベルの環境にデプロイし、人間のテスター軍団にお金を払ってテストに打ち込んでもらい、何が機能し何が壊れているのかに関するレポートを提供するという方法があります。このストラテジーの問題点は、コストがかかる上になかなか進まず、膨大な時間が費やされることです。また、コードをよりテストしやすくすることで得られる多くの利点も見逃すことになります。

テスト全体は階層化 されており、各階層には利点があります。この記事では、ユニットテストレベルでのテストの記述に焦点を当てています。これらは、ビルドフェーズ中に自己実行でき、開発者がコードベースの一環として記述できるテストです。

モッキングはエンタングルメントへの対処に有用

私は、ダウンストリームサービス操作に関連するすべての機能をモック化する必要があると強く信じています。モッキングという用語を初めて耳にする場合は、クラスとメソッドをテストするために、外部依存関係をここでモック化 (実際のオブジェクトの動作を模倣するオブジェクトを作成) します。

サードパーティの依存関係がある場合は、可能な限り、自走式テストケースでモック化する必要があります。これにより、テストを記述するチームにとって、多くの柔軟性がもたらされます。

私はかつて、アプリケーションのアーティファクトをビルドする際に、テストスイートを実行するためにデータベースをスピンアップする必要があると主張する人と議論したことがあります。この人は、データベースロジックに問題がある場合は、アプリケーションを構築すべきではないと主張しました。効率性はさておき、この人の懸念は理解できます。しかし、テスト対象のデータベースが本番環境と全く同じでない限り、テストがうまくいったという安心感は誤りである可能性があります。テスト対象のデータベースで問題が発生した場合、ビルドが脆弱になる可能性があることは言うまでもありません。さて、外部サービスをテストの一部として取り込むケースもありますが、これを紹介する前にモッキングについて説明したいと思います。

ダウンストリームサービス操作に関連するすべての機能をモック化した場合でも (適切かつ不適切な潜在的な出力のシミュレーションを含む)、データベースに問題が発生する可能性はあります。ただし、モッキングとテストを適切に行っていれば、少なくともコードでそれを処理できることがわかります。

Mockito のようなプロジェクトは、特にこの点で役に立ちます。Spring Framework または Quarkus のようなフレームワークと組み合わせると、ロジックをインターセプトする簡単な方法が提供され、以下を返します。

  • ハッピーパスをテストする想定内の結果

  • アンハッピーパスをテストする誤ったデータ

  • エラー処理とロギングをテストする結果の代わりとなるエラー

また、特定のコンポーネントをスパイして、想定どおりのタイミングで実行するために呼び出されていることを確認することもできます。モッキングは、テストの移植性と性能を高めることができます。とはいえ、サードパーティサービスからのすべての良い応答と悪い応答をモック化することは、大変な作業になる可能性があります。

サービスに対するモッキングとテストの間の適切な媒体が、Test Containers です。このチームは、このプロジェクトについて「JUnit テストをサポートする Java ライブラリであり、一般的なデータベース、Selenium Web ブラウザー、または Docker コンテナで実行できるその他のものの軽量で使い捨てのインスタンスを提供する」と説明しています。

基本的にこれは、テストの制御下にあるデータベースまたはキャッシュのインスタンスを含むコンテナを JUnit テストで簡単にスピンアップできることを意味します。そのため、テスト対象に応じて、良い状態 (または悪い状態) で設定/構成できます。

Test Containers チームは、コンテナの管理およびサービスへの Ingress/Egress に関して、ほぼすべてのことを考慮してきました。Test Containers には、既存のデータベース、キャッシュ、メッセージブローカー、およびモードなどのモジュールも含まれています。唯一の要件は、アプリケーションのビルドに Maven または Gradle を使用することです。そして、テストフレームワークとして JUnit または Spock を使用する必要があります。

もちろん、あなたのコードベースにテストがない場合や、Maven や Gradle を使用していない場合は、モックの追加は言うほど簡単ではありません。これについては、今後のブログ記事で具体的なテストストラテジーについて説明する際に、また触れたいと思います。

コンテナフレンドリーなコード:「Twelve-Factor App」手法の活用

2 つ目の目標は、コードをよりコンテナフレンドリーにすることです。前述したように、コードベースをコンテナに入れることが、モダナイゼーションプロジェクトにとって望ましい最終状態である場合があります。その場合は、このセクションは読み飛ばしていただいてけっこうです。ただし、そうでない場合は、コンテナへの移行によって、変更が行われるコードベースにもたらされる利点について以下に概説します。

一般に、より「コンテナフレンドリー」なアプリは、デプロイが容易です。つまり、アプリケーションのバージョンをすばやく実行できることを意味し、機能を検証し、ユーザーフィードバックを得る場合に適していると言えます。アプリケーションがコンテナで起動し、実行できるようになれば、Kubernetes プラットフォームを利用して、開発のフィードバックループを強力に推進することも可能になります。

Kubernetes プラットフォーム (例:Red Hat Openshift) を使用すると、デプロイメント能力だけでなく、デプロイメントを観察する能力も大幅に向上します。これらのプラットフォームの多くが、ログやメトリクスを取得するための簡単にアクセスできるワークフローを提供しているからです。フィードバックループとソフトウェアの安全な変更については、今後のブログ記事で詳しく説明します。

Twelve-Factor App は、変更しやすいソフトウェアをビルドする場合に非常に有用な手法です。これらの要因に従うことで、開発者はあらゆる環境 (コンテナを含む) で実行できるコードをビルドすることができます。

Twelve Factor App の特に便利な点については、今後のブログで詳しく紹介したいと思います。ただし、特にレガシーコードに関しては、12 の要因すべてに従おうとすることは、非現実的である点に注意する必要があります。アプリケーションを未来の状態に近づけ、プロダクトループとオペレーションループの両方で反復可能とするのに十分な要因のみに従うことができます。

たとえば、Kubernetes ディストリビューションで実行できるように、コードをコンテナフレンドリーにすることが目標である場合は、アプリケーションをコンテナにデプロイして実行し、その周りにある程度の可観測性を持たせれば十分かもしれません。これを達成するために必要なのは、12 の要因のうち、ほんのいくつかだけです。前述のとおり、これについては、今後のブログ記事で詳しく説明する予定です。

テストとコンテナのオプション、および Twelve-Factor App の手法を確認したことで、プロジェクトを開始する上で役立ついくつかの有用なストラテジーが得られました。作業中の仕事の種類が、具体化されてきました。ただし、開始する前に、チームがその作業を効果的に行うために、いくつかの重要なツールを準備する必要があります。これに関しては、次のブログ記事で重点的に説明します。

コンテナフレンドリーなブロッカー:ミドルウェアとの密結合

場合によっては、コードベースが、コンテナフレンドリーでないミドルウェアやサービスと密結合していることがあります。たとえば、コストが高すぎる Enterprise Java アプリケーションサーバーから移行するというモダナイゼーションの目標を追求しているときに、コードベースにそのアプリケーションサーバーに結合するアノテーションが含まれていることに気付く場合があります。データベースへの接続プーリングや JNDI へのアクセスに、アプリケーションサーバーが使われているといった単純なことは、リファクタリングプロセスを通じて解決することができます。ただし、メッセージ駆動型 Bean (MDB) のようなものは、切り離すことが難しい場合があります。

ここで、プロジェクトの早い段階で成功を確立すること、そしてタイムリーに完了できない (または、まったく完了できない) 作業に貴重なリソースを無駄にしないことに関して、以前のブログ記事 で紹介した「適切なパターンを選択して開始する」という内容が重要となります。

コンテナの場合

Linux コンテナは、アプリケーションとそのランタイム環境全体 (実行に必要なすべてのファイル) でパッケージ化および分離できるようにするテクノロジーです。これにより、完全な機能を保持したまま、含まれるアプリケーションを環境 (開発、テスト、本番など) 間で簡単に移動させることができます。

モダナイゼーションの観点からは、コードをコンテナで実行するように移動することは、Red Hat OpenShift などの Kubernetes ベースのコンテナ・プラットフォームへの扉を開くため、モダナイゼーションの目標になる可能性があります。コンテナ・プラットフォームは、モダナイゼーション・プロジェクトに取り組むチームに大きな利点をもたらします。

コンテナの簡単な概要

以前から存在するコンテナ

1979 年に chroot システムコールが導入され、プロセスとその子の root ディレクトリがファイルシステム内の新しい場所に変更されました。これがプロセス分離の始まりでした。プロセス分離を使用すると、アプリケーション/サービスをオペレーティングシステム (OS) で実行し、同じ OS で実行されている他のアプリケーション/サービスや OS 自体のプロセスから分離することができます。何年にもわたって、FreeBSD、Solaris Zones、Process Containers、および LXC がコンセプトを改良し続けてきました。

Docker でより簡単に

2013 年に Docker が登場し、アプリケーションがコンテナ環境で実行を開始する前に、コンテナ環境で何がセットアップされるかを決定する簡単で強力な方法が提供されるようになりました。Dockerfile (Linux イメージに何が入り、どのようにビルドすべきかを記述) と Dockerhub (Docker ファイルを実行した結果のバイナリーを含む) のようなイメージリポジトリは、ほとんどのソフトウェアプロジェクトに必要なツールの定番となっています。

コンテナと仮想マシンの比較

ハイパーバイザーで管理する必要があり、使用可能となる前にオペレーティングシステム (OS) をセットアップする必要がある仮想マシン (VM) とは異なり、インスタンスごとにコンテナに OS をインストールする必要はありません。コンテナには、ハイパーバイザーやゲスト OS のオーバーヘッドなしで、より多くのリソースを単一のホストマシンにパックできるという利点があります。

コンテナ・オーケストレーション・プラットフォーム

コンテナは VM ほど信頼性が高くありません。コンテナに障害が発生したり、ホスト OS に障害が発生したりすると、ホスト OS が生成したすべてのコンテナが消失する可能性があります。

コンテナの一時的な性質に対処するために、多くのコンテナ・プロビジョニング・プラットフォームが作成され、コンテナのワークロードの実行や、トラフィックの出入りの管理に関するすべての取り組みを管理しています。Kubernetes プロジェクトをベースとした例としては、Red Hat OpenShift、Azure Kubernetes Service (AKS)、Google Kubernetes Engine (GKE) などがあります。Kubernetes 以外のオプションとしては、Google Cloud Run や Amazon Elastic Container Service (ECS) などがあります。

コンテナが開発に与える影響

コンテナは常に変化しています。コンテナはスケールアップすることが可能です。つまり、同じワークロードの 3 つのバージョンを急きょ存在させることが可能 (それぞれが独自のコンテナで実行) で、それらを 1 つのリソースプールから別のリソースプールに移動して、冗長性の要件を満たすことができます。

これは、一連の手順を手動で実行してアプリケーションを実行することは (VM で実行されているアプリの場合と同様に) 不可能ではないにしても、非常に困難となることを意味します。

また、アプリケーションが依存する特定のミドルウェア (特定のアプリケーションサーバーなど) は、コンテナ内で実行できない場合があります。そのような環境で成功するアプリケーションの開発を支援するために、開発チームが従うべき一連の原則として Twelve-Factor App が作成されました。

目標に集中する

このブログ記事では、コードベースをより変更しやすくするための大まかな目的について、いくつか説明しました。とはいえ、ここには罠があります。テストカバレッジを重視したり、Twelve Factors を過度にローテーションしたりすると、失敗する可能性があります。結局のところ、テストには簡単に合格するかもしれませんが、アプリケーションを未来の状態に移行させる (約束した価値 が満足のいく結果となる) ことに関する進捗状況を示す必要があります。

これらの大まかな目的と、アプリケーションを望ましい未来の状態に移行するために必要な具体的な作業を両立させることは、プロジェクトリード の責任となります。これは簡単なことではないかもしれませんが、だからこそ、適切なチームを編成することが非常に重要となるのです。

次のブログ記事では、チームが最も効率的な方法で作業できるように、すべてのツールとリソースを配置することについて説明します。


About the author

Luke Shannon has 20+ years of experience of getting software running in enterprise environments. He started his IT career creating virtual agents for companies such as Ford Motor Company and Coca-Cola. He has also worked in a variety of software environments - particularly financial enterprises - and has experience that ranges from creating ETL jobs for custom reports with Jaspersoft to advancing PCF and Spring Framework adoption in Pivotal. In 2018, Shannon co-founded Phlyt, a cloud-native software consulting company with the goal of helping enterprises better use cloud platforms. In 2021, Shannon and team Phlyt joined Red Hat.

Read full bio