6月4日(日)に開催されたJavaコミュニティイベント「JJUG CCC 2023 Spring」に、GMOペイメントゲートウェイ株式会社 坂口健太が登壇しました。セッションでは、ヒューマンエラー防止の考え方をもとに、開発者が安全に開発できるようにするためのSpring Bootプロパティの運用について、実装例を用いて解説しました。
登壇者
GMOペイメントゲートウェイ株式会社システム本部 関連事業サービス統括部 後払いサービス部 後払い決済グループ坂口 健太
GMO後払い」ではSpring Bootへのフレームワーク更改を実施
GMOペイメントゲートウェイでは、連結会社であるGMOペイメントサービスが提供するBtoC EC取引向け後払い決済サービス「GMO後払い」の開発を行っており、直近ではSpring Bootへのフレームワーク更改を行っています。
Spring Bootのプロパティ機能・プロファイル機能を活用することによって、環境依存の部分を外部化することができます。しかし、設定を誤ると、テスト用であるはずの設定が本番環境に適用されてしまうといったさまざまなリスクが考えられます。
そこで本セッションでは、「GMO後払い」の開発における実装例を用いて、ヒューマンエラーを防ぐためのSpring Bootのプロパティ運用について坂口が解説しました。
なお、本セッションでは次のソフトウェアとバージョンを使ってコードを紹介しています。
ヒューマンエラー対策の概要
ヒューマンエラーの対策としては、まず、ヒヤリハットも含めたヒューマンエラーをリストアップし、分類します。そして、それぞれの背後要因を洗い出し、対策案を列挙。このうちコストや時間を踏まえ実行可能な対策を選択・実施し、評価するという流れで進めていきます。
ヒューマンエラーを分類すると、「認知」「判断」「記憶」「行動」の4つに分けられます。このうち、「認知」に起因するエラーの背後要因の例としては、マニュアルが読みにくい、モニターが小さすぎて文字が読みづらい、部屋が熱くてボーッとする、二日酔いで頭が痛い、疑問点を上司に聞きづらいなどといった項目が考えられます。それぞれ一般化すると、ソフトウェア、ハードウェア、環境、心身・能力・経験・モチベーション、人間関係・コミュニケーションの問題として分類できます。坂口は「こうして要因を明らかにすることがトラブルを最小限に留めるために重要な手順と考えている」と話します。
ヒューマンエラーの発生確率を下げるには
ヒューマンエラーの対策手法は大きく分けて、未然に防ぎ発生確率を下げる「発生防止」、エラーが起きた際に速やかに検出する「波及防止」、被害を最小限に抑える「影響緩和」という3つの対策があります。
ヒューマンエラーの発生確率を低減させるものとしてそれぞれどのような対策があるかをリストアップした表が以下になります。坂口によると、上のものほど効果が大きいため、対策案を検討するには上から順に進めていくことが望ましいといいます。本セッションで坂口は、このうち1〜4について詳細を解説しました。
Spring Bootのプロパティ運用のリスクとは
対策を考える前にまずは、Spring Bootのプロパティ運用のリスクを整理しておきます。
想定される問題の1つめは、ローカル環境でプロパティファイルを変更していたが、下記のようにsrc/main/resources/application.yamlの実行状態がmodifiedになっているケースです。
この場合、いくつかの要因によって、ローカル環境用の設定が本番環境にデプロイされてしまうリスクが考えられます。要因としては、部分コミット(hunk)したつもりが全量コミットしてしまった、期限が迫っていたためあまり確認せずにコミットしてしまった、いつもどおりで大丈夫だろうと思いあまり確認せずにPull Requestをマージしたなどといったことがあげられます。
2つめは、開発者がSpring Profilesを指定する場合、ローカル環境に接続するつもりがステージング環境に接続してしまうといった形で想定とは異なる環境に接続するケースです。
そして3つめは、プロパティに間違った値を設定してしまうケースです。数値の桁や単位の誤り、値の範囲の誤りなどが考えられます。
Spring Bootのプロパティ運用の改善方法
これら3つの問題の発生を防止するため、下記1〜4の対策をそれぞれ適用していくことを考えます。
作業そのものをなくす・減らす人が作業しなくてもいいようにする仕様外の入力・操作をできないようにするUI/UXを改善しわかりやすくする
坂口は「どの対策も工夫レベルのものなので導入の難易度は高くない」と説明します。
以下で順番に見ていきます。
1.作業そのものをなくす・減らす
ヒューマンエラーにつながる作業をなくす、または減らすことで発生確率を減らすという考え方です。Spring Bootのプロパティ運用では、src/main/resources/application.yaml を変更しなくてよいようにするということになります。下図のようにgit statusコマンドを実行した際、application.yamlがmodifiedとして出てくる機会を極力減らすことで、ヒューマンエラーを回避します。
1-1. プロパティを環境変数として渡す
1つめの対策としては、プロパティを環境変数で渡すことがあげられます。環境固有の設定をapplication.yamlを変更せずに環境変数経由で渡すようにするということです。dotenvという機能(またはそれに相当する機能)を利用するのが一般的な方法です。
坂口は、Gradleのカスタムプラグインの実装を例に解説していきました。下記が、カスタムプラグインのビルドスクリプトです。
buildSrcディレクトリにbuld.gradle.ktsを作成。pluginsブロックではjava-gradle-pluginを指定。6行目以降のgradlePluginブロックではプラグインの名前や実装クラスを指定
次に、プラグインの実装クラスです。
1つめの赤枠の部分でJavaのプロパティファイルを読み込み、Gradleのタスクで環境変数として渡せるようプロパティクラスをMapに変換している。2つめの赤枠の部分で、1つめの赤枠でロードした環境変数のMapを追加している
1つめの赤枠内で使用していたメソッドはそれぞれ下記のようになっています。
プロパティファイルを読み込む処理と、 java.util.Propertiesクラスのキーと値をストリング型とするMapに変換している
続いて実際のプロジェクトに作成したdotenvプラグインを適用します。
PluginsブロックでプラグインIDを指定している
なお、環境変数経由でプロパティを渡す場合、Configuration yamlのプロパティの名前を一定のルールに従って変換します。たとえば、ドット(.)をアンダースコアに置換したり、ダッシュ(-)を削除したり、大文字に変換したりといった具合です。下記は、データベース接続時のドライバークラス名を指定する際のspring.datasource.driver-class-nameというプロパティを環境変数として渡す場合の例です。
実行した結果は下記のとおりです。
env.propertiesファイルでは、先のルールに従ってAPP_MODEを大文字で定義しています。アプリのソースコードでは、app.modeのPropertyを取得してログ出力するコードを書いています。Spring Bootのアプリケーション実行後の標準出力の結果が右上です。Gradleプラグインを用いてenv.propertiesファイルの中身を環境変数として読み込み、それがSpringフレームワークのConfiguration Propertyとして参照されることが確認できます。
1-2. プロパティファイルをクラスパス外に配置する
src/main/resourcesディレクトリは、MavenやGradleの標準ディレクトリレイアウトでリソースファイルを配置する場所となっていることが前提です。ここでいうリソースファイルとは、設定ファイルや画像ファイルなどのJavaコード以外の静的なファイルを指します。同ディレクトリはビルド時にターゲットクラスパスにコピーされます。つまり、src/main/resourcesディレクトリ内のファイルはJarにバンドルされるため、クラスパス外に配置するということは、src/main/resourcesディレクトリ以外の場所に配置するということを意味します。これにより、誤ってコミットした場合でもJarにバンドルされず、プロダクション環境には影響しません。
ファイルおよびディレクトリ構造は下記のようになっています。
中央赤枠のsrc下のjavaとresourcesディレクトリがjarにバンドルされる。上と下の赤枠はjarにバンドルされない、かつSpring Bootが標準で読み込む場所となっている
具体的には、プロパティファイルは表に記載の順序で読み込まれます。1番目と2番目はクラスパスにあるプロパティファイル、3番目と4番目はカレントディレクトリまたは作業ディレクトリにあるプロパティファイルで、この順序で読み込まれるため、採用される値は後勝ちとなります。そのためクラスパスに本番環境の値が設定されていたとしても、カレントディレクトリ側に開発環境の値を設定することで、本番環境のクラスパスの中にあるapplication.yamlを設定変更せずに開発環境として実行できます。
上の赤枠がクラスパスの外に配置したプロパティファイル、下の赤枠がクラスパス内に配置したプロパティファイル。これでアプリケーションを実行すると、ログには「モード:開発環境」と出力される
2. 人が作業しなくてもいいようにする
これは、手作業をなくしてツールやシステムに置き換えることで、手作業に起因するヒューマンエラーが起きないようにするというものです。Spring Bootのプロパティ運用においては、ビルドツールに任せるという考え方になります。
坂口は、1つの例としてspring.profilesの設定をビルドスクリプトに指定する方法を紹介しました。この方法では手作業で接続環境を設定しないため、誤った環境に接続することを防げます。
ローカル環境でSpring Bootのアプリケーションを開発する場合、bootRunタスクとtestタスクの2つのタスクしか実行せず、これらのタスクに対して、spring.profiles.activeというシステムプロパティをGradleのタスク定義にセットするようにしている
3. 仕様外の入力・操作をできないようにする
プロパティに誤った値が設定されていたとしても、そのプロパティが実際に呼び出されるまでは通常問題に気づくことができません。そこで、誤った値ではアプリを起動できないようにすることで、早い段階で問題を検出できるようになります。具体的には、Configuration Properties Validationを活用します。
このメリットとしては、Validationをパスしないとアプリが起動できないため、アプリ起動時という早い段階で問題に気づける点にあります。また、Validationのルールをプロパティクラスに定義するため、仕様が把握しやすいという利点もあります。
左上がValidationの条件。monthというプロパティに対して最小値1、最大値12を設定。右上はプロパティに設定した値。ここでは、エラーになるよう0と設定している。標準出力を見ると、app.monthに設定された値、Validationのエラー理由などが表示され、アプリケーションの起動に失敗している
4. UI/UXを改善し、わかりやすくする
視認性、識別性、操作性を向上することで、作業を容易化・確実化し、読み間違いや判断の誤りなどヒューマンエラーになる確率を低減することができます。
たとえば、数字の区切り文字の活用です。数値リテラルの桁の間のどこにでも任意の数のアンダースコアを含められるため、この仕様を活用することで、大きな数字でも認識しやすくなります。
※セッションでは、「Java言語の仕様」と説明をしていましたが、正しくは「YAMLの仕様」でした。Java言語の仕様にも数値リテラルにアンダースコアを含められる仕様がありますが、application.yamlの部分においては、YAMLの仕様となります。
Any “_” characters in the number are ignored, allowing a readable representation of large values.
https://yaml.org/type/int.html
Properties Conversionを活用するという方法もあります。Spring Bootでは標準で、時間、日付、データサイズという3つのプロパティ変換の仕組みがあります。これを活用すると、15分という値を設定したい場合、「15m」と定義できます。仮にロング型で単位をミリ秒で設定しなければならない場合も、暗算したり、インターネット検索したりする必要はありません。このように、数値に加えて単位を指定できるため可読性が高いことと、わざわざ単位変換をする必要がないという点がProperties Conversionを利用するメリットです。
まとめ
本セッションでは以上のように、ヒューマンエラーを防ぐSpring Bootプロパティ運用として、ローカル開発やテストを目的とするsrc/main/resources/application.yamlの変更は割ける、プロファイルの指定はビルドツールに任せる、安全なバインドやバリデーションを活用する、認知しやすい表現や型を使うといった対策が坂口より紹介されました。
坂口は「Spring Bootの仕組みを活用し、ヒューマンエラーを防いでもらいましょう」と呼びかけ、セッションを締めくくりました。
エンジニア採用実施中!
GMO-PGではエンジニアを積極採用中です。さまざまなバックボーンを持つエンジニアのみなさまの力を必要としています。興味のある方はぜひ採用HPをご覧ください。
また、これまで当社エンジニアが登壇したウェビナーやインタビューは以下にまとまっていますので、併せてご覧ください。
https://www.gmo-pg.com/blog/feature/feature03/