😽

金融の基幹システムを1年半かけて.NET 6に移行した話

2022/08/26に公開

はじめに

本稿は「.NET 6移行祭り! C# Tokyo」イベントで発表した「金融の基幹システムを1年半かけて .NET 6に移行した話」の内容を文書化したものです。

[2022.08.28追記]

さて、はじめにおことわりを。

おもったより大きな反響があって、想定より多く読まれており、とくに正しく伝えられていない箇所があると思い、少し補足を入れました。

ここで基幹システムといっていますが、金融の勘定系システムという意味ではありません。

基幹システムというとCore Systemという意味(これは勘定システムでしょうね)と、Mission Critical Systemの2つがあると思います。

本稿の対象は後者で、システムのお客様が、Mission Critical Systemと判断されて基幹システムとして扱われています。

金融の勘定系とは規模や複雑性、クリティカルな度合も異なりますが、お客様からみたときにMission Criticalだと思われるシステムで、ライフサイクルの短い.NETとどう付き合っていくか?というのがお伝えしたかった内容です。

[2022.08.28追記おわり]

Agenda

  1. プロダクト概要
  2. プロジェクト概要
  3. 顧客をどう説得したのか
  4. テスト戦略

まず、対象のプロダクトがどんなものかお伝えし、移行プロジェクトの概要をお話します。その際にどうやって顧客を説得したか、どんなテストを計画したか、お話しします。

なおアイスブレイク部分は、本文から削除しています。

プロダクト概要

さぁ、本題に入りましょう。まずは対象プロダクトの概要からです。

業種的には金融のお客様で、対象のプロダクトは仮称Image Document Workplace、通称IDWです。

IDWは画像化された手書き書類を扱うワークプレイスで、2013年に初期バージョンの開発が開始され、当初の利用者数は200人程度でした。以後、継続的に、年50~100人月程度の追加開発が行われ徐々に適用業務も広がり、2020年の12月にはついに基幹システムに昇格しました。

全国の営業店で受け取った書類は、その場で画像化され、以後はすべてIDW上で業務を行います。


 
構成はこんな感じの、よくあるクラサバです。裏側に画像識別などをするサーバーが並列化されているところが、すこし変わっていますね。また、インターネットに接続されていなことが死ぬほど不便です。

移行前の環境ですが、クライアントはWindows 10、サーバーは2008R2、.NET Framework 4.5.2で動作していました。

プロジェクト概要

で、このシステムを最新環境に移行するのが、本日お話しているプロジェクトになります。

プロジェクトのゴールは、22年の7月までに移行を完了すること。

移行に際して、業務に影響を与えず移行すること。併せて、基幹システムに見合った安定した開発と運用を実現すること。

非機能要件を見直すこと。生産性向上のための機能追加・改修。そして、クラウドへ移行することだったんですが・・・

クラウドへの移行は見送られました。理由は色々あるんですけど、マインナンバーを扱うシステムで、コンプライアンス的な問題を解決できる確証が、この時点では得られなかったと言うのが一番の理由です。

とはいえ、今後の機能追加の際には、オンプレ・クラウドのハイブリッドを視野に入れていくことになるかもしれません。

また、上記の2番3番ですが、2020年の基幹昇格時に炎上した影響が表れています。このときは冗談抜きのガチ大炎上で、今回成功裏に終えることはお客様を含めたチーム全体にとっての悲願でした。

そのため、プロジェクトをサーバー移行と、機能改修の2つのフェーズに分割しました。

最初のフェーズではOS・ミドル・.NETのバージョンアップだけを実施し、機能的な追加や改修は行いません。クライアントは、接続先を変えるだけで、新環境への移行を目的とします。これなら、最悪すぐ切り戻せます。

その後、落ち着いたところで、フェーズ2として機能改修を実施するというのが、グランドスケジュールでした。

そして、アーキテクチャ的に最大の検討課題だったのが、.NETを何にアップデートするか?ということでした。

クライアント側は .NET Frameworkで仕方がないとして、問題はサーバーサイドです。

悩ましいスケジュールなんですよね。.NET6の正式リリースに、ギリ、間に合います。選択肢は2つ。

ひとつは .NET Framework 4.8に移行して永久に塩漬けすること。

もうひとつは、.NET 6に移行して、2年おきにリプレースし続けることです。

いろいろ悩んだんですが、まぁ当時の気持ちを正直に白状すると・・・

これからまた5年も10年も、.NET Frameworkと心中は嫌だという気持ちでいっぱいでした。そう思うと、.NET6に移行すべき言い訳がいっぱい沸きでてくるんですよね。

あたらしい .NETを使うってことは、それだけで性能は向上するし、nullableみたいな機能は品質の向上に間違いなく寄与するでしょう。今後の新しい便利機能は .NET向けにでてくることも想定されます。

また今後、クラウドを視野に入れていくとした場合、当然 .NET6の方に強みがあること。

エンジニアのモチベーションもあがることも、生産性や品質の向上に効果があるはずです。

一説によると幸福感の高い社員の創造性は3倍、生産性は31%、売上は37%高いうえに、欠勤率が41%、離職率は59%低く、業務上の事故も70%少ないなんて研究結果もあるらしいです。

事故が70%少ないって、品質向上がひとつの課題であるなかとくに魅力的に見えます。そもそも・・・

弊社は、社員のエンゲージメントを高めて、イキイキと働くことで

お客様にもエンパワーしようと、いうのは会社のビジョンだったのではないか?と、まぁ湧き上がる「自己正当化」ストーリーの数々。こういう一方的な視点って、エンジニアの悪癖のひとつですよね。

これじゃ、だれも納得してくれないと言うことに、私は20年気が付かなかった気がします。いまも心から、理解できていないかもしれません。

顧客をどう説得したか

では、どうすれば納得してもらえるのか?やはり基本に立ち戻って、お客様の視点で考えてみるしかないと思います。

お客様からみた .NET6ってどう見えるでしょうか?話しはだいぶ単純化しているので、もちろんこれですべてではありませんが・・・

まず、2年おきにリリースするってことは、つまり「そのつど、障害発生のリスクがあるってことですよね?」と、思われるでしょうね。当然、リリースにはテストだなんだでコストもかかります。

この辺が、受け入れがたい一番の障壁になってくるのかなと、私は考えました。この右側に、なにかを積み上げて・・・

天秤を倒さないといけないわけです。さぁ、どうやって倒しましょうか?

ところで冷静になって考えたとき、まずこの2つって、同じウェイトではないですよね。時と場合によって、もちろん変わってくるんですが・・・

今回に限ると、私はこんな風に感じました。技術的リスクのほうが圧倒的に解消しやすいなと。なぜか?まず前提として・・・

今回 .NET6にすること自体は、大きなリスクはないと考えていました。まず .NET Frameworkに依存する機能がほぼないことは分かっていましたし、十分な体制があり、十分な時間をかけることが可能で、資金も必要なだけあったからです。

リスクは、あくまでも今後2年ごとに、更新し続けることにありました。その上で、2年ごとの技術的なリスクはどの程度のものなのか?

.NET Frameworkから .NETへの移行は破壊的変更が多数ありましたが、.NET6から8へは、基本的に後方互換性が期待できると考えています。すくなくとも、Frameworkからの移行と比較した場合、圧倒的にリスクは小さいはずです。

つまりこのプロジェクトにおいて、「再現性ある自動テスト」が、「十分な品質」で用意できれば、移行時のリスクは最小限にできるだろうと考えました。

そういった視点で、プロジェクトのゴールを振り返ってみましょう。

今回のゴールの1つには、そもそもテスト品質の改善が含まれています。

ゴールが達成できれば、おのずと技術的なリスクは、打ち消せると考えました。

残るはコストです。これは難敵です。何をもって倒すべきでしょうか?

品質や生産性の向上、将来的なクラウド適正も考慮して、これで倒せるでしょうか?

そんなわけ、ないんですよね。

これらはそもそも、同じ尺度で定量的に比較するのは、不可能とは言いませんが非常に難易度が高く、しかもあくまで推測にしかなりません。事実にはならないですよね。

この状態では、確実に「やらない」となります。

では何をのせるのか?じつはプロジェクトゴールにヒントがありました。それは・・・

業務・システムともに習熟したメンバーの育成です。

そもそもメンバー育成が、ひとつのゴールとなったのには理由があります。

このプロダクトは9年間、継続して開発してきましたが、その間チームメンバーは入れ替わり続けました。だいたいこのあたりから、初期メンバーは私以外いなくなって、徐々に品質が維持できなくなって・・・

基幹昇格のタイミングで大炎上しました。

このときの炎上は、開発サイドにも、顧客サイドにも多くの問題がありました。

開発サイドの最大の問題は、未改修の機能で業務が停止する障害を発生させてしまったたことでした。それは改修における影響範囲が、正しく把握できなかったことが要因でした。

そもそも、想定可能なメンバーは私一人しかいなかったんですね。そして「私が」見落としました。

その結果、習熟度の高いメンバーを育成することは、大きな目標となったわけです。

でも実際、メンバーの育成ってすごい難しいですよね?一度育てればおわりではなく、育成後は維持しなくてはいけない。そこでふと思いました。

.NET6へ定期的にアップデートすることで、メンバーの維持ができるのではないか?ということです。

これまでの実績からも、おおよそ2・3年でメンバーは入れ替ることが分かっていました。では2年おきに再教育するとして、最適な教育は何か?

お客様含めて、さんざん話し合った結果、ビジネスシナリオのテストを全部やり直すのがベストだろうとなりました。

そして .NETのライフサイクルは3年で、LTSのリリース間隔は2年です。

そうです。再教育兼ねて .NETをアップデートすることで、なんと実質無料でテストできるのです!

ここに、どどん!とメンバー再教育コストを載せることで、ちゃんと対比して比較できる構造となって、釣り合ってることが、お客様にも理解いただけるようになりました。

あとはシンプルです。ここに一度取り下げた、これらを載せなおします。

性能や品質・生産性が向上することの価値自体は、エンジニアじゃなくても当然理解できています。これらを釣り合った状態で天秤にのせれば、十分に傾けられるわけです。こうして私は .NET6へ、移行する理解を得ることができました。

ところで・・・

なんか話が旨すぎない?と思った人。正解です。

じつはプロジェクトゴールが決まる前に、私の頭の中にこれがありました。

コストや技術的リスクに釣り合う要素って何があるだろうと、ずっと考えていたわけです。

そして、これなら、納得してもらえるかもしれないと考えました。

プロジェクトゴールをこう説明しましたが、実は当初の顧客要求は少し異なっていました。

このあたりは、お客様が言い出したことではありませんでした。しかし、このプロジェクトとは別の保守・運用の課題として、弊社とお客様の間で、この問題はずっと課題として管理されていました。そこでこれらをマージして・・・

私たちから、お客様に、プロジェクトゴールとして、これを提案したわけです。

もちろん、ここまでにお話しした、具体的な施策もすべて提案に含めました。あえて伝えなかったのは、私が .NET 6にしたくて仕方なかったことだけです。

テスト戦略

さて、これだけでは技術的な話が何にもなくてさみしいので・・・

テスト品質の改善について、今回練り直したテスト戦略をおはなししつつ、実際のデモを見ていただこうとおもいます。(デモは動画をご覧ください)

ただテスト戦略を理解していただくためには、その前提となる、ドキュメントとテスト体系について触れる必要がありますので、ちょっと寄り道させてください。

このプロジェクトのドキュメントとテストは、ざっくりこんな関係になっています。

業務システムなので、トップの概念として業務が存在し、業務には複数の業務シナリオが含まれています。

業務シナリオは、1つ以上の機能を利用して実施し、機能には同じように複数の機能シナリオが含まれます。

これらを受けて実装し、機能シナリオと1:1になる機能テストを実施します。

機能テストが通ったら、業務シナリオと1:1となる業務テストを実施する。

こんな感じのV字モデルになっています。

さて今回のプロジェクトのゴールをもとに、設計とテストの体系を考えたとき、業務テストと機能テストが、プロジェクトのゴールが、それぞれこんな感じで結びつくだろうと考えました。

メンバーの育成はおもに業務テストで担い、テスト品質の改善対象は、業務テスト・機能テストどちらもスコープにはいります。

.NETバージョンアップの主役は機能テストです。そのため、この後は機能テストに絞ってお話します。

今回のプロジェクトを開始する前の機能テストの状態ですが、テストケースとしては、おおむね十分なものがそろっていました。またテストを実施するための、再現性の担保されたテストデータは揃っていましたが、テストは手動で実施していましたし、期待結果との比較は目視で行っていました。これらを自動化することをゴールとしました。

では具体的にどうやって自動化していくか?

実際に実施したのがこちらの方法です。

まず現行の .NET Frameworkのバージョンで手動テストを実施して、実施後にDatabaseの全テーブルをダンプして期待値を作成します。

その後、公式ガイドにしたがって .NETへ移行し、テストを自動化し、実行結果を再度ダンプして .NET Frameworkでの期待値と比較します。

こうすることで・・・

バグも含めて現行動作を担保できるはずです。

バグもって思うかもしれませんが、10年も動いているシステムのバグは、利用者にしたらもう仕様ですよね。それを前提に業務設計されているので、勝手に直すわけにいきません。もし改修が必要であれば、機能追加・改修フェーズで実施することにしました。

さて、先ほどさらっと・・・

全テーブルをダンプして比較と言いましたが、まじめに考えると、結構めんどくさいです。

たとえば時間の扱いとかですね。というのは、時間は期待結果が可変になる可能性があるからです。

時間の期待値としては、ざっくり3パターン考えられるんですが、1つ目のパターンが、期待値が固定となるパターンです。これはおもにテスト開始前に登録されて、テスト実施後に意図せず変更されていないか確認するパターンです。

次が、期待値が可変で、かつ、テスト開始前の時間になるもの。たとえば「テストするためにテスト開始前で、なおかつ5分以内のデータが必要です」みたいなケース。

そして逆に、可変で、なおかつテスト開始後の時間になるケース。一番分かりやすいのは更新日時とかですね。

テストが難しい要素は、もちろんこれだけではありません。いまお話した時間や、たとえばログのホスト名みたいな環境情報。冪等性が担保されていないサービス・ライブラリの依存なんかがメンドクサイです。

3はそっちを直せるなら、直せばいいんですが、たとえばPDF生成機能なんかがあった場合は難しいです。PDFのメタデータには作成日時なんかが含まれていて、同じバイナリーには絶対になりませんし、じゃぁPDFの中身解析するかというと、それはまたメンドクサイという・・・

これらはとても、個別にガンバルような内容じゃないですよね。ということで、ツール&ライブラリーを作りました!

ということで、DatabaseからCSVでエクスポートして期待値を作成するツールと、テスト実施後にDatabaseとCSVを比較するライブラリです。

さてリポジトリをのぞいてみると、これら5つのディレクトリがあります。

DbAssertionsはコアライブラリで、一応DB非依存で作成していて、2・3から利用されています。

2番目はDbAssertionsのSQL Server向けの実装です。大きく2つの役割を提供していて、DBから値をエクスポートして期待値ファイルを作成したり、ユニットテストに期待値と比較するAssertionを提供します。3番目のAppやテストコードから利用されます。

3番目は、2番目を実行するためのコンソールアプリケーションで、4番目は1・2のテストコード、5番目は、今回デモで利用するサンプルの諸々のリソース置き場です。なお、今回これらの内部コードについては説明は割愛します。中身知りたい人は勝手に見るでしょうし、なんなら、もうそっちのけで見てる人いるんじゃないですかね。

まぁそれはさておきデモのお題ですが・・・

いつもありがとう。AdventureWorksさんを利用します。dockerイメージが公開されているのでそれを使います。

デモの対象はPersonスキーマのPersonテーブルを利用して、テスト前に1行目の更新日時を現在時刻に更新します。

そしてテストでは、2行目の、TITLEをMr. に、更新日時を現在時刻に更新します。

なおこのデモで使うスクリプトやSQLも先ほどのリポジトリのSampleフォルダーの下に格納しています。

さて、テスト自動化の手順ですが、まずはAdventureWorksをまっさらな状態で起動して、テスト前の状態にデータ更新するため、Initialize.sqlを実行します。

その後、自動化対象を実行します。今回は、アプリではなくて簡略化するためSQL単体にしておきます。具体的にはUpdateTitle.sqlを実行します。

そしてツールでDBをダンプします。

この1~4までを2回実行します。なぜ2回なのかは、後ほど実際にやりながら確認してもらいます。

これで期待結果が作成できるので、あとはテストを自動化すれば完成です。

では実際にみてみましょう!動画を!

というわけで、こんな感じでテストの自動化を、DBサーバーとAPサーバー上の、全48プログラムに対して実施してすべて自動化しました。

DbAssertionsは、一般の機能テストにも使えないことはないんですが、ちょっと使う局面が限定的なので、現時点ではドキュメントとか書いていません。

もし、私も使いたい!って人がいましたら、ドキュメント書きますので、Issueにその旨記載していただければ対応したいと思います。

まとめ

というわけで、まとめです。

プロジェクトスポンサーから見た場合、.NET Frameworkが選びやすいのは事実だと思います。

今回は私が顧客を説得できたシナリオを紹介しましたが、背景に強く依存した内容で、これをそのまま流用するというわけにはいかないでしょう。

大切なのはwin-winなシナリオを見つけることです。技術者として、よりモダンな環境に身を置けるようにするため、皆さん自身のwin-winのシナリオを見つけましょう!

Discussion