Powered by

ヒューマンエラーを防ぐ Spring Boot プロパティ運用【JJUG CCC Spring 2023】

article-108_top.jpg

GMOインターネットグループがセッションスポンサーとして協賛している、日本Javaユーザーグループ主催の「JJUG CCC 2023 Spring」が2023年6月4日にハイブリッド形式で開催されました。
当社からは、GMOペイメントサービスが提供する「GMO後払い」の開発で行っている、ヒューマンエラー防止の考え方を基に開発者が安全に開発できるようにするためのプロパティ運用について、実装例を用いてご紹介しました。

※本稿では「JJUG CCC 2023 Spring」にて登壇・配信した「ヒューマンエラーを防ぐ Spring Boot プロパティ運用」を書き起こし記事にしてお送りします。
▶動画はこちらからご覧いただけます:https://youtu.be/P409ukHLIzg

このセッションについて

ヒューマンエラーを防ぐSpring Bootプロパティ運用というタイトルで発表させていただきます。
GMOペイメントゲートウェイ株式会社の坂口と申します。どうぞよろしくお願いします。

article-108_thumb01.jpg

このセッションについてです。
ヒューマンエラー防止の考え方をもとに開発者が安全に開発できるようにするためのSpring Bootのプロパティ運用について実装例を用いてお話します。
技術的な内容としましてはSpring Bootのリファレンスドキュメントの2章と3章にありますExternalized ConfigurationProfiles、そしてGradleのビルドスクリプトになります。

article-108_thumb02.jpg

このセッションでは次のソフトウェアとバージョンを使ってコードを紹介しております。
Javaは最新のLTSである17を、Gradleは8.0。Spring Boot3.0。加えて、コード量削減のためにLombokを使用しております。

自己紹介

article-108_thumb03.jpg

改めまして、坂口健太と申します。
GMO
ペイメントゲートウェイで決済システムの開発に従事しております。
本社は渋谷にありますが、開発拠点は福岡にもありまして、私は福岡オフィスの方に在籍しております。

本日はJJUGのために出張してきています。
入社後、バックオフィスの開発を経験し、現在は後払いの開発を担当しています。
好きなプロダクトはGradleです。そのため、本セッションでもSpring Bootの機能を補う形でGradleのコードを紹介しています。

ヒューマンエラーとは

まずヒューマンエラーとは、厚生労働省の職場の安全サイトには意図しない結果を生じる人間の行為と記載されています。

article-108_thumb06.jpg

例えば左側のイラストのように、マニュアルを見ながらパソコンを操作していたとします。
その場合に操作を誤った結果、右側のイラストのようにデータが飛んでしまったというようなことです。人間が関わっている以上、ヒューマンエラーの発生は避けられないですし想定できる要因だけでなく、思いもよらぬ要因だったりするなど、多種多様な要因で発生します。
では、どういう流れでヒューマンエラーの対策をして行くのか。次のスライドで説明したいと思います。

ヒューマンエラー対策の流れ

article-108_thumb07.jpg

ヒューマンエラーの対策の流れは、このようになります。
まずはヒューマンエラーをリストアップします。実際にインシデントに繋がったものだけでなく、些細なヒヤリハットなどもリストアップします。
次にそれらを分類します。こちらは次のスライドで説明致します。
そして、なぜ発生したのか、どのような要因によって発生したのか背後要因を洗い出します。
続けて対策案を列挙します。
さらに列挙した対策案のうち、コストや時間などを踏まえて実行可能な対策を選択し、ようやく対策を実施します。
最後に実施した対策に効果があったのか、新たな問題点が無いかなどを評価します。

article-108_thumb08.jpg

それぞれのステップを全てお話しますと、Javaの要素がないままセッション時間が過ぎてしまいますので、本セッションでは、この黄色で塗りつぶしているヒューマンエラーの分類、背後要因、対策の3つをお話して本題に入りたいと思います。

ヒューマンエラーの分類

article-108_thumb09.jpg

ヒューマンエラーの分類につきまして。
最初のスライドにありましたイラストをもとに説明いたします。
左側のイラストのように、マニュアルを見ながらパソコンを操作していたとします。
その場合にパソコンを操作している人が何かを誤った結果、右側のイラストのようにデータが飛んでしまった。この何かを誤ったという部分がヒューマンエラーということになります。このヒューマンエラーを分類すると。

article-108_thumb10.jpg

「認知」「判断」「記憶」「行動」の4つに分けることができます。
ヒューマンエラーは認知する、判断する、記憶する、行動する、これら機能が適切に働かない場合に起こります。

article-108_thumb11.jpg

  • 認知では作業指示を聞き間違える、マニュアルを読み間違えるなどの認知エラー。
  • 判断では状況判断や大丈夫だろうといった思い込みなどの判断エラー。
  • 記憶では忘れたなどといった記憶エラー。
  • 行動では入力ミスや操作ミスなどの行動エラー。

このようにそれぞれの機能ごとにエラーを分類できます。
今回は認知に起因するエラーの背後要因をあげたいと思います。

認知エラーの背後要因(例)

article-108_thumb12.jpg

認知エラーの背後要因の例としましては

  • マニュアルが読みにくい。
  • モニターが小さすぎて文字が読みづらい。
  • 部屋が暑くてボーッとする。
  • 2日酔いで頭が痛い。
  • 疑問点を上司に聞きたいが聞きづらい。

このように背後要因は作業者本人以外にも本人を取り巻く環境が要因となるものがあることがわかるかと思います。

article-108_thumb13.jpg

これらを分類すると
ソフトウェアの問題、ハードウェアの問題、環境の問題。心身の状態能力や経験、モチベーションなどといった自分自身の問題。人間関係コミュニケーションの問題に分類できます。

ヒューマンエラーを対策する上で要因を明らかにすることは、トラブルの発生を最小限にとどめるためにも重要な手順だと考えています。
ただ、本セッションではヒューマンエラーの分析手法については深掘りしておりませんので、興味がある方はヒューマンファクターのキーワードで調べていただけたらと思います。

ヒューマンエラーの対策

続きましてヒューマンエラーの対策について。

article-108_thumb15.jpg

ヒューマンエラーの対策は、大きく3つに分けられます。

  • ①ヒューマンエラーを未然に防ぎヒューマンエラーの発生確率を低減する発生防止の対策。
  • ②ヒューマンエラーが起こっても速やかに検出する波及防止の対策。
  • ③ヒューマンエラーが起こった場合に、被害を最小限に抑える影響緩和の対策。

① 発生防止

article-108_thumb16.jpg

発生防止から順に説明致します。
この表はヒューマンエラーの発生確率を低減させるものとして、それぞれどういう対策があるのかを挙げたものになります。
上から順に効果が大きくなるため、対策案を検討する際は上から順に検討することをお勧めします。
1から4につきましては本題の方で説明しますので、ここでは割愛します。
5の安全を優先するは「わかるだろう」と暗黙知とするのではなく、このメッセージが表示された場合は次に何をするのか、表示されなかった場合はどうするのかなどの判断基準を明確にして判断に起因するエラーを防ぐことや重要な決定をする際に、デフォルトの選択を実行でない方にすることで行動に起因するエラーを防ぐなどになります。

例えば削除ボタンを押した際にデフォルトでキャンセルが選択された状態でダイアログが表示されるなどすることによって、勢い余ってエンターキーを押したとしても処理が実行されなくなるということです。
最近では単にボタンだけでなく削除とかメッセージを入力要求するようなものもあるかと思います。

ステップ6の知識や能力を身につけるは、サービスやプロダクトへの理解を深めるだけではなくて危険予知のトレーニングをしたり、ヒヤリハットの事例を共有したりして、エラーを予測する力を身につけることで、認知判断行動のエラーを防ぎます。

② 波及防止、③ 影響緩和

article-108_thumb17.jpg

波及防止では自分で気づけるようにしたり、自分以外の人や仕組みでエラーを検出できるようにします。
影響緩和は様々な手段でエラーに備えます。体制プラン、バックアップ保険などになります。

ここまでがヒューマンエラーに関する内容でした。

次の章より本題のSpring Bootのプロパティ運用に関する内容をお話します。

Spring Bootのプロパティ運用のリスクとは

まずはSpring Bootのプロパティ運用のリスクについてお話します。
今回は3つ挙げてみました。

想定される問題①

article-108_thumb19.jpg

1つめ、ローカル環境でgit statusコマンドを実行した場合に、スライドのイメージのようにsrc/main/resourcesディレクトリの下のアプリケーションやファイルの変更状態がmodifiedになっているようなケースです。この場合、いくつかのケースによって、本番環境にデプロイされてしまうことが考えられます。そのケースとは「部分コミット(hunk)したつもりが全量コミットしてしまった」「いつもは意識的にコミットしないようにしていたが期限が迫っていたため、あまり確認せずにコミットしてしまった」「いつも通り大丈夫だろうと思いあまり確認せずにPull Requestをマージしたなどです」

想定される問題②

article-108_thumb20.jpg

2つめは開発者がプロファイル(Spring Profiles)を指定することがある場合。

想定とは異なる環境に接続してしまう問題。イラストのように、ローカル環境に接続するつもりがステージング環境に接続してしまうようなことです。

想定される問題③

article-108_thumb21.jpg

3つめがプロパティに間違った値を設定してしまう問題。数値の桁や単位の誤り、値の範囲の誤りなどが考えられます。
スライドでは30万という値を設定しようとした場合に誤ってゼロを1つ多く300万というのを設定してしまったというようなことですね。もしくはプロパティの定義が秒の単位で指定する箇所をミリ秒の単位を指定するものだと思ってしまって3000という値を設定したなどの場合、これらの値が間違った値でデプロイされてしまうという問題です。

次の章よりこれら3つの問題を防ぐためのSpring Bootのプロパティ運用についてお話したいと思います。

Spring Bootのプロパティ運用改善

article-108_thumb23.jpg

Spring Bootのプロパティ運用改善の章では発生防止に関する対策のステップのうち、1から4の対策をそれぞれ適用していきます。

ステップ1、作業そのものを無くす・減らす。
ステップ2、人が作業しなくても良いようにする。
ステップ3、仕様外の入力や操作をできないようにする。
ステップ4、UI/UXを改善し、分かりやすくする。

article-108_thumb24.jpg

この章の目次としましては上のようになっています。
4つのステップに対して合計6個の対策をご紹介いたします。
どの対策も工夫レベルのものとなっていますので、導入の難易度は高くないと思っています。

1.作業そのものをなくす・減らす

まずは「作業そのものを無くす、または減らす」です。
ヒューマンエラーにつながる作業そのものを無くす、または減らすことで、ヒューマンエラーの発生確率を低減します。

article-108_thumb26.jpg

Spring Bootのプロパティ運用についてはsrc/main/resourcesディレクトリ下のapplication.yaml を変更しなくてよいようにします。

図のように、git statusコマンドを実行した際にapplication.yamlが、modifiedして出る機会を極力少なくすることで、「コミットしてしまった」というヒューマンエラーを回避することができます。

-1.プロパティを環境変数で渡す

2つめの対策は、プロパティを環境変数で渡す。

article-108_thumb28.jpg

環境固有の設定、たとえばデータベースへの接続文字列をイメージしていただくとよいと思います。
そういう値をapplication.yamlを変更せずに環境変数経由で渡すようにします。
単純にOSの環境変数をメンテナンスするのは非常に面倒ですので方法としましては一般的にdotenvといわれる機能があるのですがそれを利用します。
今回はdotenvGradleプラグインというのが、一般にコミュニティプラグインとしてあるようですが、Gradleのカスタムプラグインの実装をしたものを紹介したいと思います。

article-108_thumb29.jpg

まずはカスタムプラグインのビルドスクリプトを書きます。buildSrcディレクトリにbuld.gradle.ktsファイルを作成します。pluginsブロック、2行目のところですね、java-gradle-pluginを指定します。6行目以降のgradlePluginブロックではプラグインの名前や実装クラスを指定します。

article-108_thumb30.jpg

次にプラグインの実装クラスになります。

プラグインインターフェースを実装しており、上の方の1つめの赤枠の部分でJavaのプロパティファイル、 java.util.Propertiesクラスのファイルを読み込みGradleのタスクで環境変数として渡せるようにプロパティズクラスをMapに変換しています。

Spring Bootではローカル環境で開発する際は、bootRunタスクとtestタスクを使用するかと思いますが、2つめの赤枠の部分ではそのbootRunタスクとtestタスクに対して1つめの赤枠でロードした環境変数のMapを追加しています。

article-108_thumb31.jpg

1つめの赤枠内で使用していたメソッドはそれぞれこのようになっています。
プロパティファイルを読み込む処理とjava.util.Propertiesクラスをキーと値をString型とするMapに変換しているだけですので、ここでコードの紹介だけで次のスライドに進みたいと思います。

article-108_thumb32.jpg

続いて、実際のプロジェクトに作成したdotenvプラグインを適用します。
pluginsブロックでプラグインIDを指定しているだけです。

article-108_thumb33.jpg

環境変数経由でプロパティを渡す場合、application.yamlプロパティの値名前を一定のルールに従って変換します。ドット(.)をアンダースコアに置換、ダッシュ(-)を削除、大文字に変換。
表にはデータベース接続時のドライバークラス名を指定する際のspring.datasource.driver-class-nameというプロパティを環境変数として渡す場合、どういう名前にする必要があるかというのを記載しました。

なお、環境変数経由でプロパティを渡す場合、application.yamlのプロパティ値を一定のルールに従って変換します。

article-108_thumb34.jpg

実際に実行した結果をもとに説明したいと思います。

画面の左上がenv.propertiesファイルになります。ここでは先ほど説明しました。バインディングのルールに従って、APP_MODEを大文字、アンダースコア区切りで定義しています。下のアプリのソースコードではapp.modePropertyを取得してログ出力するコードを書いています。
右上はSpring Bootのアプリケーション実行後の標準出力の結果となります。

Gradleプラグインを用いてenv.propertiesファイルの中身を環境変数として読み込み、それがSpringフレームワークのConfiguration Propertiesとして参照されることが確認できるかと思います。

1-2.プロパティファイルをクラスパス外に配置する

作業そのものをなくす・減らすの2つめの対策は、プロパティファイルをクラスパス外に配置する、です。

前提としましてはsrc/main/resourcesディレクトリについてお話します。

article-108_thumb36.jpg

src/main/resourcesディレクトリはMavenGradleの標準ディレクトリレイアウトでリソースファイルを配置する場所となっています。
リソースファイルは、設定ファイルや画像ファイルなどのJavaコード以外の静的なファイルを指します。このディレクトリはビルド時にターゲットクラスパスにコピーされます。
つまりsrc/main/resourcesディレクトリ内のファイルは、Jarにバンドルされます。クラスパス外に配置するということは、src/main/resourcesディレクトリに以外の場所に配置するということを指します。

article-108_thumb37.jpg

クラスパス外に配置することで、Jarにバンドルされなくなります。ということは、誤ってコミットした場合でも、リポジトリには変更が記録されてしまいますが、Jarに入らないため、プロダクション環境には影響しないということになります。

article-108_thumb38.jpg

このイメージはIntelliJ IDEAのプロジェクトビューでプロジェクトのファイルディレクトリ構造を表示したものになります。真ん中の赤枠のsrc/main/resourcesディレクトリ下のJavaディレクトリとリソースシーズンディレクトリ、この2つがJarにバンドルされます。
上と下の赤枠の部分がJarにバンドルされない箇所になります。かつSpring Bootが標準で読み込んでくれる場所となっています。

具体的には、プロパティファイルは表に記載の順序で、プロパティファイルが読み込まれます。
1
番目と2番目はクラスパスにあるプロパティファイル、3番目と4番目はカレントディレクトリまたは、作業ディレクトリにあるプロパティファイルになります。この順序で読み込まれるため、採用される値は後勝ちとなります。
そのため、クラスパスに本番環境の値が設定されていたとしてもカレントディレクトリ側が開発環境の値を設定することで、本番環境のクラスパスの中にあるapplication.yamlを設定変更せずに開発環境として実行することができるようになります。

article-108_thumb39.jpg

article-108_thumb40.jpg

先ほどのイメージで説明しますと、まず、クラスパスのapplication.yamlが読み込まれます。

article-108_thumb41.jpg

article-108_thumb42.jpg

次に、2番目はスキップしていますけど、カレントディレクトリの直下に、あるapplication.yamlが読み込まれます。最後にカレントディレクトリの下にあるconfigディレクトリの下のapplication.yamlが読み込まれます。

article-108_thumb43.jpg

こちらが実際に実行したものになります。
上の段がclasspathの外に配置したプロパティファイルで開発環境という値を設定しています。
下の段がclasspathに配置したプロパティファイルで、プロダクション環境という値を設定しています。この状態でアプリケーションを実行するとログにはモード開発環境と出力されています。

src/main/resourcesディレクトリのapplication.yamlを変更することなく、開発環境向けにプロパティを変更できることが確認できたかと思います。

2.人が作業しなくてもいいようにする

次は人が作業しなくてもいいようにする、です。

article-108_thumb45.jpg

これは手作業をなくしてツールやシステムに置き換えることで、手作業に起因するヒューマンエラーが起きないようにするということです。

2-1.ビルドツールに任せる

Spring Bootのプロパティ運用においては、ビルドツールに任せるということになるかと思います。

article-108_thumb47.jpg

どんなことをビルドツールに任せるかですが、いろいろあるかと思いますが、私はSpring Profilesのプロファイルの指定をビルドスクリプトに設定することをやっています。

そうすることで、手作業で接続環境を指定しないので誤った環境へ接続するのを防ぐことができると考えています。

article-108_thumb48.jpg

具体的にはローカル環境でSpring Bootのアプリケーションを開発する場合とbootRunタスクとtestタスクの2つのタスクぐらいしか実行しないかと思います。
それらのタスクに対して、それぞれspring.profiles.activeというシステムプロパティをGradleのタスク定義のところにセットするようにしています。

3.仕様外の入力または操作をできないようにする

3つめは、仕様外の入力または操作をできないようにする、です。

article-108_thumb50.jpg

通常はプロパティに誤った値が設定されている場合でも、そのプロパティが実際に呼び出されるまでは問題に気づけないかと思います。誤った値ではアプリを起動できないようにすることで、非常に早い段階で問題を検出することができるようになります。

3-1.ConfigurationPropertiesValidationを活用する

具体的にはConfigurationPropertiesValidationを活用することです。
ConfigurationProperties
Validationを活用すると二つの利点があります。

1つめは、Validationをパスしないとアプリが起動しない起動できないためアプリ起動時という非常に早い段階で問題に気づける点。
2
つめは、Validationのルールをプロパティクラスに定義するため、どういう値が妥当なのか必須入力なのかどうかなど、仕様が把握しやすい点です。

article-108_thumb52.jpg

article-108_thumb53.jpg

具体的には、このスライドにちょっと詰め込んでいます。左上がプロパティクラスにValidationの条件を設定しているところ。今回はmonthというローカル変数プロパティに対して最小値が1と最大値が12というのを条件としています。右上がプロパティに設定した値になります。今回はわざとエラーになるように0という値を設定しています。標準出力を見るとapp.monthプロパティにセットされた値や定義場所。Validationのエラー理由などが出力されアプリケーションの起動に失敗しています。

4.UI/UXを改善し、分かりやすくする

4つめは、UI/UXを改善し、分かりやすくする、です。

article-108_thumb55.jpg

これは視認性、識別性、操作性を向上することで、作業を容易化・確実化することです。
読み間違えや判断の誤り操作ミスなどのヒューマンエラーになる確率を低減できます。

4-1.数値の区切り文字を活用する

1つめは、数値の区切り文字を活用することです。

article-108_thumb57.jpg

これはJava 7で実装されたJava言語の改善になります。
実は、Javaでは数値リテラルの桁の間のどこにでも任意の数のアンダースコアを含めることができます。この仕様を活用することでスライドにあります数値も一瞬で読み取ることができるかと思います。
ちなみにスライドの数値は十億ですね。3桁区切りで定義しているので、すぐに認識できるかなと思います。

※セッションでは、「Java言語の仕様」と説明をしていましたが、正しくは「YAMLの仕様」でした。Java言語の仕様にも数値リテラルにアンダースコアを含められる仕様がありますが、application.yamlの部分においては、YAMLの仕様となります。

4-2.Properties Conversionを活用する

UXを改善し、分かりやすくする、の2つめはProperties Conversionを活用するです。
Spring Bootでは時間日付データサイズの3つのプロパティ変換の仕組みが標準であります。
この仕組みを活用すると、例えば15分という値を設定したい場合、15mと定義することができます。
仮に、ロング型で単位はミリ秒で設定しないといけないとかだと、暗算したりgoogleで検索したりする必要があるかと思いますが、それが不要になります。Properties Conversionを活用すると二つの利点があります。

1つめは数値に加えて単位を指定することができるため、可読性が良い点。
2つめがわざわざ単位変換をする必要がない点。

article-108_thumb59.jpg

まとめ

article-108_thumb61.jpg

まとめになります。
ローカル開発やテストを目的とするsrc/main/resources/applicationの変更は避ける。
プロファイルの指定はビルドツールに任せる。
安全なバインドやValidationを活用する。
認知しやすい表現や型を使う。

article-108_thumb62.jpg

Spring Bootの仕組みを活用し、ヒューマンエラーを防いでもらいましょう。

※本コンテンツ内容の著作権は、GMOペイメントゲートウェイ株式会社に属します。

SSL GMOグローバルサインのサイトシール