ソフトウェアのポータビリティを考える
ポータビリティ(携帯性, 移植性)は、ソフトウェア開発のプロセスにおいて無視できないと考えています。
この性質が高いほど品質が担保され、かつ、開発し易くなるのではないでしょうか。
今回はポータビリティに焦点を当てて、ソフトウェアのアーキテクチャを考察してみます。
なぜポータビリティが重要なのか?
そもそも、ソフトウェア開発の現場で「ポータビリティはどうなのか?」という観点で評価することは少ないと思います。
私も長年気にせずに業務していましたが、私のエンジニア人生のキーパーソンである "師匠" (後々お話しできればと思います)から、
このポータビリティの重要性を説いて頂きました。
すごく単純に表現するならば、そのソフトウェアは「いつでも」「どこでも」動く方が良い、ということです。
- ローカルだと E2E の動作確認ができない
- 色々と事前準備しないと正しくテストが実行できない
上記の様な状態は「ポータビリティが低い」と判断できます。(よくある事例と思いますが)
ポータビリティが低いと様々な負債を抱えることになります。
- ローカルで動作確認できないから動くか分からないけどコードをマージする
- 途中参加した開発者がローカルの環境構築だけで数日要してしまう
- ローカルだと Pass したテストが CI サーバーで Fail してしまいパイプラインが詰まってしまう
- (大人の理由も込みで)インフラ関連の変更が必要になった場合の作業コストが膨大になってしまう
パッと思いつくだけでもこれだけあります。
ポータビリティを高く維持することで、上記の様な負債をあらかじめ生じさせない様にできるでしょう。
これは、アジャイル開発における「品質を作り込む」ことの1つだと考えることもできます。
具体的なポータビリティを高める工夫
私がこれまで行ったポータビリティを高めるための工夫について記載していきます。
どれも「当たり前のこと」と思われた方は、既にポータビリティを高めるための活動ができていると思います!
外部への依存を減らす(もしくは依存性を逆転させる)
これはオブジェクト指向プログラミングや SOLID 原則をご存知であれば、改めて詳しく述べる必要はないでしょう。
昨今のパブリッククラウドの技術発展により、簡単に汎用的な機能を実装することができるようになりました。
- メッセージング
- メール
- 認証・認可
など、Webサービス、業務アプリケーション問わず利用する機能は、パブリッククラウドが提供するマネージドサービスで事足りるケースがほとんどです。
さらに、 OSS でも上記と同様に、様々な汎用的な機能を提供するサービスが存在します。
ソフトウェア開発時にこういったサービスを利用することは、開発期間の短縮にも繋がり、採用されることが多いと思います。
一方で、コードベースで見たときに、がっつりと業務ロジックとこれらのサービスが依存してしまうと、後々痛い目に会うことが結構あります。
一般的には DIP(依存性逆転の法則)
などで回避することが多いと思いますし、あえて業務ロジックと外部サービスの間に、自前の機能を挟むことで抽象度を上げる方法もあります。
上記は業務アプリケーションが Keycloak
を利用して認証・認可を実現する例です。
上段の図は業務アプリケーションが Keycloak
を直接利用しています。
Keycloak
にはログイン画面も用意されているため、100% フル活用すれば、認証・認可周りの実装はほぼ必要ないでしょう。
しかし、これではKeycloak
に完全に依存することになるため、
Keycloak
がサービス終了してしまった- 会社の都合で
Keycloak
が使えなくなってしまった Keycloak
を利用していることをユーザーに知られてはいけなくなった
などの状態に陥った時の改善コストは高くなってしまいます。
そのため、あらかじめ下段の様に Keycloak
への依存を吸収するようなイメージで「抽象的な認証・認可サービス」を自前で用意します。
そうすることで、業務アプリケーションは Keycloak
を利用していることを知る必要がなくなり、依存度が下がります。
この結果、例えば Keycloak
のログイン画面は利用せず自前で用意したり、Keycloak
には存在しない機能のアドオンもしやすいです。
そして、これがポータビリティとどう関係あるのか説明します。
例えばローカルで開発・動作確認する際、この「抽象的な認証・認可サービス」にモック動作する機能を用意すれば、
ローカル環境に Keycloak
がなくても認証・認可を行うことができます。
要は ローカルに Keycloak
がないのでログインできなくて、それ以降の機能の動作確認ができませんという現象が無くなります。
つまり、ローカルでも動作確認し易い(ポータビリティが高い)状態になれるという訳です。
※ Keycloak
を例にしましたが、Keycloak
であれば Docker
で簡単にローカルにも構築できるので「そこまでする必要ある!?」と思われた方はごもっともと思います。
clone 直後に単体テストを実行できるようにする
1. clone する
2. 単体テストを実行する
3. 全て Pass する
この状態を保ち続ければ、単体テストに関するポータビリティは非常に高いと言えるでしょう。
そして、それは QA(品質保証) の観点においても、非常に重要な状態だと思います。
しかし、私の経験ですが、上記の 3 ステップをスムーズに実現可能なプロジェクトは少ないです。
大抵の場合、2. 単体テストを実行する
前にやらなければならないことが多いです。
では、どうすれば良いのでしょうか。いくつか具体例を考えてみます。
- モックを利用して「対象クラス」だけのテストに注力する
- DB を利用する場合は
H2
などのインメモリDBで実施する - どうしてもミドルウェアが必要な場合は
Testcontainers
などを利用する
Java & SpringBoot
ベースの話で申し訳ないですが、、
基本的には Mockito
などを利用して、テスト対象クラス以外はモック化するのが望ましいでしょう。(それが単体テストの定義だと思いますが)
また、DB を利用する場合はJava & SpringBoot
であれば簡単に H2
などのインメモリDBを利用できるので、
あとは @Sql
アノテーションなどで各テストの初期データをセットする様にすれば、
MySQL
などがインストールされていない PC でもテストを動かすことができます。(そして何より速いです)
Testcontainers
も SpringBoot
がエコシステムを提供していると思いますので、利用する価値はあると思います。
ただ、実行時間が極端に長くなりますし、これに関しては Docker
がインストールされていないと動かないので、
ポータビリティの観点では 100 点満点とは言えないですかね。
長々と記載しましたが、冒頭で書いた 3 ステップを「いつでも」「どこでも」実行可能にし続けること、それを意識することが大切です。
Mockito
や H2
の導入などは最初の壁さえ乗り越えれば、あとは簡単です。
そこを避けて「パブリッククラウドのDBに自宅の IP アドレスを許可して直接繋ぐ」なんてことをすると、
最初は素早くテストを実装できると思いますが、後々苦しみます。
まとめ(高いポータビリティのポイント)
あまり具体例を挙げられませんでしたが、、
結局のところ下記を意識し続けることが、システム・アーキテクトの1つの重要な責務なのではと考えます。
- そのソフトウェアはローカルでも動作確認ができるか
2.そのソフトウェアどこでもテストできるか
3.そのソフトウェアを別のインフラに移植することは簡単か
3番は滅多に起きないと思いますが、企業によっては大人の事情でよくある話かもしれません。
なので便利なサービスを利用するのは重要ですが、依存性を逆転するなどして、移植し易さを常に考慮しておくべきかなと思います。