BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ アーティクル MDSDのおかげで引き続き安泰

MDSDのおかげで引き続き安泰

原文(投稿日:2009/2/25)へのリンク

要約

ソフトウェアプロダクトはいつになったら、真に完成したと言えるのでしょうか。プロダクトはほとんどの場合、製造中止になったり、後継製品が出たりしたときに寿命を迎えます。この最終段階に至るまで、ほとんどすべてのソフトウェアプロダクトが進化の一生をたどります。存続期間が長く、大規模で複雑な企業システムには、時間が経過するにつれて維持不能になり、融通がきかなくなるという独特の傾向があります。これが原因で開発速度が落ち、顧客の要求に対して反応するのに時間がかかるようになります。

この論文では、こうした不具合に対処する目的で一般的なMDSDアプローチを利用する方法を説明します。まず、下位非互換性やアップグレード問題といった、対処しなければならない問題の概略を紹介し、こうした問題を軽視すべきでない理由を説明します。続いて、こうした非機能的な問題点が今日のアーキテクチャのどこに隠れているかをお見せします。

ライフサイクルにおいて過小評価されているこうした問題点を、柔軟性を失わずに管理下におさめる方法を3つの例を使って示します。この例は、eHealth産業における現実のアジャイル・プロジェクトから借りてきたものであり、ここで確認したベストプラクティスを他のコンテキストにも応用する方法を、この例を使って説明します。

ここで学んだ教えを「経験則」としてまとめており、論文の最後には、提案したソリューション構築の基礎となっている有用なフレームワークやツールの参考文献を入れました。

序論

ソフトウェア工学においては、モデル駆動型ソフトウェア開発(MDSD)がただの一時的流行ではないことが過去数年間で証明されました。現在、MDSDが約束したこと[1]の多くが実現しており、サクセスストーリーの数はますます増え続けています。

この論文では、今日のソフトウェアシステムの高度な問題点に取り組む際にさえ活用可能な、モデル駆動型アプローチを例示します。MDSDのベストプラクティス[2]を念頭に置きながら、典型的な非機能的要件として、ソフトウェア・ライフサイクルの問題点に焦点を合わせます。

ソフトウェア・ライフサイクル

最新のソフトウェアシステムのほとんどすべてが、その存続期間中、ライフサイクルの問題に直面します。プロダクトの出荷やデプロイメントの瞬間から、下位互換性や移行可能性の戦略に関する質問に答えられなければなりません。開発中は往々にして、互換性に関する懸念を過小評価するか、完全に無視します。こうした姿勢は最終的にはしっぺ返しを受けます。なぜなら、遅ればせながらも互換性を持たせなければならなくなり、多くの資源をプロダクトにつぎ込まなくてはならなくなるからです。次の例でわかるように、この件を深刻視すれば、ソフトウェアプロダクトの発展に直接の影響を及ぼす可能性があります。

java.lang.Cloneableのケース

Javaのjava.lang.Cloneableインターフェースは、下位互換性がソフトウェアの発展をいかに制限するかを明示しています。JDK 1.0で導入されたこのインターフェースは、単なるマーカーインターフェースであり、何のメソッドも提供しません。クローニングをサポートしたい型には、このインターフェースの実装が必要です。さらに、 java.lang.Object.clone()も適切に実装されなければなりません。java.lang.Cloneable内でclone()メソッドを定義する方が、より合理的で自然なAPIのように思われます。このわかりきったインターフェースの変更は決して行われませんでしたが、「下位互換性をだめにする」というのがその理由でした。この新しいインターフェースメソッドを導入していたら、その時点までにこのインターフェースを実装した世界中の全クラスにコンパイルエラーを起こしていたでしょう。この古いバグの完全な歴史をご覧になるには、[3]をご参照ください。java.lang.Cloneableに関する詳細な議論については、[4]をご覧ください。

私たちのシステムにおける既存のライフサイクル問題を細かく分析する前に、まず一般的な用語を提示しておくことが重要です。

下位互換性

以下の条件を満たせば、システムには下位互換性があると見なされます:

  • そのシステムの旧ジェネレーションで使われていたインターフェースを処理できる
  • そのシステムの旧ジェネレーションのデータを処理できる
  • サービス・コンシューマがバージョンの変更によって影響を受けない

システムの修正により、下位非互換になる可能性があります。専用の互換性レイヤを使用することにより、システムの下位互換性を回復できます。互換性レイヤの役割は、バージョン変更前のシステムインターフェースのビューを提供することです。こうした状況の下、練りに練ったインターフェース設計がさらに重要になります。

この論文の残りの部分では、バイナリとソースの互換性の区別は重要でありません。[5]ではJava言語向けに、この2つの違いを説明しています。

アップデート

アップデートは、現在インストールされているソフトウェアを改善するために行う行為です。アップデートを通じて、アプリケーションの機能が拡張されることは決してありません。アップデートの目的は、(たとえば、セキュリティホールを閉じることにより)堅牢性をアップさせることです。インターフェースとデータ表現の両方が損なわれなければ、下位互換性に関してアップデートは問題を起こしません。OSGiのバージョニング・スキーム[6]では、バージョンの分類子パターンをmajor.minor.microと定義しています。アップデートを示すのは3番目の桁であるmicro桁の増加のみです。

アップグレード 

すでにインストールされているソフトウェアプロダクトの新バージョンや、そうしたソフトウェアプロダクトへの追加をアップグレードと見なします。アップデートと比べると、アップグレードでは別の機能が提供されます。旧バージョンと新バージョン間にどの程度の差が存在するかにより、互換性の問題が発生する可能性があります。再度OSGiのバージョニングの話になりますが、minorバージョンの番号の増加は下位互換性のあるアップグレードを指します。majorリリース番号の増加は、下位互換性のないアップグレードのことです。

移行

移行とは、ある動作環境から別の動作環境に移動するプロセスです。ソフトウェアシステムでは、同じソフトウェアの上位バージョンへ移動することに相当します(ここでは省略していますが、類似の競合プロダクトへの移動も、移行シナリオと呼べるでしょう)。プロダクトの新バージョンに前バージョンとの下位互換性があるというなら、新バージョンへの切り替え時、消費者はこの移行で追加の措置を何らとる必要はありません。それ以外の場合は、移行では新しい環境に順応するために、それ相応の追加措置が必要です。そのため、下位非互換をもたらす変更は、そのプロダクトの全クライアントに影響を及ぼします。あなたが提供するプロダクトを使う顧客が多ければ多いほど、下位非互換の変更は全関係者の悩みの種となります。依存している顧客の数が増えると、下位互換性のない変更とその後の移行に要する全体費用は、ほとんどの場合、正当化されません。そのため、下位互換性を理由として、ソフトウェアシステムの進化が大いに制限されます。

MDSDは救世主

あなたのプロダクトがすでにモデル駆動型アプローチに依存しているなら、前述のコンフリクト状態の緩和に利用可能な手段が多数あります。目的は、クライアントを満足させ続け、クライアントがシステム変更の影響を受けないようにすることであり、それと同時に、開発中に重荷を感じることなく、なおかつプロダクトを進化させられるようにすることです。

最新アプリケーションのそれぞれにおいて、下位非互換の状況を必ずもたらす潜在的なトラブルメーカーを識別できます。MDSDが配備されれば、こうしたトラブルメーカーに対処する道具を手に入れたことになります。次の3つの例では、今日のソフトウェアアーキテクチャでライフサイクルの問題点が一般に隠れている場所や、モデル駆動型アプローチを使って互換性を維持する方法をお見せします。

APIの非互換性 

公開された各APIには、プログラミングインターフェースの提供者と潜在消費者の間のコントラクトが記載されています。既存のAPIを互換性がない新バージョンで置き換えると、古いAPIを利用しているクライアントを閉め出してしまいます。このことはJavaのような共通言語のプログラミングインターフェースに当てはまるだけでなく、公開されているすべての形式のインターフェース(たとえばWebサービス)に当てはまります。

1

APIの非互換性は、専用の下位互換性レイヤを通じて解決可能です。ソフトウェアシステムで何の設定もしなければ、APIの最新バージョンのみを公開し、旧バージョンはサポートしません。そこで下位互換性レイヤをターゲットシステムの前面にデプロイすれば、サポートする全バージョンのAPIが提供されます。この論文では、このレイヤをミドルウェアのESBの形式でデプロイしようが、ターゲットアプリケーション内にデプロイしようが、それは重要ではありません。下位互換性レイヤはAPIのファサード(facade)的な役割を務め、バージョン間のメッセージ変換[7]を行います。このインフラが整ったら、メッセージ変換向けのルールを定義しなければなりません。

 2

この時点でMDSDが登場します。互換性のないAPI変更のそれぞれについて、対応するメッセージ変換を定義しなければなりません。きわめて重要なプロジェクトでは、この種の変更は例外というより規則に値します。そのため、変換の記述は頻繁に発生するミッションクリティカルな作業になります。変換をひとつでも忘れると、旧バージョンのAPI向けインターフェースが機能しなくなります。モデル駆動型アプローチに従えば、ある種のモデルの形でインターフェースの定義が表現されます。ソフトウェアシステムの各バージョンに対応するモデルのバージョンが存在します。対応する2つのモデルインスタンスを比較することにより、2つのインターフェースバージョン間の変更が容易に回復可能になります。その結果、いわゆるdiffモデルというものが手に入りますが、このdiffモデルには2つのバージョン間でなされたすべての変更が含まれています。diffモデルを入力して、人間が読める形式であらゆる変更を詳述する変更レポートを作成できます。このレポートをチェックリストと考えれば、システムに発生したすべての変更を自分の管理下に入れることができます。そうすれば、変更をひとつ見落として、対応する変換を書き忘れるといったことは、過去の遺物となります。ところが、diffモデルを利用して、さらに先へ進めるのです。一群のインターフェース変更は似通ったパターンをたどります。たとえば、転送データ型への新規の必須フィールドの追加には、常に同種のメッセージ変換がつきものです。ですから、こうした変更向けに変換を記述するのは退屈な反復作業であり、自動化にもってこいです。モデル駆動型のサポートがなければ、開発者は手作業ですべての変換を書かなければなりません。しかし、変更の全情報がすでに利用可能になっているので、識別済みの特定パターンに沿った変更については、メッセージ変換コードをモデル駆動のやり方で作成できます。これにより開発者はエラーを犯しやすい反復的な作業から解放され、時間を節約できるので、その時間をやりがいのある事柄に使えます。確かに、このアプローチには限界があります。変換コードの100%生成を期待するのは非現実的です。識別済みのパターンカテゴリーに分類不可能な、複雑なモデル変更がどんな場合にも存在するでしょう。そのような場合は、手作業で変換を作成しなければなりません。ですから、最終的には、生成した変換と手作業で記述した変換の両方を手にすることになり、生成コードと手作業によるコードを区別するベストプラクティス[2]を念頭に置いておかなければなりません。けれども、下位互換性の維持になぜこれほど注力しなければならないのでしょうか。これはまさしく妥当な疑問であり、すべてのプロダクトそれぞれについて、答えを見つけなければなりません。歴史的な理由や法的な理由により、下位互換性の維持以外の選択肢がないことも多々あります。とりわけeHealth分野にはこの答えが当てはまります。外部の中央集中システムと通信でやりとりする開業医には、いまだ非常に時代遅れの環境で運営しているところが多く、通信に使うインターフェースの安定を当然のことのように期待しているのです。ターゲットインターフェースを変更すれば、開業医それぞれが適応しなければなりません。ターゲット側でこの非互換のコンフリクト解決に時間を割くことは、外部に存在する旧式な異種環境すべてを移行させるよりも、合理的なことが多いのです。

スキーマの逸脱

HibernateなどのO/Rマッパーを使用したデータベース抽象化には、長所と短所の両方があります。一方では、基礎をなしているデータベース技術から、望みの抽象化を開発者に提供します。しかし他方では、バージョン間に存在するスキーマの逸脱が見えないようにしてしまいます。パーシステンスモデルに変更があれば、そのそれぞれに関して、デプロイ済みのアプリケーション向けにデータベーススキーマをやり直す必要があります。たとえば、ドメインクラスに新規の持続フィールドを加えれば、対応するデータベーステーブルにも列を追加する必要があります。パーシステンスモデルとデータベーススキーマに違いが生じ、同期が図られていないことを、スキーマの逸脱と呼びます。

3

ここでも、MDSDが救いの手を差し伸べます。パーシステンスモデルを一級のアーチファクトとして取り扱い、そこから基礎をなしているデータベーススキーマを作成すれば、互換性を目的にモデル情報を再利用できるでしょう。異なるバージョンについてそうしたモデルを手にしていれば、バージョン間の変更に関して有用な情報を提供してくれるdiffモデルを、ここでも作成できます。これまでにように、実施されたすべての変更を詳述する変更レポートを、前回同様に生成できます。さらに、そのdiffモデルに基づいてSQLアップグレードスクリプトを自動的に作成できます。ここでも、スクリプトの100%生成は意図していませんし、現実的でもありません。生成されたスクリプトを、スキーマのより複雑な逸脱をカバーするために手作業で記述したスクリプトで、常に補完する必要があります。生成されたアーチファクトと手作業で記述したアーチファクトをバージョン間のインターバル内で組み合わせることにより、次バージョンにアップグレード予定のデータベーススキーマのマイグレーション・パス(移行の道筋)が定義されます。SQLには種々の方言が存在するので、サポートされているすべてのデータベースについてアップグレードスクリプトを提供することが肝要です。複数のデータベースがサポートされているなら、異なる方言に応じたスクリプトにする必要があります。Rubyの移行[8]では、スキーマの変更を記述するDSLをRubyコミュニティが提供していますが、このDSLはデータベースに依存しません。スキーマ変更の記述は次に、様々な方言向けにSQLステートメントに翻訳されます。SQLを直接作成するのではなく、モデル駆動型アプローチではdiffモデルをRubyの移行表現に変換することができます。そうすることにより、SQLステートメントを作成する責務をRuby移行側に移すことになります。MDSDが提供するサポートを利用することにより、ドメインモデルに手を出して変更することにまつわる不安は、消えてなくなります。開発者はドメインモデルの進化をためらわなくなり、開発計画にはアジリティが戻ってきます。

言語の変更

モデル駆動型のテクニックを使う際、ドメイン固有言語(DSL)を使ってモデルを表現します。そうしたDSLは、なくてはならないドメイン固有の表現と抽象概念を提供してくれるので、ターゲットドメインの記述には最適です。汎用言語(GPL)と比較すると、DSLはGPLほど完成度が高くないので、変更を免れません。基礎となるドメインとメタモデルが進化し、追加のコンセプトやコンストラクトがDSLに加えられたり、古いものを置き換えたりすると、言語自体も進化します。残念ながら、ドメイン固有言語そのものが、非互換という病に免疫を持っているわけではありません。

4

しかし、潜在的なコンフリクトが持ち上がるたびに、対処する方法と手段があります。今回は、ドメイン駆動設計で使われているAnticorruption Layer(破損防止レイヤ)パターン [9]を利用します。その名のとおり、このパターンは破損を解消すると謳っています。言語変更のシナリオでは、言語のメタモデルを変えた瞬間、破損が起こります。旧メタモデルを満たしていたモデルはすべて新メタモデルのバージョンに沿っていないため、そうしたモデルはすべて「破損している」と見なされます。ですから、レガシーのメタモデルはもうサポートされておらず、クライアントは新しいメタモデルに基づいた言語に移行せざるを得なくなります。専用のAnticorruption Layerがあれば、古いメタモデルを維持するとともに、より高度なメタモデルを目指して進化を促進できます。Anticorruption Layerは、ファサード(facade)、アダプタ、トランスレータを組み合わせたもので、異なるモデル表現間の翻訳を担当します。クライアントから見えるメタモデルのバージョンの他に、内部メタモデルも維持しています。この内部メタモデルは、さらなる処理に使われる最も重要なメタモデルです。外部モデルインスタンスはすべて、内部メタモデルに準拠したモデルインスタンスへと翻訳されます。ですから、外部メタモデルがターゲットドメインのある種のビューを提供することになります。Anticorruption Layerのおかげで、そうした複数のビューをユビキタスなメタモデルにマッピングする道具が手に入ります。Anticorruption Layer内に存在するバージョンの翻訳が明確に隔離されているとなれば、メタモデルの非推奨戦略をコントロールしながら容易に実施できます。

Anticorruption Layerのメリットを示す簡単な例:全世界に所有する高級車用の車庫の収容能力を整理する目的で、独自のDSLを使っていると仮定します。バージョン1では、各自動車の幅、高さ、長さを指定します。数ヵ月後、車庫にはS、M、Lの3種類の駐車場サイズしかないことがわかります。ですから、新バージョンでは寸法の代わりに、サイズを保存します。それぞれの車庫向けに直ちに新バージョンを導入するのではなく、Anticorruption Layerを実装します。このレイヤは、寸法を使った当初のメタモデルを理解しますが、内部では駐車場サイズを使ってそのメタモデルを翻訳します。

経験則

例で見たように、直面する非互換性は多種多様です。論文の最後は、MDSDと組み合わせた互換性を扱う際に念頭に置くべき重要なルールで締めくくります:

下位互換はユーザーフレンドリー

高性能で人気の高いアプリケーションは顧客数の増加を約束します。顧客が多くなるほど、つきつけられる必要条件も多くなります。ですから、よく売れているアプリケーションではさらに開発が続き、次バージョンが出る傾向にあります。これまで、非互換性との奮闘に失望した顧客の例を十分見てきました。非常に高いレベルの下位互換性があれば顧客は満足し、あなたが提供するソリューションから離れなくなります。「顧客が満足している」という要因は、システムを長続きさせる上で重要です。

下位互換性は高くつく

互換性はタダでは手に入りません。ライフサイクルの問題点は、アーキテクトなら気づかずにやり過ごしたい、眠れる巨人であることが多いのです。 気づいた瞬間―その瞬間は開発の非常に遅い段階にやってくるのが一般的ですが―こうした眠れる巨人を管理下に置くにはとてつもない労力が必要なことが多いのです。モデル駆動型のテクニックを使えば、互換性の問題点をのっけから評価し統合できます。そうすれば、よく売れているプロダクトの進化が互換性問題によって制限されることはなくなります。インクリメンタルな移行を利用することにより、経費を低く抑え、複雑性を扱いやすいレベルにとどめます―サポートしている全バージョンの間で移行できる道を提供するのではなく、直前バージョンから次バージョンへの直線的な移行(リニアな移行)のみを提供します。こうした連鎖的な移行の形をとることにより、同じ目標を達成できます。

非互換に免疫のある者など存在しない

互換性は産業界のほとんどすべてのプロダクトで考慮しなければなりません。組織内のプロダクトに限り、使用法と互換性に関するクレームを規制してもいいかもしれません。幅広い利用者向けのその他すべてのプロダクトは、行き止まりを避けて進まなければなりません。プロダクトが望みの方向で日の目を見るようにするには、下位互換性を維持して適応できることと、必要不可欠であることが必要です。それができて初めて、プロダクトは生き残る可能性をもつことができるのであり、長寿プロダクトになることができるのです。ですから、決して互換性をあとからの思いつきとして扱わないでください。この点は、特に公開で開発されているプロダクトに当てはまります。実際、オープンソースの開発者には、自分のプロダクトがどのように、いつ適用されるのかが、決してわからないのですから。

リリースされた途端、もうレガシー

リリースのバージョン番号は、システムの存続期間中のある特定のスナップショットを表しているにすぎません。あるバージョンがリリースされた途端、開発はすでにその先へ進んでいます。ですからリリースは、過去の開発状態を表すものです。そのリリースはすでに出荷済みですから、リリースされたバージョンの顧客を満足させ続けるには、そのリリースをサポートしなければなりません。

モデルの差分もまたモデル

最初の2つの例では、モデルの差分にも、さらなる処理を進めるために利用可能な貴重な情報があることを強調しました。既存のモデルバージョンからdiffモデルを入手するとしても、モデル駆動型アプローチがすでに適用されていれば、ほんの少しの労力ですみます。既存のツールチェーンに追加のステップを設けて拡張し、diffモデルの作成と処理を行う必要があります。既存のモデルバージョンの維持には、環境(すなわち、バージョン・コントロールシステム)の利用もあり得ます。

ベストプラクティスとしてのAnticorruption Layer(破損防止レイヤ)

Anticorruption Layerがあれば、互換性を維持しつつ、柔軟であり続けるための、強力なパターンを持っていることになります。Anticorruption Layerにより、内と外の表現をはっきりと隔離できると同時に、両者間に強い結びつきを保持できるのです。既存のツールチェーンへAnticorruption Layerを導入するにしても、その侵襲性は最小限ですし、事後に導入を実施できます。いったん統合されれば、両エンドの独立した進化が可能になります。

有用なフレームワークおよびツール

JavaのMDSDフレームワークに目を向けると、「MDSD/MDAツールを構築するためのツール」を謳っているopenArchitectureWare(oAW)[10]が主流です。Eclipse Modeling Project(EMP)のサブプロジェクトであり、モデル駆動型の開発に堅牢なソリューションを提供します。EMPでは、Eclipse Modeling Framework(EMF)[11]が主なビルディングブロックであり、構造化データモデルを中心として高度なサポートを提供します。oAWはEMF技術を大いに利用するので、両方を組み合わせて使うことをお勧めします。EMF Compare [12]はEMFモデルを比較するための実装で、「価値あるdiffモデルを作成する」という必要性に最適と思われます。

著者について

Andreas Kaltenbach氏はInterComponentWare AG(ICW)[13]のソフトウェア開発者兼トレーナーです。ICWはドイツのヴァルドルフに本拠を置くヘルスケアアプリケーションの専門企業です。Kaltenbach氏は、ICWのeHealth Framework(eHF)と、このフレームワークを越えた環境におけるMDSDとセキュリティを中心に仕事をしています。氏はまた、ICWを代表する開発者のひとりとしてOpen eHealth Foundation [14]で活動していますが、このFoundation(財団)の主な目的はヘルスケア産業にオープンソースのコミュニティを確立することです。

リンクおよび文献 

[1] Völter, Stahl: Model-Driven Software Development (2006)

[2] Efftinge, Friese, Köhnlein: Best Practices for Model-Driven Software Development (2008) http://www.infoq.com/jp/news/2008/09/model-driven-dev-best-practices

[3] Cloneable doesn't define .clone, http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4098033

[4] Bloch: Effective Java: A Programming Language Guide (2008), item 11

[5] Gosling et al.: The Java Language Specification, Third Edition (2005), chapter 13

[6] OSGi Alliance: OSGi Service Platform Core Specification (2007), chapter 3.6.2

[7] Hohpe: Enterprise Integration Patterns (2003), chapter 8

[8] Understanding Migrations, http://wiki.rubyonrails.org/rails/pages/understandingmigrations

[9] Eric Evans: Domain-Driven Design: Tackling Complexity in the Heart of Software (2003), chapter 14

[10] openArchitectureWare, http://openarchitectureware.com/

[11] Eclipse Modeling Framework, http://www.eclipse.org/modeling/emf/

[12] EMF Compare, http://wiki.eclipse.org/index.php/EMF_Compare

[13] InterComponentWare AG, http://www.icw-global.com/

[14] Open eHealth Foundation, http://www.openehealth.org

この記事に星をつける

おすすめ度
スタイル

BT