モデル駆動開発の考え方
前節の例ではC言語のソースコードの生成にBridgePointを使いました。ですが、モデル駆動開発は、開発工程の中で「モデル変換」を使う開発方法であることが重要です。コード生成はモデル変換のひとつと考えておくといいでしょう。
モデル駆動開発はモデル変換による開発
モデル駆動開発は、分析・設計情報をモデルとして表現し、モデル変換の繰り返しによって開発を進める開発手法です。この方法を使うことでシステムの可搬性・再利用性を高め、開発成果の相互運用性を向上するのが目標です。
モデル変換は、入力のモデルに付加情報を加えて、異なる視点のモデルやコードを得る方法です。変換というと、あらかじめ決められたことしかできず、自分たちの知識や経験を反映できない方法というイメージがあるかもしれません。実は、モデル変換は、そこを表出化する方法だと捉えた方がいいかもしれません。図7は、モデル変換が知識や手順をデータ化する様子を説明した図です。
図の上側は一般的な開発の進め方です。この方法だと、設計と実装の担当者は、前工程の仕様書を受け取り、前工程の複数の仕様書の情報をひも付け、そこに担当者の知識を追加しています。下側がモデル変換よる進め方です。担当者の作業や知識を「変換ルール」として取り出し、これを前工程の仕様に適用して後工程の仕様やコードを得ています。このように、属人的作業や作業者の暗黙的な知識を「変換ルール」としてデータ化し、変換ルールを使って次の成果を得るのが、モデル変換を使った開発なのです。
モデル変換の連鎖
モデル駆動開発は、モデル駆動アーキテクチャ(MDA:Model-Driven Architecture)(注:MDAはOMGの商標になっています)が提唱する四層モデルに従っています。図8にMDAの四層モデルを示します。
計算非依存モデル(CIM:Computation Independent Model)には、システムと外部とのやり取りなど、対象システムの要求を記述します。
プラットフォーム非依存モデル(PIM:Platform Independent Model)には、システムの内部構造、振る舞いなど、対象システムの設計情報を記述します。
プラットフォーム依存モデル(PSM:Platform Specific Model)には、プラットフォームに適用するための詳細を付与した設計情報を記述します。例えば、プラットフォームにあたるのがOSならOSの呼び出しに依存するモデルになり、フレームワーク上のアプリケーションなら、フレームワークが提供するサービスやAPIに依存するモデルになります。
一見、この四層モデルは、4段階のプロセスを持つウォーターフォールモデルを表しているように見えるかもしれません。そうではなく、四層モデルはモデル変換の前後関係を表していることに注意しましょう。
図9は、モデル変換を繰り返す前後でモデルの位置付けが変わることを表しています。
モデル変換では変換の際に、新しい情報を付加したり複数の変換前のモデルやデータをひも付けたりします。変換前のモデルは、付加される情報等に依存していないので、プラットフォーム非依存モデル(PIM)であるとみなせます。そして変換後のモデルは、付加される情報等に依存しているので、プラットフォーム依存モデル(PSM)になります。ところが、この段階の変換後のモデルは、次の段階の変換で付加される情報等には依存していません。つまり、前の変換後のモデルは、次の変換にとっては変換前のモデルになっているわけです。
変換の繰り返しとモデルの関係を具体的に考えてみてみましょう。図10は、販売管理アプリケーションをモデル変換で開発している様子を表しています。
変換aでは、[1]販売管理アプリケーションのモデルにWeb3層モデルのための情報を付加し、[2]Webベース販売管理アプリモデルを得ています。この変換aから見ると、[1]がPIM、[2]がPSMです。次の変換b1では、J2EEに依存する情報を付加して[3]J2EE対応販売管理アプリモデルを、変換b2では、Railsに依存する情報を付加して[4]Rails対応販売管理アプリモデルを得ています。この変換b1、b2から見ると、[2]がPIM、[3]と[4]がPSMとなるのです。
モデル変換方式
PIMからPSMを得るモデル変換には「モデルタイプマッピング」と「モデルインスタンスマッピング」の2つがあります。
モデルタイプマッピング方式は、PIMの中で使われている型から、PSMの中で使う型への変換です。図11に示すように、PIM側のモデル要素の型情報を型変換規則によってPSM側の型に変換します。
モデルインスタンスマッピング方式は、PIMの中の特定の要素からPSMの中の特定要素への変換です。図12に示すように、PIM側のモデルの中で特別扱いしたい要素(特定のクラスやインスタンス)があるとき、その要素にマークを付けておきます。モデル変換の際に特別扱いしたい要素が見つかると、付けられていたマークに対応した固有の変換規則を使って変換します。
大抵のシステムはタイプマッピングによる型変換だけでは対応できないので、システム全体にはタイプマッピングで定めた規則を適用し、特定の要素についてはインスタンスマッピングを使うといったように、2つの方式を併用しています。
モデル変換と手作業の関係
では、2つの変換方式が、皆さんがよくやっているプログラミングの作業のどこに対応しているのか考えてみましょう。
まず、クラス図を見ながらC言語のソースコードを書くときを考えてみましょう。実装方式はいろいろありますが、ここでは、クラスの名前を構造体の名前にし、属性をメンバ変数に対応させ、メソッドを構造体型のポインタを引数に持つ操作関数群に対応する方式を想定します。
例えば、FILEクラスがあって、これをFILE型構造体に対応付けると考えます。すると、図13のような対応付けができるのがわかるでしょう。この図は、皆さんがよく知っている標準入出力関数をタイプマッピングで説明したものといえます。
他に、インスタンスを構造体型の変数の配列やクラスライブラリのコレクションとして実装することもあると思います。これらも、タイプマッピングを手動でやっているとみなせるでしょう。
振る舞いのコードを書く場合も同じように考えることができます。皆さんは、振る舞いのコードをステートマシン図を見ながら実装するでしょう。例えば、状態遷移表を配列で作る、switch-case文を使う、あるいはステートパターンに従ったクラス群を書くといった方法から選んで実装するのではないでしょうか。これらも、モデルとコードの対応関係が決まれば、タイプマッピングによる変換が可能です。
実は、BridgePointがクラス図とステートマシン図からC言語のソースコードを生成できるのは、特別な方法があるからではなかったのです。上の例などを見ればわかるように、むしろ、皆さんが実装の際にやっている作業をそのまま定型化したものなのです。
さて、皆さんが手で実装するときには、次のケースもあるでしょう。
-
対象システムの外のクラスや外部の既存コードを利用しているときは、実装しないで参照を宣言する
- OSのAPIやドライバの呼び出しに変換するところ
- 他のサブシステムとのやり取り用I/Fを呼び出すところ
-
システムのセットアップ
- 開始時のインスタンスやインスタンス間の関連(ポインタの代入等)を書く
- ROM化したいインスタンスに対して特定のアドレスを割り当てるなど、通常とは別の処理を書く
-
異なるコレクションタイプの選択
- タイプマッピングの規則で、インスタンスは単方向リストに格納するようにしたが、特定のクラスのインスタンスは配列に格納したい
こういった場合、皆さんは図13のような構造体への対応付けはせずに、別の書き方で実装しているでしょう。
モデル変換においても、タイプマッピングでは特定のクラスだけを特別扱いできないので、皆さんが特別扱いするような実装に対応できません。そこで、特別に扱いたいクラスや状況があれば、それらに応じたマークを用意します。
ここでは、BridgePointが提供しているマークをいくつか紹介しましょう。
-
初期化に使う関数名を指定する
- MarkInitializationFunction("*", "setup")
-
外部から呼び出せる関数を生成する
- TagSyncServiceSafeForInterrupts("ILB", "kick_start")
- 外部エンティティへの参照(生成しないで参照するもの)を指定する
-
指示したヘッダファイルの関数を呼び出すようにコードを生成する
- WireRealizedExternalEntity("ODMS", "PIO", "PIO", "PIO", "PIO_bridge.h")
MarkInitializationFunctionの引数に出てくる「*」は、全てのサブシステムにおいて、の意味だと思ってください。
また、次のサービスルーチン、ヘルパールーチンの挿入もマークによって実現しています(ここでの「*」は、全部のクラスが対象の意味です)。
-
状態遷移やアクション言語のトレースコードを挿入する
- MarkStateTransitionTracingOn("*")
- MarkActionStatementTracingOn("*")
-
インスタンスハンドルが実体を指しているかチェックするコードを挿入する
- MarkEmptyHandleDetectionOn("*")
インスタンスマッピングの具体的な例として、特定のクラスについてインスタンスの格納に別のデータ構造を指定した場合を、図14に示します。
この例では、タイプマッピングの変換規則は、クラスが見つかったらそのクラスのインスタンスを格納するデータ構造に配列を使用する、と決まっています。ところがPointクラスだけは、事情があってLinkedListクラスを使うことになっていました。この場合タイプマッピングでは対応できません。そこで、Pointクラスの実装に使うコレクションにはLinkedListを使うことをマークしておきます。そうすれば、変換時に配列ではなくLinkedListを使うことができます。
このように、インスタンスマッピングも皆さんが手で実装している作業を定型化したものであることがおわかりいただけたかと思います。
まとめ
モデル駆動開発がモデル変換の繰り返しによる開発方法であることを紹介しました。
また、モデルタイプマッピング、モデルインスタンスマッピングといったモデル変換方式は特別なものでなく、皆さんが手で実装する際にやっている作業と変わらないことも、おわかりいただけたのではないでしょうか。
モデル駆動開発は難しいものでも、変わった方法でもありません。そして、現在は高価なものでもなくなりました。オープンソースになったことで、生成するコードへの責任も自社で負うことが容易になりました。ぜひ一度、小さな事例からモデル駆動開発を使った開発を試してみてはいかがでしょうか。
トップエスイーについて
「トップエスイー」は、国立情報学研究所で提供している、社会人エンジニア向けのソフトウェア工学に関する教育プログラムです。トップエスイーでは講義や制作課題を通して、最先端の研究成果や現場で得られた知見が蓄積されてきました。その「アウトカム」、つまり成果やそこに至る過程を紹介し、現場のエンジニアの方々に活用していただける記事を連載しています。トップエスイーでは、アーキテクチャに関わる講義が充実しています。コンポーネントベースの開発手法、ソフトウェアパターン、アスペクト指向といった中心的な話題を扱う講義に加えて、今回紹介したモデル駆動開発やオブジェクト指向設計、ソフトウェアの再利用、そして今回紹介したモデル駆動開発の講義も開講され、開発現場で活かせるシステムのアーキテクチャ設計の学習をサポートします。
