Text
DCI Tokyo 2 - Commonality / Variability Analysis: Practical MPD - が開催されました
2018年1月10日に開催された DCI Tokyo 1 に続き、2018年3月27日に DCI Tokyo 2 が開催されました。今回も James Coplien @jcoplien さんをお招きしてのトークセッションとなりました。会場は 株式会社ヴァル研究所 様に提供していただきました。
セッションは、前回同様 @remore さんと @ganchiku さんによる同時通訳とともに進められました。
今回のテーマはマルチパラダイムデザイン(Multi-Paradigm Design: MPD)の中核を成し、DCI / リーンアーキテクチャ(Lean Architecture)とも深く関係する 共通性/可変性分析 でした。
レポートは @smori1983 が担当させていただきます。
当日の様子は Coplien さんの許可を得て YouTube の DCI Tokyo 公式アカウントにて公開されています。Coplien さんのおもしろTシャツにも注目しながら、ぜひご覧ください。
DCI Tokyo 2- Commonality / Variability Analysis: Practical MPD by James Coplien (Part 1 of 4)
DCI Tokyo 2- Commonality / Variability Analysis: Practical MPD by James Coplien (Part 2 of 4)
DCI Tokyo 2- Commonality / Variability Analysis: Practical MPD by James Coplien (Part 3 of 4)
DCI Tokyo 2- Commonality / Variability Analysis: Practical MPD by James Coplien (Part 4 of 4)
DCI Tokyo 2- Commonality / Variability Analysis: Practical MPD by James Coplien (extra)
全体の流れ
全体の流れとしては、1980年代以降のソフトウェア開発の歴史を、主にオブジェクト指向に対する考え方の変遷を通して振り返りつつ、MPD と DCI の関係性に迫るものとなりました。
オブジェクト指向(1988)
当時は C++ の他に、テレコム業界向け言語である CHILL や、軍需産業向け言語である Ada などの言語があり、それらの言語がオブジェクト指向なのかという議論がされていました。
最終的に C++ が残ることになりますが、C++ の作者である Stroustrup 氏は、そもそも C++ をオブジェクト指向言語ではなく、マルチパラダイム言語と定義していたことに注意する必要があります。
当時考えられていたオブジェクト指向の構成要素は次の3項目でした。
inheritance / 継承
polymorphism / ポリモーフィズム
instantiation / インスタンス化
1994年に Coplien さんは David Weiss 氏と Robert Chi Tao Lai 氏とともに仕事をしますが、その時、共通性/可変性分析を通じて、ソフトウェアファミリの振る舞いなどを特徴づけ、当時取り組んでいたソフトウェアのための DSL(Domain Specific Language) を開発しました。開発言語は C でした。
この、共通性に注目してグルーピングしたものをファミリと定義する方法は、Weiss 氏らが考案したFAST(Family Abstraction Specification Technique) という手法に基づいています。
DSL は小さなドメインを対象にしていて、ドメインが変化しない限りはうまくいったそうですが、ドメインは変化していくものです。DSL は、デバッガなど周辺技術との密な関係性の中で成立するため、その変化に対しては脆いという問題がありました。
ただし、共通性/可変性分析自体は非常に有用で、ドメイン分析に対しては非常に多くの収穫があったそうです。
分析の結果得られる共通性/可変性テーブルの内容の埋め込み(注: 解決ドメインの抽象による実装)に関して、
DSL で埋め込むと、固くなりすぎ、脆くなる。
C で埋め込むと、言語特有の制約が多く、開発者の intentionality(意図)がわからなくなる。
という課題がありましたが、C++ を用いることで、よりうまくいくようになりました。
オブジェクト指向(1995)
Coplien さんは、C++ を
共通性/可変性分析のための、汎用言語キットである
と捉えます。
その視点で見たオブジェクト指向は、次の4項目の構成要素で理解されるようになりました。
Common Signature
Common Data
Variation in Data
Variation in Algorithm
また、polymorphism / instantiation は、共通性/可変性のバインディングタイム(注: 共通性/可変性が変数に束縛されるタイミング)の選択の問題として捉え直されることになりました。
オブジェクト指向(DCI 時代)
Coplien さんによれば、更にこの後、3段階の気付きを経て、現在のオブジェクト指向理解へとつながっているそうです。
1段階目: There is no OO in C++
2010年くらいまでは、オブジェクト指向という言葉を、抽象データ型( Abstract Data Type: ADT )という意味で使っていた。
C++ は実際には抽象データ型ではなく、具象データ型( Concrete Data Type: CDT )であるということ。
2段階目: There are no objects in source code
C++ プログラムはソースコードである。
そこにはオブジェクトは存在しない。ソースコードに記述しているのはクラスである。
多くの人々が Java などの言語でやっているのはクラス指向プログラミングである。
3段階目: OO is mental model
Trygve Reenskaug 氏とオブジェクトとは何か、オブジェクトパラダイムとは何かについて話し合い、DCI が生まれた。
DCI は、抽象データ型の実装を意味したこれまでのオブジェクト指向とは別の視点を与える。
実行時におけるオブジェクトのネットワークがどのように問題を解決するかに注目する( Alan Kay の定義)。
我々はクラスとロールをごちゃまぜにして考えていた。
抽象データ型について考えるのがクラス指向。
ロールについて考えるのがオブジェクト指向。
抽象データ型+インスタンス化がロールを実行(play)。
MPD と DCI
MPD は、クラス指向のレイヤーにおいてドメイン分析を行う時、ソフトウェアファミリの共通性/可変性分析を通じてドメインモデルを構築する手法を提供します。
DCI は、我々のメンタルモデルに基づいて、オブジェクトどうしの相互作用を定義します。そこでは、抽象データ型としてのクラスに具体的なロールを注入されたオブジェクトが、特定のコンテキストのもとに配置され、相互作用するという、動的な側面を捉える方法を提供します。
以上を簡単にまとめると、次の表のようになるかと思います。
パラダイム 設計対象 領域 手段 視点 DCI オブジェクトとそれらの相互作用 ロール / コンテキスト ユースケース Time MPD 抽象データ型、モデルの構造 クラス / Abstract Base Class (ABC) 共通性/可変性分析 Space
歴史的には MPD が先行しました。それは後に、モデルの静的な構造の分析を行うための領域を担う設計方法論という捉え直しがされました。そして、オブジェクト指向に対する新しい視点を提供する DCI が、クラス指向では扱うことが難しかった、モデルの動的な関係性を捉えるための方法論として提起されています。
Time and Space と DCI における共通性/可変性
DCI は、オブジェクトどうしの相互作用に注目します。つまり基本的には時間の経過(シーケンス)が意識されます。しかし、あるロールを付与された複数のオブジェクトが配置されてこそ、相互作用が成立するものです。
コンテキストとは、このように、ある状況に参加する主体の空間的配置と時間的な相互作用の総体として捉えられるものと言えます。
ここで、日本人にとってはありふれた「間」という概念が取り上げられました。具体例としては次のようなものが話題にあがりました。
歌舞伎( Coplien さん)
龍安寺の石庭( @koriym さん)
また、アレグザンダーの考案したパターンランゲージに話が及びました。パターンランゲージは、建築の構造(空間的なパターン)だけではなく、その構造が生み出す人々の相互作用(時間的なパターン)も内包したものとして理解されるべきです。
pattern of behavior = pattern in space + pattern in time
セッションの終盤では、このように、DCI が担う領域における共通性/可変性概念の適用可能性が示唆されました。
さいごに
会場を提供していただきましたヴァル研究所様、DCI Tokyo スタッフの皆様、今回もすばらしいイベントとなりました。ありがとうございました。
次回は、ワークショップ形式での開催も視野に入れながら、DCI Tokyo 3 が開催されることと思います。MPD / DCI / Lean Architecture に興味のある方は、ぜひご参加ください。
1 note
·
View note
Text
DCI Tokyo 1 - Lean Architecture by James Coplien - が開催されました(後編)
後半のセッションでは、「What the system is(共通性や可変性を分析する)」を説明しつつも、「What the system does」とは別のものとして切り分けることはできないという主張から、 DDD とリーンアーキテクチャとの比較、そしてパターンに関する議論が展開されました。 @remore が前半部分をまとめてくださっていまして、そこで出て来るいくつかの用語(Form, Structure, What the system is, What the system does)を前提として私のできる範囲で行いました。
なお当日のCoplien氏によるセッション内容は、許可を得た上でYouTubeにアップロードされていますので、より深くご覧になりたい方はこちらも併せてご参照下さい。
DCI Tokyo 1 - Lean Architecture by James Coplien (Part 1 of 2)
DCI Tokyo 1 - Lean Architecture by James Coplien (Part 2 of 2)
What system is に関して
Jim Coplien さんの言うアーキテクチャ
共通性、可変性分析に関する What system is に関連している。
ほとんどのドメイン知識は、アルゴリズムで表される What system does よりも What system is の方にある。
ソフトウェアを開発する際に重要なもの
プログラム言語自体の知識ではなく、ドメインでありビジネスである。
ほとんどのドメイン知識は、構造(Structural)であって、アルゴリズムではない。
このミートアップでは、構造(Structure)の方を主に扱う。What system is の方である。
実際のシステムを構築する際には、構造は安定されていることが多いので先に行うことが多く、それらが出来上がってから、まだ実装されていない機能を作り始める。
その機能が、人々が求めているユースケースになり、これがビジネスとなる。
What system is と What system does の関係性
しかし、What system is と What system does は別々に考えられるものではない。それは、構造と時間やアルゴリズムを全く別のものとして、分けることは不可能だからである。
リーンアーキテクチャ本では、 What system is も What system does も説明しており、夫婦で共著で書いている。 リーンアーキテクチャ本では分けることができると書いたが、実際は簡単に構造とユースケースを分けることができるわけではない。
それは、歴史が物語っている。
1960年から1970年では、ノイマンコンピュータでは、データ構造は全く注目せずに、 What system does に注力され、 CPU が大事でこの性能を良くするということが重要だった。
1970年後半にデータベースが登場し、ボトルネックはデータへのアクセス速度が重要となった。CPU のことは忘れ去れられ、What system is が注意の関心の的になり、データモデルが重要になった。
1980年にコンピューターネットワーキングが登場し、オブジェクト同士のコミュニケーションが重要になった。こちらは What system does がまた注目された。
このように、歴史を見ると、 What system is と What system does のサイクルができている。
アーキテクチャ全般に言えることだが、例えば部屋の構造では、どこにライトがあって、どこドアがあって、それが何か(What system is)を説明することができるが、どうしてそこにある(What system does)のか、とか、部屋の中をどうやって歩くかなどのプロセスにも影響している。つまり、これらは簡単に分けることはできずに互いに関係しあっている。
リーンアーキテクチャと DDD の関係性
DDD
分析に関しては扱っていない。
コードに関することを主に扱っている。
形態(Form)の社会性は扱わない。
構造(Structure)のみに注目しており、具体的な事柄から始める。
リーンアーキテクチャ
人々について、そして人々のメンタルモデルを扱う。モデルに関する議論をすることを扱っているので、社会的な活動である。
形態(Form)を扱う。
形態(Form)からスタートして、抽象的な Commonality から Variation を適用させて正しい実装をしていくことである。
アレグザンダーは、アーキテクチャは、社会的な活動であると明記している。
対称性とパターンに関して
アレグザンダーは、空間からイベントのパターンを作成している。ソフトウェアはまだ歴史が浅いため、パターンはほぼ存在しない。建築は世紀を超えてパターンを作ってきた。ソフトウェアの世界で言われているパターンは、コンテキストの上でのロジックであって、それはパターンではない。唯一、例として上げてもらったパターンは、リーキーバケットに関するものであり、イベントのパターンを説明している。
リーキーバケット
共通性と可変性は対称性を持っているが、あるとき対称性が壊れることがある。プログラム言語では対称性を表そうとするが、実際の要件では、対称性のない複雑なものを作り上げる必要がある。リーンアーキテクチャは対称性を扱っており、負の可変性(Negative Variation)は扱っていない。ここが複雑な場所である。パターンは、What system is と what system does の両方を考える必要があり、また、空間と時間を考える必要がある。
パターンに関する議論に関しては、私(ganchiku)の中で消化しきれていない場所があり、まとめることができませんでしたので、今後の課題としてアレグザンダーや Jim Coplien さんの議論を追いかけて、自分の言葉で説明できるようになっていこうと思います。
会場を提供していただいた UUUM 株式会社、また、オーガナイズしていただきました DCI Tokyo メンバーの皆様ありがとうございました。
1 note
·
View note
Text
DCI Tokyo 1 - Lean Architecture by James Coplien - が開催されました(前編)
1月10日に六本木ヒルズにて、James Coplien氏をお招きしてLean Architectureに関する勉強会を開催しました。UUUMさんに大変素敵な会場を提供頂き、スタッフ含めて40名前後の参加者が集まりました。
このブログでは、当日の翻訳担当を務めた@remoreと@ganchikuが、当日の翻訳内容から抜け落ちた部分の捕捉を含めて、内容のアウトラインを簡単に振り返れればと思います。セッションの前半部分は@remoreが、後半は@ganchikuが解説を担当します。
なお当日のCoplien氏によるセッション内容は、許可を得た上でYouTubeにアップロードされていますので、より深くご覧になりたい方はこちらも併せてご参照下さい。
DCI Tokyo 1 - Lean Architecture by James Coplien (Part 1 of 2)
DCI Tokyo 1 - Lean Architecture by James Coplien (Part 2 of 2)
Form(形態)とLean Architecture
形態は機能に従う(Form Follows Function)という言葉があります。Googleで検索してみると、元々はアメリカの建築家ルイス・サリヴァンの言葉であると出てきます1。この言葉は建築やプロダクトデザインなど様々な領域で引用されている言葉ですが、ソフトウェア・エンジニアリングの世界でもしばしば引用されているようです。今回のセッション前半では、前回の内容を簡単におさらいしつつ、この「形態」に焦点を当てた内容が主に展開されました。
前半部分のセッションの内容について、独断と偏見で要点を書き出してみました。なるべく短く抜き出してみたつもりなのですが、各トピックの内容が濃いせいか、結果的にそれなりの長さになってしまいました:
1. Form(形態)とStructure(構造)の違い
Formは抽象的であり、Structureは具体的である
複数のFormをよく見ていくと、パターン認識によって共通性と可変性という特徴を抽出することができる
ArchitectureはFormに関すること
Formは対称性を強調する
人間の脳は共通性を見つけることに秀でている
2. 現代のプログラミング言語とOOPについて
共通性と可変性のペアをパラダイムと呼ぶ
プログラミング言語はこれらのパラダイムのうちいくつか限られた数だけを言語の設計思想に取り入れているに過ぎない
例えばOverloadingといったOOPのプログラミングテクニックはパラダイムの一つと言えるし、その他の言語が持つ機構や機能(例えばC++の場合#ifdef, Template、class, 継承といった言語が提供する機能)は共通性と可変性のペアをForm(形態)において表現しているだけとも言える
これを実際に実装していくとStructure(構造)の話となるが、設計において我々がどうそれを捉えているかというと、あくまでFormの形で捉えている
C++を作ったStroustrup氏は、C++をオブジェクト指向言語とは呼ばずマルチパラダイム言語だと呼んでいる
オブジェクト指向は一つのパラダイムに過ぎないのに、「オブジェクト指向分析」で全ての問題を解決しようと考えだすからおかしくなる
C++はオブジェクト指向以上のことが実現できる言語
3. マルチパラダイムデザインがどこで始まり、Lean Architectureがどこからきたのか
Lean Architectureでは2種類の視点でFormを捉えている
What the system is(共通性や可変性を分析する)
What the system does(振る舞いや"間"で見る)
建築家は「形態は機能に従うか?(Does Form Follows Function?)」という点に関して各々の主張を持っている
建築家アドルフ・ルースは「形態は機能に従う」と唱えた
ソフトウェアアーキテクトもこれを唱えがちだし、ユースケースを分析すれば最適なアーキテクチャも見つけられると考えがちであるが、これは完全に間違いである
銀行口座送金の例を見ると、振る舞いを表現するために適切なものはオブジェクトIDでもクラス名でもなく、Roleであることが分かる
機能の形態(Form of Function)はRoleの中にある
Roleという概念が一級市民として存在しており、オブジェクトがRoleを動的に演じることができるようなプログラミング言語を想像してみよう。機能の形態をコードで表現することができ、コードを読むだけでユースケースを理解できる。これがDCI。
ここでいう"機能の形態"は、オブジェクト間のやり取りや振る舞いといった"What the system does"の部分にあたるもののこと
前半サマリは以上となります。なお、動画の54:43頃から始まる対称性や幾何学の話を起点に展開される、現代のシステム開発におけるオブジェクト指向言語のあり方と利用のされ方に対する強い批判は、要約ではとても表現しきれる内容ではなくかつオススメの内容のため敢えて上記には含めていません。動画全てを見る時間が取れない方でも、英語と内容の両方が一部難解な部分もありますが2それでも、ぜひ一度ご覧頂くことをオススメします。
To Be Continued
更に興味がある方がいらっしゃいましたら、今回のセッションの参加ブログを書いて頂いた方もいらっしゃいますので、こちらも併せてご参照頂けると理解がより深まるかと思います。@ganchikuによるセッション後半の解説記事も追ってこちらのブログで配信予定となっております。また、Twitterでは #dcitokyo というタグで本勉強会についての過去のツイートを検索できますのでこちらもオススメです。
Jimは年内にまた来日予定があるとお話されていましたので、次回日程等決まりましたらまた告知などを行っていければと考えています。その際に、最近開設されたDCITokyoの公式Twitterアカウントから各種告知等されていく予定となっていますので、今後こちらもぜひご注目ください。
当日Jimはルイスと同年代の建築家アドルフ・ロースの言葉と紹介していたため、要出典確認 ↩︎
55:46頃にJimが解説しているオペレーショナルモデルについて当日私の方で通訳として十分に説明できなかった部分については、追ってTwitterで後追いする形で一部捕捉の説明を試みていますので、こちらもご参照ください ↩︎
0 notes
Text
「Lean Architecture / DCI Evening」参加レポート
2017年10月18日、James Coplienさんとその奥様であるGertrud Bjørnvigさんをお招きして、「Lean Architecture / DCI Evening 」というイベントを開催しました。日本ではソフトウェアパターンやアジャイルのリーダーとして知られるJames Coplienさんは、『 マルチパラダイムデザイン 』(1998年)でドメインとドメイン間の関係を中心に据えた設計パラダイムを提唱していました。Coplienさんは2009年、MVCアーキテクチャの考案者である Trygve Reenskaug さんと共に「DCIアーキテクチャ」を発表しました。2010年、CoplienさんはGertrudさんとともに書籍『 Lean Architecture 』を上梓、トヨタ生産方式をソフトウェアアーキテクチャに適用するリーンアーキテクチャについて、DCIアーキテクチャをそのビルディングブロックと位置づけた具体的な方法を記述しました。今回、マルチパラダイムデザインの読書会を大阪で開催していた縁から、表題のイベントを開催することができました。この記事ではセッションの内容についてトークを再現する形でお伝えしたいと思います。
NOTE: (※)内は筆者によるコメントです。また、基本的に英語から修正せずに翻訳しているので、事実関係に関しては確認がとれていないものもあります。
CoplienさんによるDCIアーキテクチャの概説
Coplienさん(以下、C): 最初にクイズをします。みんな立って。DCIは、DCIと呼ぶ前はなんと呼んでいたか知っていますか?・・・DCA (Data-Collabolation-Algorithm)(※関連リンクを参照)です。知ってた人はそのまま立って。これを差し上げます。(※参加者の一人がサイン付きのLean Architectureをプレゼントされる)
C: (※DCIで開発するための言語基盤とIDEを提供する)Trygveのプログラムの拡張子は「.k」になっているのはなぜでしょう?これは哲学者のカントからとっている。
C: オブジェクト指向プログラミングができない、モダンな言語はなんでしょう?Rust?Java。OK、それだ。Javaはオブジェクト指向ではなく、クラス指向の言語だ。
C: DCIのWebサイトfulloo.infoにいろんなリソースがあります。サンプルコードとかビデオとかも入っている。
C: 今日はインフォーマルな会です。小さいDCIのチュートリアルをすることもできるし、私が最近どういう研究をやっているかを話すこともできる。DCIが実際にどうやってプログラマーの集中力を高めてエラーを取り除くことができるかということを話してもいいし、Trygve言語の実際の例もお見せすることができる。見た目はJavaみたいだけどね。そこには、コンテキストとロールも登場する。設計の話もしてもいいし、ドメイン分析の話をしてもいい。
参加者A: ドメイン分析とTrygve IDEも見たい。私たちは大阪で「マルチパラダイムデザイン」の読書会をしていました。MPDとDCIをどのように繋ぐかの話も聞きたい。
C: 今日は何人、大阪から来たの?Oh! DCIを殆ど知らないという人は?最初に小さいインタラクションをしましょうか。15分くらい。
C: マルチパラダイムデザインは、クラスベースの言語に基礎を置いて議論を展開している。DCIはリアルなビルディングブロック(構成要素)とオブジェクト群のネットワークです。そこが大きな違いです。ドメイン分析とかユースケース、ロールのアクターに関しては後ほど妻が説明してくれる。
C: まずはDCIの触り。簡単なデモを。銀行口座間の送金のケースを考えてみる。ソフトウェア上ではそれはどういう状態でしょうか。クラスを書いて、オブジェクト同士をインタラクションさせますね。クラスを書くときというのは、クラスの全てのメソッドを考慮して書く必要がある。
C: ATMの例を考えてみましょう。ATMが稼働していて、それを確認するために、コンソールにオブジェクトIDをプリントするプログラムを考えてみましょう。
(※メソッドが呼ばれるたびにオブジェクトIDがプリントされていくデモ)
C: これを見て何かパターンは見つかるでしょうか? 単に同じオブジェクトが延々と呼ばれていることがわかるだけですね!(※会場笑)呼ばれるオブジェクトの順番とかは全然重要ではなかったわけです。
C: 次にオブジェクトIDではなく、クラス名が表示されるようにしてみましょう。クラス名は重要ですよね。コンソールを見てパターンは見つかりますか?ないですね。これもまだ完璧ではないようです。クラスのうち、2つは口座に関するもの、最後の3つ目は通貨に関するものだということはわかります。しかし、アーキテクチャの振る舞いはクラスだけでは表現されていないようです。
C: さて、今度はメソッドが呼ばれる毎に、役割(※ロール)がプリントされるようにしてみますよ。・・・Ah ha!(※会場笑) 分かりましたね。お金を送金するときの構造はこういうふうになりますよね。構造は役割(※ロール)の中に入っています。
C: 1つの銀行に2つ以上の口座を持っている人はいますか?(※会場から手が)それでは、私に1つの自分の口座から他の口座に送金するユーザーストーリーについて説明して下さい。
参加者B: ある口座からある口座に送金します。妻にダイアモンドを買うために。貯蓄口座から普通口座へ。
C: もっと一般的に言うと?
参加者B: 振込元から振込先へ送金する。
C: 振込元口座を持っている人はいますか?それは、オブジェクトでしょうか?クラスでしょうか?なんでしょうかね、振込元口座みたいな役割とは?・・・人はこれを役割(ロール)として考えているわけですよね。クラスやオブジェクトとして役割(ロール)を考えてはいないですよね。
C: アラン・ケイの最初に考えていたオブジェクト指向というのはヒューマンメンタルモデルをコンピュータに適用させたものでした。そのメンタルモデルを今発見したのです!
参加者B: どうやって、口座の振込元とか振込先とかを判別するんでしょうか?
C: ピアジェという発達心理学者がいます。ピアジェに「オペレーショナルモデル理論」というのがあります。ビルディングブロック(※構成要素)を使って論理的に考える必要があります。オブジェクトやクラスではないものがあるのです。おそらく私たちは最初にオブジェクトを学び、その後にクラス・クラス化を学ぶようです。子どもが一番最初にオブジェクトを学ぶ時は、全てを自分自身として認識するようです、母親さえも自分だと思う。4-7ヶ月の間に区別をつけることができるようになっていく。小さな子どもは馬を見た時に、馬を指差して「犬!」と叫ぶ。(※あ〜!と会場共感の声)こういった認識は物事に共通性を見出していることによって成り立っています。私たちはプログラムを問題解決のために書いています。子どもは8ヶ月くらいの時に原因と結果という因果関係について学ぶ。その頃から机を押したり、上にあるものを叩いたりして何かが落ちたりするのを楽しむ。そうして最終的には、問題解決する能力を学習していく。それがオペレーショナルモデルと呼ばれるものです。
C: プログラムを通して問題解決するためにこの考えが必要ですね。プログラムを書くときにはどうやっているのでしょう?ここにこのようにオブジェクトが散らばっている。そしてオブジェクトをつなげるユースケースがある。これはまた別のユースケースですね。これはまた別のユースケース、また別のユースケース・・・(※会場笑)ユースケース��オブジェクトのシーケンスではないんですよね、こう見ると。順番があるわけではない。
C: では、ユースケースのコードをオブジェクトよりも上のレイヤーに取り出す、ということをやってみましょう。今、ユースケースのコードは、オブジェクトのクラスの中にあります。このユースケースの部分を、クラスの外側に出すのです。そうすると、オブジェクトの方は基本的なクラスのインスタンスのままなので、とても単純です。しかし、これらは実際のところユースケースの一部にはなりません。では、ユースケースの部品は何でしょうか? ロールですね。ユースケースの部品は、ロールの中にあります。開発者は、このようなロールの中からいくつかを選んでまとめます。このまとまりを、コンテキストと呼びます。 つまり、コンテキストがユースケースに相当します。そして、ユースケースを実行するには、サブジェクト(※ユースケースの入り口となるエンティティ、後述のサンプルコードのTransferMoneyContext.SOURCE_ACCOUNTに代入されるオブジェクトにあたる)にこれらのメソッド(※ロールのメソッド)をインジェクトします。このオブジェクト(※他のエンティティ、後述のサンプルコードのTransferMoneyContext.DESTINATION_ACCOUNTに代入されるオブジェクトにあたる)にもこれらのメソッドをインジェクトします。
C: オブジェクトは既にあるからユースケースを書きたい。例えばヘリコプターを飛ばすことについて考えてみましょう。オペレーショナルモデルで思考して、ロールを考えます。映画の「マトリックス」を見たことがあるますか?トリニティはヘリコプターを飛ばさなければいけなかったですよね。ヘリコプターを操縦するプログラムをダウンロードしていました。ロールのプログラムをダウンロードすることによってヘリコプターを飛ばすことができたわけです。ランタイムでオブジェクトに対してどのようにユースケース上で振る舞うのかを教えてあげる。ユースケースが終わると、オブジェクトは何をやるか、何をやったかを(※どのように振る舞うかを)忘れてしまうわけです。
参加者B: トリニティはオブジェクト(※エンティティ)、パイロットはロールということですね。
C: トリニティは、ヘリコプターを操縦するためのメソッドを持っている。手を動かしたり、足を動かしたりといった基本的なトリニティが持っているメソッドを使うことができる。コンテキストがヘリコプターを運転させる。トリニティは基本的な動作はできる。コンテキストとトリニティの間のメソッドの契約というものが必要で、トリニティは人なので人が持っているメソッドだけが使えるという契約がある。手を動かすとか。ヘリコプターを操縦するというのははコンテキストです。ロールはデータを持たない。純粋にメソッドを持っている。以上がDCIの基本的な話でした。
参加者C: ロールとクラスの違いがまだわからない。
C: クラスはデータを持ちます。あなた達はいわば人間クラスのインスタンスですよね?そこから初期化されている。(※会場笑)ヘリコプターパイロットはロールなので、ヘリコプターパイロットのインスタンスを作ることはできない。人間が必要なんです。あるいはロボット?もしくはゴリラ。
参加者C: Javaのインタフェースはロールに近い?
C: 似てるとは言えるけど、実装がないからロールを完全に表すかというと違うと思います。(※Java 8以降はインタフェースは実装を持てるようになりましたが)あと、Javaはインタフェースは静的に決まっていて、動的に決めることができないというところが異なります。クラスがインタフェースをextendするというのを全てコンパイル時に決めなければなりません。
参加者D: Swiftのプロトコルエクステンションを使うと近いことができますか?
C: Objective-Cでお腹いっぱいなので、Swiftの学習は避けているものでね(※会場笑)プロトコルエクステンションを使うと近いことができるようです。
参加者C: クラスとロールの違いについて。Java クラスとインタフェース、Scala クラスとトレイト、C++ 動的クラス、Virtualクラス これらはDCIの実装として使うことができるか。
C: Scalaのトレイトはかなり近いです。Scalaではトレイトのミックスインを使う。トレイトを使ってクラスに合成できる。その結果クラスは両方の機能のメソッドを持つことができる。C++ではどうするか?C++の場合はクラスの合成に継承を使ってしまうので難しい。どうするか?ロールをテンプレートを使って書くことができる。テンプレートを使えばDCIを実現することは可能です。ただそれはコンパイルのタイミングになってしまう。ダイナミックではない。fulloo.infoも見てくださいね。なんとなく、DCIのイメージが掴めましたか?
C: DCIのための言語として気に入ってるものの一つはRubyです。オブジェクトの拡張が簡単にできる。モジュールを使ってミックスインすることもできます。ただ、メソッドは自由に追加することはできるが、それを取り外すのが難しい。名前の衝突の問題もある。Matzはこの問題について理解してくれたが、一度インジェクトしたものを引き剥がす機能を入れることまでは、説得するには至らなかった。Rubyの仮想マシンを変更すればできるのですがね。
(※実は筆者はRubyistなのですが、refinementを使うとある程度は目的に適うのではないのかなーと思ってしまいました)
C: Trygveを使ってる人。(※一人だけ手が挙がる)オープンソースですよ!(※会場笑) お金の送金のサンプルを見せていきます。ここにクラスがあります。これはドメイン分析から抽出されたものです。 amount()(金額) などのメソッドを持っている。クラス指向のプログラミングならこんなものでしょうか?Trygveの紹介をしていきますね。
(※以下のコードはTrygve で書かれた送金のサンプル https://github.com/jcoplien/trygve/blob/master/examples/july_money_transfer.k より引用)
/* * july_money_transfer.k */ class Currency { public Currency(double amount) { amount_ = amount.clone } public Currency +(Currency amount) { assert(false) return this; } public Currency -(Currency amount) { assert(false) return this; } public Currency() { } public String name() const { assert(false); return "" } public String sign() const { assert(false); return "" } public double amountInEuro() const { assert(false); return 0.0 } public double amount() const { return amount_ } public String toString() const { return amountInEuro().toString() } public int compareTo(Currency other) { if (amount() > other.amount()) return 1 else if (amount() < other.amount()) return -1; return 0 } private double amount_ } class Euro extends Currency { public Euro(double amount) { Currency(amount) } public Euro -(Currency amount) { return new Euro(amount() - amount.amountInEuro()) } public Euro +(Currency amount) { return new Euro(amount() + amount.amountInEuro()) } public String name() const { return "Euro"; } public String sign() const { return "€"; } public double amountInEuro() const { return amount() } public String toString() const { return amount().toString() } } class Account { public Account(int acctno) { acct_ = acctno } public String accountID() const { return acct_.toString() } public Currency availableBalance() const { assert(false); return null } public void increaseBalance(Currency amount) { assert(false) } public void decreaseBalance(Currency amount) { assert(false) } public void updateLog(String message, Date dt, Currency amount) { assert(false) } private int acct_ } class CheckingAccount extends Account { public CheckingAccount() { Account(1234); availableBalance_ = new Euro(100.00) } public Currency availableBalance() const { return availableBalance_ } public void decreaseBalance(Currency c) { availableBalance_ = availableBalance_ - c } public void updateLog(String message, Date t, Currency c) const { System.out.print("account: ").print(accountID()) .print(" CheckingAccount::updateLog(\"").print(message) .print("\", ").print(t.toString()).print(", ") .print(c.toString()).print(")") .println() } public void increaseBalance(Currency c) { availableBalance_ = availableBalance_ + c } private Currency availableBalance_ } class SavingsAccount extends Account { public SavingsAccount() { Account(1234); availableBalance_ = new Euro(0.00) } public Currency availableBalance() const { return availableBalance_ } public void decreaseBalance(Currency c) { assert(c > availableBalance_); availableBalance_ = availableBalance_ - c } public void updateLog(String logMessage, Date timeOfTransaction, Currency amountForTransaction) const { assert(logMessage.length() > 0); assert(logMessage.length() < MAX_BUFFER_SIZE); // assert(new Date() < timeOfTransaction); System.out.print("account: ").print(accountID()) .print(" SavingsAccount::updateLog(\"").print(logMessage) .print("\", ").print(timeOfTransaction.toString()) .print(", ").print(amountForTransaction.toString()) .print(")").println() } public void increaseBalance(Currency c) { availableBalance_ = availableBalance_ + c } private Currency availableBalance_; private int MAX_BUFFER_SIZE = 256 } class InvestmentAccount extends Account { public InvestmentAccount() { Account(1234); availableBalance_ = new Euro(0.00) } public Currency availableBalance() const { return availableBalance_ } public void increaseBalance(Currency c) { availableBalance_ = availableBalance_ + c } public void decreaseBalance(Currency c) { availableBalance_ = availableBalance_ - c; } public void updateLog(String s, Date t, Currency c) const { System.out.print("account: ").print(accountID()) .print(" InvestmentAccount::updateLog(\"") .print(s).print("\", ").print(t.toString()) .print(", ").print(c.toString()).print(")") .println() } private Currency availableBalance_; } class Creditor { public Creditor(Account account) { account_ = account } public Account account() { return account_ } public Currency amountOwed() const { return new Currency(0.0) } private Account account_ } class ElectricCompany extends Creditor { public ElectricCompany() { Creditor(new CheckingAccount()) } public Currency amountOwed() const { return new Euro(15.0) } } class GasCompany extends Creditor { public GasCompany() { Creditor( new SavingsAccount()); account().increaseBalance(new Euro(500.00)) // start off with a balance of 500 } public Currency amountOwed() const { return new Euro(18.76) } } context TransferMoneyContext { // Roles role AMOUNT { public Currency(double amount); public Currency +(Currency amount); public Currency -(Currency amount); public String name() const; public String sign() const; public double amountInEuro() const; public double amount() const; public String toString() const; public int compareTo(Currency other) } requires { Currency(double amount); Currency +(Currency amount); Currency -(Currency amount); String name() const; String sign() const; double amountInEuro() const; double amount() const; String toString() const; int compareTo(Currency other) } role GUI { public void displayScreen(int displayCode) } requires { void displayScreen(int displayCode) } role SOURCE_ACCOUNT { public void transferTo() { // This code is reviewable and meaningfully testable with stubs! int SUCCESS_DEPOSIT_SCREEN = 10; // beginTransaction(); if (this.availableBalance() < AMOUNT) { // endTransaction(); assert(false, "Unavailable balance") } else { this.decreaseBalance(AMOUNT); DESTINATION_ACCOUNT.increaseBalance(AMOUNT); this.updateLog("Transfer Out", new Date(), AMOUNT); DESTINATION_ACCOUNT.updateLog("Transfer In", new Date(), AMOUNT); } // GUI.displayScreen(SUCCESS_DEPOSIT_SCREEN); // endTransaction() } } requires { void decreaseBalance(Currency amount); Currency availableBalance() const; void updateLog(String msg, Date time, Currency amount) } role DESTINATION_ACCOUNT { public void transferFrom() { this.increaseBalance(AMOUNT); this.updateLog("Transfer in", new Date(), AMOUNT); } public void increaseBalance(Currency amount); public void updateLog(String msg, Date time, Currency amount) } requires { void increaseBalance(Currency amount); void updateLog(String msg, Date time, Currency amount) } public TransferMoneyContext(Currency amount, Account source, Account destination) { SOURCE_ACCOUNT = source; DESTINATION_ACCOUNT = destination; AMOUNT = amount } public TransferMoneyContext() { lookupBindings() } public void doit() { SOURCE_ACCOUNT.transferTo() } private void lookupBindings() { // These are somewhat arbitrary and for illustrative // purposes. The simulate a database lookup InvestmentAccount investmentAccount = new InvestmentAccount(); investmentAccount.increaseBalance(new Euro(100.00)); // prime it with some money SOURCE_ACCOUNT = investmentAccount; DESTINATION_ACCOUNT = new SavingsAccount(); DESTINATION_ACCOUNT.increaseBalance(new Euro(500.00)); // start it off with money AMOUNT = new Euro(30.00) } } context PayBillsContext { public PayBillsContext() { lookupBindings } role [] CREDITORS { } requires { Currency amountOwed() } stageprop SOURCE_ACCOUNT { public String accountID() const; public Currency availableBalance() const; public void increaseBalance(Currency amount) unused; public void decreaseBalance(Currency amount) unused; public void updateLog(String message, Date dt, Currency amount) unused } requires { String accountID() const; Currency availableBalance() const; void increaseBalance(Currency amount); void decreaseBalance(Currency amount); void updateLog(String message, Date dt, Currency amount) } // Use case behaviours public void doit() { for (Creditor credit : CREDITORS) { // Note that here we invoke another Use Case TransferMoneyContext xfer = new TransferMoneyContext( credit.amountOwed(), SOURCE_ACCOUNT, credit.account()); xfer.doit() } } private void lookupBindings() { // These are somewhat arbitrary and for illustrative // purposes. The simulate a database lookup InvestmentAccount investmentAccount = new InvestmentAccount(); investmentAccount.increaseBalance(new Euro(100.00)); // prime it with some money SOURCE_ACCOUNT = investmentAccount; Creditor [] creditors = new Creditor [2]; creditors[0] = new ElectricCompany(); creditors[1] = new GasCompany(); CREDITORS = creditors } } { // Main TransferMoneyContext aNewUseCase = new TransferMoneyContext(); aNewUseCase.doit(); PayBillsContext anotherNewUseCase = new PayBillsContext(); anotherNewUseCase.doit() }
C: Account(口座) という別のクラスがあります。ドメイン分析から抽出されたものです。口座番号というデータを持っている。メソッドは increaseBalance()(残高増) と decreaseBalance()(残高減) 。口座番号を与えれば、このメソッドを使っていろんなことができる。いわば、ちょっと高級な Integer みたいなものです・・・。全くもって良くないですね!貯金用の口座とか、投資用の口座とかどんどん増えていってしまいます。会社の口座、お金の債権者… ここまでができの良くないクラス指向のプログラミングのやり方でした。
C: さて、それでは送金のユースケースについて見ていきましょう。Context がキーワードです。多くの点でクラスのように見えるのだけれど、クラスとは異なっていて、ほとんどの場合データを持たずにロールだけを持っている。 Amount(金額) と呼ばれるロールはとてもシンプルなロールです。これで資金移動をすることができる。
C: (※デモを見せて)これがロールのインタフェースのシグネチャです。オブジェクトがこれらのメソッド全てをサポートするように定義する必要があります。これを「必須の契約」(※the requires contract)と言います。オブジェクトがロールを表現する場合、これらのメソッドを全てサポートしなければならない。
参加者C: ここで言う契約とは、「契約による設計」の契約のことですか?
C: その通りです。バートランド・メイヤーという人が提唱した契約による設計。この人の言ってることは、だいたい合っているけど突き詰められていない。
参加者B: メイヤーの考えのどこが機能しないんでしょう?
C: クラスAがあり、そこにメソッド1があります。そこには事前条件、メソッド実行、事後条件があります。メソッド1は事前条件が真であることを要求し、事後条件が保証されていることを約束します。次にメソッド1の中からメソッド2を呼ぶケースを考えてみましょう。メソッド2にとっての事前条件を見た時、メソッド2にとってはメソッド1が状態を維持してくれているという想定をしているわけですが、メソッド1の実行中に自己隠蔽されている状態の中で条件がおかしくなった状態でメソッド2を呼ぶとメソッド2が結果保証している事後条件が壊れてしまうことがある。
C: クラス側で本体となる実装を持たないのは、ロールのプレイヤーの持っている実装を使うだけだからです。ロールを演じるオブジェクトの実装を使います。別のオブジェクトに由来するメソッドを使うわけです。これらのメソッドはパブリックインタフェースで、ロールが何をするかを知ることができます。
C: よりわかり易い例として SourceAccount(振込元口座) と呼ばれる別のロールも見ていきましょう。SourceAccount は transferTo()(〜に送金する) として呼ばれるメソッド、責務を持っています。今からこの箇所のコメントを外して動くようにしましょう。トランザクションを開始しますよ。もし振込元口座が利用可能な残高を持っていれば、コメントを外した箇所は何になるでしょうか?
参加者B: ロールを注入されたオブジェクト
C: そうです! SourceAccount ロールを適切に演じるオブジェクトです。利用可能な残高はどこでしょうか?気をつけてください。ここ(※ロール)にはありませんよ。残高は、ロールを演じる口座オブジェクトの側、ロールプレイヤーにあります。
C: クラスみたいだけれど、ランタイムで考えられるようにしてくれます。SourceAccount ロールを演じるオブジェクトについて考えてほしいです。ロールプレイヤーはマトリックス(※コンテキストが持つロールとオブジェクトのマッピング表)によってもたらされるものです。ロールがオブジェクトに注入されると、オブジェクトが機能を持つようになって、送金ユースケースを実現できるようになります。送金メソッドは振込元の Account オブジェクトに注入されます。
C: こちらは別のロール、DestinationAccount(振込先口座) です。DestinationAccountはメソッド transferFrom()(〜から送金される) を持ちます。transferFrom() メソッドは DestinationAccount の残高を更新します。
C: コンテキストのコンストラクタを見てみましょう。TransferMoneyContext(送金コンテキスト) です。コンテキストの中で何がオブジェクトかを伝える必要あります。送金の金額、振込元、振込先、口座の種類。
C: マトリックス(※コンテキストが持つロールとオブジェクトのマッピング表)を表すオブジェクトを持っていて、ロールのメソッドが振込元の Account オブジェクトに注入されます。要はこれ(※コンテキスト)はオブジェクトだと言うことです。Account オブジェクトに対して振込元口座にあるべきメソッド群を与えていきます。送金ユースケースのためにロールプレイヤーが演じるメソッド群です。そうすれば、ランタイムでオブジェクトを組み立てることができます。
C: 他のユースケース、お金を支払うについて見ていきましょう。特別なロールがあります。そして、Amount や SavingsAccount(貯蓄口座) のようなオブジェクトが新たに一つここにあります。これらは、プラグアンドプレイをするために用意したアクターです。私が台本を書いたロールがあり、アクターは芝居においてロールを演じます。
C: これは舞台のようなもので、大道具(stage props)はアクターの持っている道具です。大道具だけでは、一切何もできません。ただデータを引き出すことはできます。
参加者B補足: 何かをすることはできないのだけど、データを引き出すことはできる。
C: このロールの全てのメソッドは const で、アクターの状態を変更することはできません。
参加者B: const というのはロールの型かなにかですか?
C: そうです。ロールの束縛、制約の種類です。
C: さて、stage props (大道具に由来する機構名) の使いどころはわかりますか?オブジェクトは2つのロールを一度に演じるかもしれません。同じオブジェクト、同じインスタンスで二役を一度にこなすことがあります。とても複雑な例として再帰実行になるケースもありえます。これが先程上がっていた問題の一つで、複数回呼び出したら事前条件が崩れることがあるという例です。このstage propsはデータを更新しないから、問題が起こらないようにすることができます。
C: ちょっと難し目の例を見てみましょう。Javaのグラフ描画のデータ構造です。ブロック崩しと言われるピンポンゲームです。変数の中をチェックするデバッガも入っています。700行くらいのプログラムでこれができます。
C: MPDからDCIへどう変遷していくかについて。よく知られているように、対象のドメインを理解することから始めます。殆どの場合、インタラクティブなプログラムはクラス指向のプログラムでできています。オブジェクト指向プログラミングとは、アラン・ケイが定義したように、オブジェクト同士の協調によるネットワークです。これをどうやって意識するかについて。
GertrudさんとCoplienさんによるユーザーストーリー、アジャイルとリーンアーキテクチャ
Gertrud さん(以下、G): 皆さんはユーザーストーリーは知っていますか?ユースケースは?アジャイルになるためにはユーザーストーリーをたくさん作りましょう。リーンになるためには、ユースケースが必要。
G: ユーザーストーリーの形式(フォーム)として、以下のようなフォーマットで考えるといいです。
As a <USER or ROLE> 例: 口座を持っている人として I want <FEATURE> 例: ある口座から別の口座に移したいという行為 So I can <MOTIVATION> そうすればなにができるか。例えば、ダイアモンドを買ってあげることができる。
ユーザーストーリーの構成要素はこのようになっています。これをブレインストーミングなどでたくさん作っていく。 上から、Who、What、Why に対応している。
G: ユーザーロールには、口座を持っている人、銀行の従業員、(※例えば電気代を支払おうとする)市民などがいます。
G: お金の送金は、今回の場合、全てのロールで行うケースがある。しかし、それぞれのロールでやりたい事は違う。ロールやモチベーションによってコンテキストが変わってくる。実装する前に、モチベーションがわかるので、実装することができる。これは実装する前に知る必要があるんです。
G: ユーザーストーリーの洗い出しは、ブレインストーミングであげていく。するとかなり数が増えていく。そこから一般的な(※抽象度の高い)状態に近づけていく。そうすれば、ユーザーストーリーからユースケースに落とし込むことができる。
G: ユーザーストーリーは具体的で、抽象的なのがユースケース。どこまで抽象的にするべきか?コードで理解できるレベルまでです。ユースケースをリーダブルコードに落とし込めるところまで持っていくのが目標になる。
G: ユーザーストーリーはインプットなので、なるべくたくさんの実例を出したほうがいい。銀行の業務であれば、昔に使っていた小切手の話をユーザーストーリーとして抽出するくらい。
G: ユースケースの段階で要点を厳密に抽出する。これがそのままプログラムに使われるから。
(ここで休憩に入りました)
G: ユースケースで大事なことはターミノロジー(※用語)。システム分析とシステム設計で同じボキャブラリー、ターミノロジーを使ってお互いの同意を取っていくことが大事。関係者の間で合意をとるのも大事になってくる。
G: 分析と設計で、同じボキャブラリー、同じターミノロジーを使って話をするので、相互に一貫性をもたせることができる。もちろん違うチームに行くと言葉も変わっていく。
G: 銀行だったら銀行員にも話を聞かないといけない。ドメインエキスパートだけではなく、銀行員もチームに入って、彼らの使っている用語をコードに落とし込んでいく。ユースケースレベルになると、コードのステップレベルまで落とし込めるようになる。
G: コアシナリオとサテライトシナリオというのがある。コアシナリオはシナリオの中でも本道で大事な物。サテライトシナリオは、例外的だったり拡張されていたり複雑だったりするもの。それでも、システムはこういうものもカバーしなければならない。
参加者: なぜ、「サテライト」という用語にしたか?
G: オリジナルの用語は、インクルードとエクスクルードだった。その使い分けはわかりにくいので、コアシナリオとサテライトシナリオにした。また、デビエイション(※deviation: 逸脱、脱線、偏向)と一時期は呼んでいた。コアシナリオじゃないなにか、そんな感じ。
G: ユースケースという用語は昔ながらの用語で古臭い感じがするので使わない方向でというのはある
G: 感覚的にはコアシナリオがスコープ全体の80%、残りはサテライトシナリオという割合になる。
G: サテライトシナリオのほうがアジャイルっぽいやり方で進めていける。そもそも複雑だったり、変わりやすかったりするので。
G: 具体的なユーザーストーリーをユースケースに落としていく時に、どこまで一般化するのか。例えば、銀行で資金移動する時に使う一般的な用語は送金するという用語とか、そういうことを考えてちょうどいい落とし所を見つけていく。
C: 銀行口座の残高がマイナスになった時に、自動的に所有者の他の口座から振り替えてマイナスを補填するような、システム起動のユーザーが関与しない形でのシステムオペレーションを考える。MVCはエンドユーザーから見た、それに基づいたプログラミングモデル。DCIはプログラマーサイドのユーザーモデルを表現している。先程の、ユーザーがでてこないシステムオペレーションのようなシステムオペレーションも自然に扱うことができる。
G: ターミノロジーがとても大事。ターミノロジーのデータベースを会社が持っていて、緻密に定義した辞書を持っている。そう、会社に辞書がある。プログラムの中の変数やファイル名なんかもそのデータベースを見て決める。 辞書に用語がなければ、辞書に追加したり変更したりする。追加するときにはちゃんと申請しなければいけない。
G: ソースコードの中にはコメントを書いてはいけない。さきほどの辞書を使って、ソースコードが、例えば英語からドイツ語などの自然言語にそのまま翻訳されるので。ソースコード中にコメントがあるとその内容をいちいち翻訳しなければならなくなる。
G: 最初に生まれた子供と同じくらいの注意深さで名前をつけよう。
C: ポリモーフィズムがないからコードが読みやすいのかもしれない。ポリモーフィズムがあると、メソッドがどこから呼び出されるかわからないからプログラムの見通しが悪くなる。ボキャブラリーも大事だが構造も大事。ある研究では、ユースケースのいろんな箇所にコードを分散させるとエラー率が70%増えるという調査結果がある。
C: リーンアーキテクチャの良いところとして、エラーを早く見つけることができることがある。テストをしているときではなく、コードを読んでいる時にエラーに気づくことができる。
C: フォードは組み立てを実際にして、テストは最後に確認している。トヨタの場合は、組立時にその場でテストをしている。リーンのアプローチの1つとして、問題をより早く見つけるという利点がある。
C: アジャイルとリーンについて。リーンはクラスの構造が重要。リーンにコストをかけて、アジャイルで実際にどう稼いで行くかを考える。アジャイルはオブジェクトのインタラクションが重要。
C: ドメイン分析とユースケース分析も同時並行的にやる。同時並行的にやると重なる場所がある。みんなそれぞれ同時に実行していく。
C: 野中先生がトヨタにいた時に気づいた。それぞれの分析がかさなりあって進んでいく。これを、刺し身モデルという。(※ラグビーの)スクラムも刺し身。挟まり合っているでしょ。ウォーターフォールとは違って皆が全てのことをやるからスクラム。
C: リーンは長い期間のプランニングが必要で大事になってくる。アジャイルはフィードバックに応じた再調整が肝要。リーンは、深いレベルの専門家が作る。アジャイルはなんでもありみたいなところがある。スクラムとDCIはそのふたつのことをやる。
C: パターンも同じように両方のものを含む。これは日本の禅から来ていると思います。パターン系のガイダンスに従って、門を通ってゴールに近づくことはできるのではないか?パターンもスクラムも、ある程度のレベルではDCIも、ほとんどは日本由来のもの。道教だよね、これ。
C: リーン側のほうは、日本のトヨタの思想。山田 ひろしと言う有名なエンジニアでホンダの人がトヨタに教えた。(※原典見つけられず)
C: アジャイルのほうは、日本でよく知られた比喩で伝えるのは難しいですが… 社会的構造が違うので。デンマークは、社会がとてもフラット。日本は階層的なところがある。よりシステム化されているので、色々なルールが存在する。日本は、もうちょっとアジャイルのほうをエクササイズするといいかもしれません。
C: 実際の開発の流れ。ドメイン分析 → 共通性可変性分析 → クラス → インタフェース、API設計。最初はインタフェースしか書かない。もしかしたらスタブくらいは書くかもしれない。実装はいつ書くか?ユースケースのタイミングで。Just In Time。ユースケースが必要とするインタフェースだけを実装する。(※インターフェイスをクラスで実装するのではなく、インターフェイス自体を実装する)インタフェースは抽象的な概念で、ロールは具体的なインタラクションやアルゴリズムなので、そのタイミングで。
C: この中でプラットフォームを開発している人。プラットフォーム開発はリーンではない。あらかじめ用意しておくもの。だからリーンではない。使わないものを作る時は会社が危なくなる。これは、無駄と言います。
C: DCIアーキテクチャを利用した開発の順番としては、まずアーキテクチャを作ってそれからそれぞれデータベースとかGUIのインタフェースを作る。会社としてなにを売るかというのはいろいろあるけど、売るのはユースケース。それぞれのクラスを売っているわけではない。ユースケースは、それぞれのデータベースとかサーバーとかクライアントのユーザーインタフェースなどを少しずつ重ねたもの。
C: 生産性が一番の無駄を生むと、トヨタの大野耐一さんがおっしゃっている。
C: こういう話を、「object-composition」というメーリングリストで話をしているので、入ってくださいね。
C: 1月にまた東京に来るので、またこういう会を開催しましょう!
最後に、GertrudさんとCoplienさんと参加メンバーで集合写真を撮りました。

NOTE: 当日は同時翻訳付きの英語トークでした。通訳の任を快諾してくださった、 @ganchiku さん、@remore さん、ありがとうございました。また、記事を書くにあたっては英語の下訳で @kuma_nana さんに貢献していただきました。こちらもありがとうございます。もちろん、当ポストの文責は執筆者である私にあります。
関連リンク
fulloo.info
The trygve language project
マルチパラダイムデザイン読書会
[Reenskaug 2006] Trygve Reenskaug. MVC and DCA Example program comments
object-compositionメーリングリスト
関連記事
PHP Mentors -> Beyond MVC
PHP Mentors -> Debasish Ghosh氏のブログ記事「ドメイン駆動設計:可変性の管理」を翻訳しました
PHP Mentors -> PHPカンファレス2015 PHPメンターズセミナー「モデルを設計せよ!―ドメイン駆動設計を超えて」参加レポート
0 notes
Text
「現場で役立つシステム設計の原則」批判 (2) ~ポリモーフィズムは何のために?~
増田亨氏の「現場で役立つシステム設計の原則]」批判の第2編です。
(2)ポリモーフィズムは何のために?
オブジェクト指向の要件
本書には「変更を楽で安全にするオブジェクト指向の実践技法」というサブタイトルが付与されています。オブジェクト指向が何かという点については、論者によって違いはあるものの、以下の3つが要件とされることには、多くの人が合意するでしょう。
カプセル化
インヘリタンス(継承)
ポリモーフィズム(多態)
前回で明らかになったように、カプセル化は、オブジェクト指向とは独立にモジュール分割の指導原理としてデイビッド・パーナスにより提唱された「情報隠蔽」を敷衍したものです。オブジェクト指向の要件ではありますが、オブジェクト指向固有のアイデアではありません。
インヘリタンスは便利な機能ですが、コンポジションや移譲により代替できるので、オブジェクト指向の本質的な要件とは見做されなくなってきたかと思います。
となれば、プログラミングの歴史にオブジェクト指向が付け加えたものは、ポリモーフィズムであるということになります。オブジェクト指向のもっとも偉大な貢献は、イヌがワンと鳴き、ネコがニャーと鳴くことです!
さて、こうした視点から本書を通読すると、まず、カプセル化については、その手段に過ぎない「データと処理を一体にする」という点に囚われ過ぎて、目的で��る「情報隠蔽」を取り逃がしているということが言えるかと思います。これが前回の指摘でした。
本書でのポリモーフィズム
では、本書ではポリモーフィズムはどのように扱われているでしょうか。
本書でポリモーフィズムを明示的に扱っているのは、「Chapter2 場合分けのロジックを整理する」だけです。この章でのポリモーフィズムの用途は、区分値に関する条件分岐(if文/switch文)を排除することです。
ポリモーフィズムのこうした利用方法は、マーティン・ファウラーのリファクタリング本で紹介され知られるようになったかと思いますが、便利でもあり、広く使われているものでもあると思います。
ただ、こうした「区分値ポリモーフィズム」は、ポリモーフィズムを適用可能なユースケース全体の中で、かなり周辺的なものであるように私には感じられます。その理由について以下ご説明します。
ポリモーフィズム ― 典型的な用法
その前にまず、ポリモーフィズムの典型的な用法とはどのようなものでしょうか。前回ご提示した、受注(SalesOrder)における値引き計算を例にご説明しましょう:
class SalesOrder { Money unitPrice; Quantity quantity; // ... Money amount() { if (isDiscountable()) return discount(unitPrice, quantity); return unitPrice.multiply(quantity); } // ... boolean isDiscountable() { return quantity.compareTo(discountCriteria) >= 0; } }
このコードでは、開示してよい知識と隠蔽したい知識を以下のように切り分け、前者をisDiscountable()に閉じ込めました:
開示してよい知識
受注ごとにその内容に応じて値引き可否が決まるという知識。
隠蔽したい知識
注文数量・金額等にもとづく具体的な値引き決定ルール。
ここで、「隠蔽したい知識」をなぜ隠蔽したいかというと、それが変わりやすいと考えるからです。ならば、一歩進んで、変わりやすい部分は差し替え可能にしておこうという発想が生まれます:
class SalesOrder { Money unitPrice; Quantity quantity; DiscountPolicy discountPolicy; // ... boolean isDiscountable() { return discountPolicy.isDiscountable(this); } }
ここで、DiscountPolicyは、以下のようなインターフェースです:
interface DiscountPolicy { boolean isDiscountable(SalesOrder salesOrder); }
DiscountPolicyの実装のひとつとして、QuantityBasedDiscountPolicyがあります:
class QuantityBasedDiscountPolicy implements DiscountPolicy { Quantity discountCriteria = Quantity.valueof(100); boolean isDiscountable(SalesOrder salesOrder) { return salesOrder.getQuantity().compareTo(discountCriteria) >= 0; } }
QuantityBasedDiscountPolicyは、たぶん、DIコンテナなどにより、SalesOrderのdiscountPolicyに注入されるでしょう。
この例で、ポリモーフィズムは、SalesOrderに関する様々な関心事から値引き計算に関する関心事を分離するのに用いられています。 例えば、テストという局面をとっても、QuantityBasedDiscountPolicyはSalesOrderに関する他のテストから切り離しでテストできますし、SalesOrderの方は(スタブを用いて)値引き計算の詳細を考慮せずにテストすることができます。 さらに、このソフトウェアが成功し、他社でも使われるようになれば、値引き計算ルールをこのように簡単にカスタマイズできることは、さらなるメリットをもたらすでしょう。
DiscountPolicyのように、特定の計算/判定条件をカプセル化し差替可能にするというポリモーフィズムの利用法は、GoFのデザインパターン本で「ストラテジー(別名:ポリシー)」として知られており、エリック・エバンスのドメイン駆動設計本でも、ごく最初の方で、オーバーブッキングポリシーという例が紹介されています(p.19/位置No.1068)。高度な利用法ではなく、極めて普通な用いられ方かと思います。
前回お話しした「情報隠蔽(=カプセル化)」では、隠蔽したい知識と開示してよい知識を区分して両者を別のモジュール(クラスもモジュールです)に割り振りました。 ポリモーフィズムは、そこから一歩進んで、それら2つのモジュールを切り離し、「疎結合化」するのに役立ちます。「疎結合」というと大げさに響きますが、要するに処理の依頼側と引受側2つのモジュールがあって、依頼側が引受側を知らずとも処理を依頼できるということです。この例で、値引き可否判定を依頼する側であるSalesOrderクラスはQuantityBasedDiscountPolicyクラスを知りません。
こういった意味における疎結合化は、オブジェクト指向以前も行われていましたが(*1)、気軽に、広範に適用できるようになったのは、やはりポリモーフィズムが生み出されてからのことです。
区分値ポリモーフィズムと本書に対する評価
さて、ポリモーフィズムの典型的なユースケースをおさらいした頭で、「区分値ポリモーフィズム」を見直してみましょう。
本書が説明する通り、区分値ポリモーフィズムには、if文やswitch文を排除することで、ソースコードの読みやすさを改善する機会を提供する、という役立ちがあります。
しかし、区分値オブジェクトのメソッド(本書の例では、yen()など)を呼び出す側のプログラムは、区分値オブジェクトのクラスを「知って」いるのが通常ですから、処理の依頼側と引受側は結合したままです(*2)。 ですから、前述したように「疎結合化」をポリモーフィズムの大きな意義と捉える立場からすれば、区分値ポリモーフィズムはやや傍流的な用法に見えることがお分かり頂けるでしょう。
公平に見て、本書は、ポリモーフィズムの他のユースケースについて触れていないだけであり、それらを否定しているわけではありません。ですから、本件は、本書の問題というより、読む側が注意すべき点である、ということなのかもしれません。
ただ、「多態は、区分ごとのロジックをクラス単位に分離してコードを読みやすくするオブジェクト指向らしいしくみです(p.58/位置No.1040)」といった説明を読む読者、とりわけオブジェクト指向の経験が浅い読者が、ポリモーフィズムの主な用途は区分ごとの場合分けロジックを整理することなのだと受け止めるのは自然なことでしょう。しかし、その道の先は行き止まりなのです。
むしろ、初級者には、疎結合化というビジョンのもとで、できるだけ簡単な入り口を示してあげるのが親切というものでしょう。上述したポリシーあるいはストラテジーパターンはそのような入り口として好適な例のひとつと思います。
「現場で役立つシステム設計の原則」という格調高いタイトルと「変更を楽で安全にするオブジェクト指向の実践技法」という具体的な副題を持つ300ページ超の本の中で、オブジェクト指向の業績の中心にあるポリモーフィズムについてこのように周辺的なユースケースのみ解説されている事態は、少なくとも私には驚異的なことです。
ポリモーフィズム ― さらなる展開
今回の批判はこれで終わりですが、例として取り上げた値引き計算のケースは、ポリモーフィズムの可能性を検討する上で興味深いので、もう少し深堀りしてみましょう。 前回以来の例では値引き可否判定ロジックの扱いが焦点でしたが、実際には可否を判定するだけでなく値引き額の計算が重要でしょう。業種にもよりますが、値引きの計算は複雑で、特定品目限定の値引き、過去の取引履歴に基づく値引き、一回の総発注額に基づく値引き、キャンペーンでの値引きなど多岐にわたるかもしれません。また、そうした値引きルールは、時の経過に応じて追加され、また廃止されていきます。システムは、そうした諸々の値引きについて、理由と金額をお客様に提示する必要があるでしょう。
こうした状況にどう対応すればよいでしょうか。SalesOrderクラスは、isDiscountable()メソッドで値引き可否だけを返せばよいのではなく、値引きの詳細を返さなければなりません。例えば、以下のようなメソッドを備えることになるでしょう:
List<Discount> getDiscounts();
Discountは、値引き理由の説明と値引き額を保持するオブジェクトです:
class Discount { String description; Money discount; // ... }
ここでの焦点は、値引きルールの詳細を隠蔽することに加えて、その時々に応じて値引きルールを追加し、あるいは廃止出来るようにすることです。それを踏まえれば、getDiscounts()の詳細は以下のようになるでしょう:
class SalesOrder { // DIコンテナなどから値引きポリシーのリストを設定 List<DiscountPolicy> discountPolicies; // ... List<Discount> getDiscounts() { List<Discount> discounts = new ArrayList<Discount>(); // 値引きポリシーを適用 for (DiscountPolicy discountPolicy : discountPolicies) { Discount discount = discountPolicy.getDiscountFor(this); if (discount != null) { discounts.add(discount); } } return discounts; } }
インターフェースDiscountPolicyは以下のようになります。
interface DiscountPolicy { /** * 与えられた salesOrder に対する値引きを計算し、その結果を Discount として返す。 * 当ポリシーでの値引きが salesOrder に適用されない場合は null を返す。 */ Discount getDiscountFor(SalesOrder salesOrder); }
このようにしておけば、新しい値引き制度が出来たときには、それに対応するDiscountPolicyを作成してシステムに登録するだけで対応が完了します(*3)。
値引き計算以外に、請求/回収条件、在庫引当、配送方法などにも同じような仕組みが適用可能かもしれません。こうした手法を常に適用すべきということではありませんが、適用しようと思えば出来る、ということを理解していると、設計における選択肢の幅が顕著に広がります。
こうした例をお示ししたのは、ポリモーフィズムというものが過度に技術的なものと受け止められているのではないかと私が危惧しているからです。
イヌがワンワン、ネコがニャーニャーという説明ばかりでポリモーフィズムはわからん、という方が多いですが、一方でその方もDIコンテナは使っていたりします。DIコンテナは、技術的環境に依存するオブジェクトに関して、アプリケーションにはインターフェースだけ提示し、その実装はポリモーフィズムによって実行時に結合することで、アプリケーションと技術的環境を疎結合に保つための仕掛けです。ポリモーフィズムのメカニズムが本当にわからないのであればDIコンテナを使うことさえ出来ないはずです。
ですから、多くの人にとってわからないのは、ポリモーフィズムのメカニズムではなく、「使いどころ」なのだろうと思うのです。フレームワークなどがポリモーフィズムを使っているのはわかっている。ただ、自分たちが書いている「アプリケーション」のコードにおいてどういう局面でポリモーフィズムを使えばよいのか、わからない。結果として、ポリモーフィズムは自分たちに関係ない「技術的」な概念だと感じてしまう。そういうことではないでしょうか。
実際には、それは誤解で、アプリケーション開発においてもポリモーフィズムはカ��ュアルに利用できます。値引き計算の例ではそれをご理解頂きたかったわけです。ポリシー(ストラテジー)パターンは、ポリモーフィズムのユースケースのごく一部に過ぎませんが、疎結合化というポリモーフィズムの本旨に沿っており、かつ、利用価値も高いものだと思います。
杉本 啓 @sugimoto_kei 経営管理基盤ソフトウェア fusion_place の、プログラマ兼設計者 http://www.fusions.co.jp
[脚注]
[1] 昔からある「ユーザ出口ルーチン」などはこの例でしょう。 [2] 料金計算など区分値に依存するメソッドを区分値クラスに移すことと、その計算にポリモーフィズムを適用することは別問題です。例えば本書p.60/位置No.1062のFeeTypeクラスは以下のように記述することもできます(label()に関する実装は省略)。
enum FeeType { ADULT, CHILD, SENIOR; Yen yen() { switch (this){ case ADULT: return adultFee(); case CHILD: return childFee(); case SENIOR: return seniorFee(); default: throw new UnsupportedOperationException("Fee for fee type [" + this + "] not defined."); } } private Yen adultFee() { return new Yen(100); } private Yen childFee() { return new Yen(50); } private Yen seniorFee() { return new Yen(80); } }
p.60/位置No.1062のコードとこのコードを比べてみると、料金計算方法という知識がFeeTypeクラスに隠蔽されている点は同じで、大人・子供・シニアの料金計算がそれぞれ別のメソッドに分離されている���も同じです。違いは、条件分岐に switch文を使うかポリモーフィズムを使うかという点だけです。 こうしたメソッドが複数あるならば、それぞれの実装でswitchを書くより、ポリモーフィズムを用いるべきでしょう。上記の例のようにswitchが1箇所だけであれば、いずれにするかは、設計の良否というより多分に好みの問題と、私は思います。
なお、このケースではそもそもポリモーフィズムを使うほどのこともなく、以下のコードで十分です。
enum FeeType { ADULT(100), CHILD(50), SENIOR(80); private final Yen yen; private FeeType(int yenAsInt) { this.yen = new Yen(yenAsInt); } Yen yen() { return yen; } }
ただ、これは、単にサンプルコードの選択に関する趣味の問題であって、区分値ポリモーフィズムの妥当性という論点には影響しません。
[3] ここで示したコード例は、値引き制度の間に相互依存関係がないという想定にもとづいています。依存関係があり得る場合(例えば上得意先向け値引きを適用したときにはキャンペーン値引きは適用しないといった条件に対応する場合)、DiscountPolicyにDiscountのリストを渡し、各ポリシーがそのリストに対してDiscountを除去したり追加したりできるようにする方がよいかもしれません。
interface DiscountPolicy { /** * 与えられた salesOrder に対する値引きを計算し、その結果を DiscountのListに追加する。 * リストから既存のDiscountを除去しても構わない。 */ void updateDiscounts(SalesOrder salesOrder, List<Discount> discounts); }
この場合、Discountに、値引き制度を識別するためのDiscountTypeなどを保持させることも必要でしょう。
値引き計算といった機能をこのように疎結合化するにあたり、疎結合化されたモジュール間の役割分担をどのように設計するかは極めて重要です。あるいはそれこそがオブジェクト指向にもとづくアプリケーション設計の核心部分かもしれません。 「オブジェクト指向」という言葉を作ったアラン・ケイは「オブジェクトよりもメッセージング」と強調したメールの中で、以下のように言っています:
The key in making great and growable systems is much more to design how its modules communicate rather than what their internal properties and behaviors should be.
(拙訳)偉大で成長可能なシステムを作る上でのカギは、個々のモジュールの内部特性や振る舞いがどうあるべきかということにではなく、むしろ、モジュールたちがどのようにコミュニケ―トするかをデザインすることにこそある。
また、「マルチパラダイムデザイン」で、ジム・コプリエンが提示した「共通性/可変性分析」も、こういった相互作用に重点を置いた設計手法です。
6 notes
·
View notes
Text
「現場で役立つシステム設計の原則」批判 (1) ~何のために、「データとロジックを一体に」するのか?~
増田亨氏の「現場で役立つシステム設計の原則]」の評判が高いようです。
この本が、オブジェクト指向の初級者に受け入れられ易いことはわかります。オブジェクト指向的なプログラミングが出来ていない現場で、明日からでも出来そうなことが平易に書かれているからです。オブジェクト指向の入り口を指し示しているように見える。
一方で、私としては、この本が指し示す入り口は、入りやすいかもしれないけれど、結局はどこにも通じていないのではないかと疑っています。
本稿では、そのように私が考える理由について、3つの切り口からご説明したいと考えています:
何のために、「データとロジックを一体に」するのか?
ポリモーフィズムは何のために?
「ドメインモデル」とは何か?
長くなるので、今回は、一番目の「何のために、『データとロジックを一体に』するのか?」について。
批判 (1) 何のために、「データとロジックを一体に」するのか?
本書はデータとロジックを一体にすることを強く主張しています(p.77/位置No.1294等)。データとロジックを一体にすることは一般には「カプセル化」と呼ばれ、オブジェクト指向の特長のひとつとされていますから、こうした主張自体は突飛なものではありません。
問題は、本書の場合、カプセル化することが自己目的化していて、何のためにカプセル化するのかという視点が極めて希薄なことです。その結果、「うまいカプセル化」「まずいカプセル化」の区別がない、という状況に陥っています。
「データとロジックを一体に」している例
データとロジックを一体にするという主張を端的に実践している例として、本書p.37/位置No.759最下部に掲載されているコード例(改善後)を取り上げてみましょう:
Money amount(Money unitPrice, Quantity quantity) { if(quantity.isDiscountable()) return discount(unitPrice, quantity); return unitPrice.multiply(quantity.value()); }
これに対して改善前のコード例(p.37中ほど、位置No.738)は以下の通りです:
int amount(int unitPrice, int quantity) { if(quantity >= discountCriteria) return discount(unitPrice, quantity); return unitPrice * quantity; }
本書のこの箇所での主張は「「型」を使ってコードをわかりやすく安全にする」ということですから、改善のポイントはintで表現されていた金額、単価、数量にそれぞれ適切な型を付与したことですが、いま注目したいのはその点ではありません。
改善後のコードではQuantityクラスの導入に伴いisDiscountable()というメソッドが設けられ、そこに、値引き条件判定のロジックが移されています。これは、「データとロジックを一体に」という本書の一貫した主張に従っており、良い例として提示されていることはあきらかです。
私が提起したい論点は、これが本当に良い例なのかということです。
isDiscountable()メソッドを設けることによって、値引き可否判定ロジックは、それが用いる「数量」という変数とともに、Quantityクラスに閉じ込められます。これは確かにカプセル化ではあります。しかし本当にこれは妥当な設計なのでしょうか。どういう点でこのカプセル化は有用なのでしょうか。
情報隠蔽
その問いに答える準備として、一歩下がってカプセル化の意義について確認しておきましょう。
一般にカプセル化の目的は「情報隠蔽」にあると言われます。両者をあえて区別せず、カプセル化とは情報隠蔽のことだと考える論者もいるようです。この「情報隠蔽」という概念は1972年にデイビッド・パーナスにより提唱されました。その論文「システムをモジュールに分割する際に用いられるべき基準について」はWebで閲覧できます。
この論文でパーナスが言っている隠すべき情報とは、インスタンス変数のような具体的な「データ」のことではなく「設計上の意思決定に関する知識」です(同論文p.1056「The Criteria」)。情報隠蔽というより知識の隠蔽なのですね。
例として、スタックを表すオブジェクトを書くことを考えましょう。
スタックは、複数のデータを順に入れる(pushする)ことができ、入れた順の逆順にデータを取り出す(popする)ことが出来る、いわゆる「後入先出方式」のデータ保管容器です。
スタック内部でデータを実際に保持するためのデータ構造としては、配列や連結リストなどの選択肢があり得ますが、スタックに対する操作をpush()とpop()に限定しておけば、内部構造としてどれをとっても、あるいは、ある日思い立って配列から連結リストに切り替えても、スタックを使用している側のコードには影響を与えません。
情報隠蔽とはこのようなものです。
Quantity はどんな知識を隠蔽しているか
さて、情報隠蔽についておさらいをした目で、QuantityクラスのisDiscountable()メソッドを眺めてみましょう。これはどんな知識を隠蔽しているのでしょうか。
数量がいくつであれば値引き可となるのかという知識は隠蔽されています。元の式、quantity >= discountCriteriaでは、その点は顕わでしたが、isDiscountable()では隠されています。
一方で、このメソッドを設けたことで、「値引きは数量のみに基づいて行われる」という知識が開示されてしまっています。
値引の可否はたぶん受注入力画面上に表示したいでしょう。であるならば、そ��画面を表示するためのクラスのいずれかの箇所で以下のようなコードを書くことになるでしょう:
Quantity quantity = Quantity.valueOf( /* 数量フィールドの入力値 */input ); if (quantity.isDiscountable()) { // "値引き適用"と画面に表示 } else { // "値引き適用なし"と画面に表示 }
取引条件が変更され、数量だけでなく金額も含めて値引き適用可否を決める、となった場合、どうなるでしょうか。もとのamount()メソッドだけでなく、この受注画面のコードも変更しなくてはならなくなります。影響箇所の拡散です。
本書では、「データとロジックを一体にする」ことの目的として「業務ロジックを重複させない」ということを強調しています���(p.77/位置No.1294)、このケースではむしろ逆の結果をもたらしています。
開示してよい知識/隠蔽したい知識
上記のコードが問題を含んでいる原因は、このコードが「値引きは数量のみに基づいて行われる」という知識を適切に隠蔽していないことにあるわけです。
本来は、値引き判定のロジックをどのオブジェクトに配するかを決めるにあたって、どのような知識を隠蔽すべきか、あるいは裏返して言えば、どのような知識は開示して構わないかという点に思いをめぐらすべきでした。
値引き条件などというものは、ビジネス上の都合により変更されやすいものです。このケースのように注文数量だけで値引き可否が決まるというケースもあるかもしれませんが、発注金額も考慮し、あるいは発注者が上得意かどうかも判断要素に含める、というように変更されるかもしれません。一方で、注文数量・金額・発注者が誰かなども含む受注内容に応じて値引き可否が決まる、という点はたぶん変わらないだろうと考えられます。
であれば、このケースで開示してよい知識と隠蔽したい知識とは以下のようになるでしょう:
開示してよい知識
受注ごとにその内容に応じて値引き可否が決まるという知識。
隠蔽したい知識
注文数量・金額等にもとづく具体的な値引き決定ルール。
これを踏まえれば、isDiscountable()は、Quantityクラスにではなく、呼び出し元であるamount()メソッドを含むSalesOrder(受注)といったクラスのメソッドであるべきだということになります:
class SalesOrder { Money unitPrice; Quantity quantity; // ... Money amount() { if (isDiscountable()) return discount(unitPrice, quantity); return unitPrice.multiply(quantity); } // ... boolean isDiscountable() { return quantity.compareTo(discountCriteria) >= 0; } }
これに伴い、受注画面のコードは以下のようになります:
salesOrder.setQuantity(Quantity.valueOf( /* 数量フィールドの入力値 */input )); if (salesOrder.isDiscountable()) { // "値引き適用"と画面に表示 } else { // "値引き適用なし"と画面に表示 }
このコードであれば、上述のように取引条件が変更された場合も、SalesOrderクラスのisDiscountable()メソッドだけ修正すれば済むでしょう。値引き決定ルールが、SalesOrderクラスに完全に隠蔽されているからです。
ルール信奉の落とし穴
本書の帯には「トラブルを起こさないためにどう考え、何をすべきか?」と大書され、トラブルの例のひとつとして「1つの修正のために、あっちもこっちも書きなおす必要がある」という事象が挙げられています。
ここまで読まれた方には、「データとロジックを一体に」という単純なルールに従うだけでは、こうしたトラブルを避けることが出来ず、むしろ助長してしまう可能性すらあるということが、ご理解頂けたかと思います。
「データとロジックを一体に」というようなルールは白黒がつけやすく、それに従ってコードを作り替えていくことにはパズルのような楽しさがあるでしょう。でもそれはパズルであって設計ではありません。
本書で、このようなルール信奉は随所に見られます。例えば、本書の末尾近くではThoughtWorksアンソロジーの「オブジェクト指向エクササイズ」が推奨されています。このエクササイズは、いくつかの単純なルールに従ってコードを修正していくことで、オブジェクト指向らしい設計に馴染もうという試みです。私もこのような試みには意味があると思います。
しかし、それはあくまで「エクササイズ」として、なのです。原書では「Object Calisthenics」、直訳すれば「オブジェクト健康体操」とされています。本番の試合前に体を柔軟にしておこうということなのです。本番の試合中に健康体操をする人はいないでしょう。一方本書では、そうした位置づけが不明瞭で、こうした単純なルール集を、本番コードにも適用しようとしているように見えます。
オブジェクト指向への入り口という位置づけでなら、本書のようなパズル的なやり方もアリ、とお考えになる方もあるかもしれません。その方には、上述のisDiscountable()のように極めて簡単な例においてさえ、本書の推奨するアプローチが破綻していることを思い起こして頂きたい。初級者用のガイドラインとしても失敗しているのです。しかも実践者である著者ご自身、たぶん本ケースを、良くない設計の例と見ておられない。ルールに従ってパズルを解くとそれだけで達成感が得られるので、ルールを超える視点は頭の隅に追いやられてしまうという傾向が生じるのかもしれません。
設計の原則に立ち帰る
解決策は、「データとロジックを一体に」という、どちらかというとゲームのルールのような具体的で単純なルールから視点を引き上げ、「情報隠蔽(=知識隠蔽)」のような、より本質的な、目的志向的な設計原則に立ち帰って考えることです。
単純なルールを適用してコードを気軽に変更してみるのは大変よいことです。ただし、その結果が適切であったかは、必ず、より高い視点から評価されなければなりません。その視点のひとつが「情報隠蔽」です。単純なルールは、コード変更のきっかけにはなり得てもその正当性を保証しないのです。
一方、情報隠蔽といった抽象的な原則に従うには、相応の思考が要求されます。隠蔽されるべき知識が何か、というのは頭のひねりどころです。「値引って、数量だけでなく金額が関係してくる可能性もあるよなあ。お得意様なら条件が違うかも」といった良識あるいはドメイン知識も必要です。こうした知識は必ずしも「難しい」ものではありませんが、パズルのルールで代替できるものでもありません。
しかし、与件として与えられた問題を一定のルールの枠内で解くだけではなく、この場で何を隠蔽し何を開示すべきかといった新たな問いかけを生み出すことも含めて、問題と解の関係性を、描いては消し描いては消ししながら重層的に深化させていくことが、そもそも、設計という活動の本来の姿なのではないでしょうか。
杉本 啓 @sugimoto_kei 経営管理基盤ソフトウェア fusion_place の、プログラマ兼設計者 http://www.fusions.co.jp
追記:これはサンプルコードが悪いのか?
このエントリへの批判として、これは単に取り上げたサンプルコードが悪かったのでは、というものが散見されます。 たまたま出来が悪かった部分を、重箱の隅をつつくように批判してもしようがない、ということかと思います。
私は、それはあたらないと思っています。 私がこのサンプルコードを取り上げたのは、これが増田氏の目からみてもご自身の考えをよく体現したコードだろうと考えたからです。
このサンプルコードが記載されているページ(p.37/位置No.759)で増田氏が実演されているコード改善(リファクタリング)は、以下のように2ステップに分解することが出来ます:
ステップ1: isDiscountable(Quantity) というメソッドを抽出
ステップ2: 同メソッドを Quantity クラスに移動
ここで、2ステップ目を実行しなければ、私からの批判は成り立ちません。 それなのにあえてステップ2を実行されたのは、この本の以下のような主張に沿うならばそれが必然だからだと思います:
業務ロジックをデータを持つクラスに移動する(p.79)
使う側のクラスに業務ロジックを置き始めたら設計を見直す(p.80)
メソッドは必ずインスタンス変数を使う(p.81)
例えば、ステップ1の段階で止めると、isDiscountable(Quantity)は引数しか使わないことになり、上記3点目の規則に明らかに反するのです。 これとは別に、Quantityなど値オブジェクトに業務ロジックを持たせることはこの本の随所で推奨されています(例えば、p.86-87)。 これは、この本の一貫した主張である「データとロジックを一体に」の具体的な適用ケースです(p.87)
こうしたことを考えれば、このコード例が書き損じや考慮不足ではなく、増田氏の視点からみて良く練られたものであることをご理解頂けると思います。
8 notes
·
View notes
Text
杉本 啓「2つのドメインモデル―DDDの含意」
Kei Sugimoto, "Two Domain Models - An Implication of DDD", PHP Mentors, (December 20, 2015)
ドメイン駆動設計(Domain-Driven Design: DDD)は、オブジェクト指向、デザインパターン、リファクタリングなど、ソフトウェア設計分野における幅広い知的遺産と交差します。それゆえ、ドメイン駆動設計は、ドメイン指向のアプリケーションソフトウェア開発に馴染むような仕方でこれらの知識資産を統合したものと見ることができます。
こうした見方は妥当でかつ生産的でもありますが、DDDの業績に対しては別の見方もあり得ます。それがこの短い論考のテーマです。
この文脈において、DDDは、「ソフトウェア設計」から、私たちが特別な思い入れを込めて「情報設計」と呼ぶものへの静かな出発として考えることができるかもしれません。
ドメインモデルとは何か
とはいえ、まずは入口から始めましょう。私たちは1つの基本的な質問について再考しなければいけません:ドメインモデルとは何でしょうか?
ばかばかしい。ドメインモデルはドメインのモデルです。そうではありませんか?
具体的な例を用いて検討しましょう。バージョン管理システム(Version Control System: VCS)です。現在私たちは束にできるほどたくさんのVCSを持っていて、そのいずれもが他といくらか異なるモデルを提供しています。例えば、Subversionでは「コミット」は中央の(かつ単独の)リポジトリへのソースコードの変更の登録を意味するのに対して、Gitでは同じ言葉が分散された一群のリポジトリの特定のメンバーへのソースコードの変更の登録を意味します。「コミット」はそれぞれのVCSのユビキタス言語の語彙に含まれます。そして、そうであるなら、この語の意味のこうした違いは、その2つのVCSの基礎を成すドメインモデルの違いを反映しているのに他ならないでしょう。
この例から、これらの2つのモデル間の違いは、同一のドメインの問題を解決するためのアプローチの違いに由来することが分かります。ドメインモデルはドメイン自体のモデルではありません。そうではなく、実のところそれは、そのドメインのためのソリューションのモデルです。それぞれのVCSが誕生して初めて、それぞれのモデルが認識されるようになったのです。
このアイデアは全く単純明快です。別の例として、スプレッドシートソフトを見てみましょう。各種のスプレッドシートソフトは、シート、セル、数式、等々で構成される独特なドメインモデルを共有しています。このドメインモデルは VisiCalc とともに誕生し、その後進化してきたものです。私たちは会計の計算のためにスプレッドシートソフトを使うかもしれませんが、会計ドメインが、その歴史においてスプレッドシートの概念を持っていたわけではありません。そうではなく、スプレッドシートの概念やモデルは、幅広いドメインの問題を解決するために設計され、そのドメインが会計の一部をたまたま含んでいた、ということなのです。
2つのドメインモデル
伝統的に、ドメインモデルは、「分析モデル」と見られる傾向にあります。すなわち、設計ではなく分析の対象です。しかしながら、ドメインモデルはソリューションのモデルであるという私たちの新しいビジョンは、この見方に異議を唱えます。「ソリューション」が何であれ、それは私たちが設計するものです。折に触れて私たちが既存のソリューションの設計を分析するにしても。
「設計」は、以前に存在しなかったものを創造するのに対して、「分析」は、それがすでに存在する物事を扱うという点で、いくぶん受動的な言葉です。伝統的なOOアプローチにおいては、分析モデルは、既に存在し、当然の帰結として設計の対象ではない現実世界の抽象として見られる一方、私たちが(エンジニアとして)構築するソフトウェアの機能と構造を決定することを「設計」と呼ぶ傾向があります。エンジニアリング視点からは、この分割はとても自然にみえます。
ここでのポイントは、ドメイン駆動設計者の視点からは、「現実世界」は2つの下位要素―物理的な要素と情報の要素―を含むであろう、ということです。
物理的な世界にあっては、人々が働き、モノが動き、お金が稼がれます。それらは私たちが「ビジネス」という言葉によって想像するものです。ビジネスを進めるために、注文、請求、在庫記録、財務諸表、等々といった情報を私たちは記録し交換します。これは現実世界のもう1つの構成要素―情報処理―です。
したがって、ドメインモデルは2つの異なったモデルに分割されます。物理世界のモデルと情報のモデルです。
このような世界観のもとでは、物理世界のモデルはたいてい単に与えられ我々にとって制御できないものであるのに対して、情報のモデルの大部分は我々が設計し得る、と私たちは考えるでしょう。
2つのドメインモデルとOOA
DDD本において、エヴァンスは初期時点での表面的なモデルと深いモデルについて繰り返し語ります。コンテナ輸送アプリケーションからの彼の事例では、表面的なモデルが船舶(Ships)やコンテナ(Containers)のような物理的な実体を含んでいるのに対して、深いモデルは本船名/航海番号(Vessel Voyages)や船荷証券(Bills of Lading)(例えば p.191 「深いモデル」)のような抽象概念を含んでいます。これらのモデルの間の区別は、少なくとも部分的には「深さ」の違いによるのではなく、「対象」の違い、すなわち、対象が物理的なものか情報か、によるのです。
エヴァンスは、伝統的なOOAがこの区別を無視する様子に関して、示唆的な話を語ります。
新しいオブジェクトモデラがプロジェクトに参加すると、決まって最初に提案するものがあった。それが、あるはずのクラス、船舶とコンテナである。彼らが聡明でなかったわけではなく、ただ、深いモデルを発見するためのプロセスを経験していなかっただけなのだ。 (p.192)
2つのドメインモデルという着想に照らして、私たちは、これは「ただ、深いモデルを発見するためのプロセスを経験していなかっただけなのだ」ということだけによるのではなく、OOAの知識体系(または少なくともその一般的に受け入れられた理解)が物理的な世界のモデルと情報のモデルの区別を欠いているからであろう、と理解することができます。
しかしながら、この区別が無視されているといって驚くには当たりません。なぜなら、この2つのモデルの区別は常に明らかであるとは限らないからです。というのも、情報は、私たちの世界のあらゆる領域に浸潤してしまっており、あたかも物理的存在であるかのような顔をしてまかり通っているからです。小切手は物理的でしょうか?あるいは情報でしょうか?私たちはどのように真に物理的なものと小切手を区別するのでしょうか?あるいは、そもそも区別しなければならないのでしょうか?この点について私は明快な答えを持っていません。多くの場合、あなたは(まるでそれらが物理的であるかのように)情報モデルから安全に小切手を除外することができますが、その一方で、(紙幣、硬貨、小切手をひっくるめて呼ぶ)「現金有高(げんきんありだか)」をモデルに含めなければならないかもしれません。しかし、これは金科玉条ではありません。私たちは自身の判断力を用いなければなりません。とはいえ、物理的なモデルと情報のモデルの間に線を引くことは、依然としてモデリングにおける洞察をもたらすことができるでしょう。なぜなら、最終的に私たちがソフトウェアによって表現しなければならないものは、物理的なビジネスのモデルではなく情報のモデルだからです。
DDDの含意
ユビキタス言語とモデル駆動設計はDDDの中核を成す2つのプラクティスです。2つのドメインモデルに照らすと、ユビキタス言語は、物理的な世界のモデルから情報のモデルを切り離すための、そして後者を分析ではなく設計の対象とするための仕掛けとして理解することができるでしょう。それは、私たち開発者の領域を広げます。そしてモデル駆動設計は、ソフトウェアの構造が情報のモデルを正確に反映している状態を保ち続けることを私たちに要請します。それは、私たちの職業がアナリストと開発者に分解することを防ぎます。
かくして、情報モデルの設計は私たちの手の内にあります。私たちの情報技術の時代以前においてさえ、情報モデルは設計されてきました。複式簿記は、それらのモデルの注目に値する実例です。列車運行図表(ダイヤグラム)や楽譜はその他の例です。これらのモデルは、「ビジネス側」の人々によって設計されてきました(その頃「IT側」は存在しなかったので)。対照的に、私たちのIT時代において、情報モデルを設計する責任は宙に浮いているかのようです。私たちのITは、ビジネス側の人々にとってとても付き合いにくいものです。それゆえ、彼らは、ITを使って実装される情報モデルの設計にしばしば苦痛を感じさせられてきました。アナリストが設計の責任を担うべきなのかもしれません。しかし、彼らが行うのは設計ではなく分析です。その上、実装がもたらすであろうフィードバックを受けることができない中で設計するのは困難です(これもまたモデル駆動設計が重視する点です)。私たち開発者は、この責任を引き受けるベストな位置にいるのです。もちろん、ビジネス側の人々との緊密な協力関係を前提として。
ならば今、私たちの職業の本質的な営為のひとつは、ソフトウェア設計に裏打ちされた情報設計であるわけです。 これがDDDの含意です。
杉本 啓 @sugimoto_kei 経営管理基盤ソフトウェア fusion_place の、プログラマ兼設計者 http://www.fusions.co.jp
11 notes
·
View notes
Text
Practical Symfony #28: Workflowerを使ったビジネスプロセスの管理
この記事はSymfony Advent Calendar 2015 25日目の記事です。前日の記事はqcmatsuokaさんの「SpBowerBundleのキャッシュエラーを解決する | QUARTETCOM TECH BLOG」でした。
WorkflowerはBPMN 2.0に準拠するPHP向けのワークフローエンジンであり、2条項BSDライセンスの下でリリースされているオープンソース製品です。Workflowerの主な用途としては、人間を中心としたビジネスプロセスをPHPアプリケーションで管理することが挙げられます。
この記事では、Workflowerを使ったビジネスプロセスの管理をSymfonyアプリケーション上で行うために必要な作業について示します。
PHPMentorsWorkflowerBundleによるSymfonyインテグレーション
PHPMentorsWorkflowerBundleはWorkflowerをSymfonyアプリケーションで使うためのインテグレーションレイヤーで、以下の機能を提供します。
ワークフローに対応するDIコンテナサービスの自動生成とphpmentors_workflower.process_awareタグを使ったサービスオブジェクトの自動注入
Symfonyセキュリティシステムを使った業務担当者(パーティシパント)の割り当てとアクセス制御
Doctrine ORMを使ったエンティティのための透過的なシリアライゼーション・デシリアライゼーション
複数のワークフローコンテキスト(BPMNファイルが保存されたディレクトリ)
WorkflowerおよびPHPMentorsWorkflowerBundleのインストール
最初に、Composerを使ってWorkflowerおよびPHPMentorsWorkflowerBundleをプロジェクトの依存パッケージとしてインストールします。
$ composer require phpmentors/workflower "1.0.*" $ composer require phpmentors/workflower-bundle "1.0.*"
次に、PHPMentorsWorkflowerBundleを有効にするためにAppkernelを変更します。
... class AppKernel extends Kernel { public function registerBundles() { $bundles = array( ... new PHPMentors\WorkflowerBundle\PHPMentorsWorkflowerBundle(), ); ...
コンフィギュレーション
続いてPHPMentorsWorkflowerBundleのコンフィギュレーションを行います。以下に例を示します。
app/config/config.yml:
phpmentors_workflower: serializer_service: phpmentors_workflower.base64_php_workflow_serializer workflow_contexts: app: definition_dir: "%kernel.root_dir%/../src/AppBundle/Resources/config/workflower"
serializer_service - ワークフローのインスタンスとなるPHPMentors\Workflower\Workflow\Workflowオブジェクトのシリアライズに使用するDIコンテナサービスのIDを指定します。指定されたサービスにはPHPMentors\Workflower\Persistence\WorkflowSerializerInterfaceの実装が期待されています。デフォルトはphpmentors_workflower.php_workflow_serializerです。また、シリアライズ済みのオブジェクトをBase64エンコード・デコードするphpmentors_workflower.base64_php_workflow_serializerを使うこともできます。
workflow_contexts - ワークフローのコンテキストID毎にdefinition_dir(BPMNファイルが保存されたディレクトリ)を指定します。
BPMNを使ったワークフローの設計
BPMN 2.0をサポートするエディターを使ってWorkflowerで動作させるワークフローを定義します。最初は開始イベント、タスク、終了イベントのみで構成されたワークフローを定義し、それでワークフローの開始から終了までの動作が確認できたら、改めてワークフロー全体を設計し、定義するとよいでしょう。このBPMNファイルの名前はワークフローID(workflow ID)として使われます。例えばLoanRequestProcess.bpmnのような名前(キャメルケースを推奨)で保存します。分岐に使うシーケンスフローの条件式はSymfony ExpressionLanguageコンポーネントの式として評価されます。シーケンスフローの評価順は不定であるため、他の分岐先と整合的な条件式を設定する必要があることに注意してください。条件式の中では、PHPMentors\Workflower\Process\ProcessContextInterface::getProcessData()から取得される連想配列のキーを使うことができます。
以下のスクリーンショットはEclipseで利用可能なBPMNエディターであるBPMN2 Modelerのものです。
Workflowerがサポートするワークフロー要素
WorkflowerはBPMN 2.0のワークフロー要素のうち以下のものをサポートしています。サポート外の要素はWorkflowerで動作させることができませんのでご注意ください。
接続オブジェクト(connecting objects)
シーケンスフロー(sequence flows)
フローオブジェクト(flow objects)
アクティビティ(activities)
タスク(tasks)
イベント(events)
開始イベント(start events)
終了イベント(end events)
ゲートウェイ(gateways)
排他ゲートウェイ(exclusive gateways)
Note これらの要素はBPMNによるワークフロー定義を通してクライアントと共有されるワークフロードメインモデルを構成します。このドメインモデルはドメイン駆動設計(Domain-Driven Design: DDD)が提唱するユビキタス言語(Ubiquitous Language)の語彙となるものです。このようなドメインモデルを筆者はユビキタスドメインモデル(Ubiquitous Domain Model)と呼んでいます。ドメイン特化言語(Domain-Specific Language: DSL)はドメインとそのクライアントの間でユビキタスドメインモデルを媒介する役割を担っているといえるでしょう。
ワークフローのインスタンスを表すエンティティの設計
特定のワークフローのインスタンス(Workflowerではプロセスと呼ばれています)を表す永続化対象のエンティティを設計し、アプリケーションに追加します。このエンティティは通常PHPMentors\Workflower\Process\ProcessContextInterfaceとPHPMentors\Workflower\Persistence\WorkflowSerializableInterfaceを実装します。PHPMentors\Workflower\Process\ProcessContextInterface::getProcessData()から返される連想配列は、シーケンスフローの条件式内で展開されます。
また、アプリケーションにおける必要性(例:データベースへの問い合わせ)に応じてWorkflowオブジェクトのプロパティのスナップショットを保持するプロパティを用意するとよいでしょう。例えば、アプリケーションで特定のアクティビティに留まるプロセスをデータベースから検索する必要がある場合、現在のアクティビティを表す$currentActivityプロパティをエンティティに追加します。以下に例を示します。
... use PHPMentors\Workflower\Persistence\WorkflowSerializableInterface; use PHPMentors\Workflower\Process\ProcessContextInterface; use PHPMentors\Workflower\Workflow\Workflow; ... class LoanRequestProcess implements ProcessContextInterface, WorkflowSerializableInterface { ... /** * @var Workflow */ private $workflow; /** * @var string * * @Column(type="blob", name="serialized_workflow") */ private $serializedWorkflow; ... /** * {@inheritdoc} */ public function getProcessData() { return array( 'foo' => $this->foo, 'bar' => $this->bar, ... ); } /** * {@inheritdoc} */ public function setWorkflow(Workflow $workflow) { $this->workflow = $workflow; } /** * {@inheritdoc} */ public function getWorkflow() { return $this->workflow; } /** * {@inheritdoc} */ public function setSerializedWorkflow($workflow) { $this->serializedWorkflow = $workflow; } /** * {@inheritdoc} */ public function getSerializedWorkflow() { if (is_resource($this->serializedWorkflow)) { return stream_get_contents($this->serializedWorkflow, -1, 0); } else { return $this->serializedWorkflow; } } ...
ビジネスプロセスの管理のためのドメインサービスの設計
Workflowerをプロダクションレベルで使うためには、プロセスの開始やワークアイテムの割り当て・開始・完了等を担当するドメインサービスが必要になるでしょう。特定のワークフローのプロセスとドメインサービスを結びつけるためにはPHPMentors\Workflower\Process\ProcessAwareInterfaceを実装し、そのドメインサービスのDIコンテナサービスに対して、phpmentors_workflower.process_awareタグを付与します。以下に例を示します。
... use PHPMentors\DomainKata\Usecase\CommandUsecaseInterface; use PHPMentors\Workflower\Process\Process; use PHPMentors\Workflower\Process\ProcessAwareInterface; use PHPMentors\Workflower\Process\WorkItemContextInterface; use PHPMentors\Workflower\Workflow\Activity\ActivityInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; ... class LoanRequestProcessCompletionUsecase implements CommandUsecaseInterface, ProcessAwareInterface { ... /** * @var Process */ private $process; ... /** * {@inheritdoc} */ public function setProcess(Process $process) { $this->process = $process; } ... /** * {@inheritdoc} */ public function run(EntityInterface $entity) { assert($entity instanceof WorkItemContextInterface); $this->process->completeWorkItem($entity); ...
対応するサービス定義は以下のようになります。
... app.loan_request_process_completion_usecase: class: "%app.loan_request_process_completion_usecase.class%" tags: - { name: phpmentors_workflower.process_aware, workflow: LoanRequestProcess, context: app } ...
この例のような「プロセスと操作の組み合わせ」に���応したユースケースクラスの実装は基本的ものといえますが、プロセス操作の共通性と可変性を分析し、可変性を外部に切り出すことができれば単一のクラスに操作をまとめることもできるでしょう。
ビジネスプロセスの管理
最後に、プロセスの開始やワークアイテムの割り当て・開始・完了等を実行するためのクライアント(コントローラー、コマンド、イベントリスナー等)を実装する必要があります。これらのクライアントも可変性を外部に切り出すことができるはずです。
ここまでの作業が終われば、Webインターフェイスやコマンドラインインターフェイス(Command Line Interface: CLI)からビジネスプロセスに対する一連の操作を実行できるようになります。
BPMSによるジェネレーティブプログラミングの実現に向けて
この記事では、Workflowerを使ったビジネスプロセスの管理をSymfonyアプリケーション上で行うために必要な作業について見てきました。WorkflowerおよびPHPMentorsWorkflowerBundleが提供するのはBPMN 2.0のワークフロー要素に対応するWorkflowドメインモデルと基本的なインテグレーションレイヤーに留まるため、実際にアプリケーションに組み込むためにはさらなる作業(とスキル)が要求されるでしょう。それは決して簡単なことではありません。なぜなら、それは対象ドメインに適したBPMS(Business Process Management System)あるいはBPMSフレームワークの設計に他ならないからです。
また、BPMSによるソフトウェア開発はビジネスプロセスドメインにおけるジェネレーティブプログラミング(Generative Programming)の実践といえます。PHPで利用可能なBPMSがほとんど存在しない現在、これに挑戦する者だけがその果実を手にすることができるのです。
参考
phpmentors-jp/workflower-bundle
phpmentors-jp/workflower
メインページ - Q-BPM
0 notes
Text
Two Domain Models - An Implication of DDD
Domain-Driven Design (DDD) intersects with a broad range of the intellectual heritage in the software design discipline including object-orientation, design patterns and refactoring. And as such, it can be viewed as a synthesis of these knowledge assets tailored towards domain-oriented application software development.
Although this view is valid and productive, there could be another way of viewing accomplishment of DDD. It is the theme of this short discussion.
In this context, DDD could be thought as a quiet departure from "software design" to something we may call "information design" with a special emotional attachment.
What is a Domain Model?
But first thing first. We must have a rethink on one basic question. What is a domain model?
It sounds ridiculous. A domain model is a model of a domain. Isn't it?
Let us discuss about this on a concrete example: version control systems (VCSs). We now have a bunch of VCSs, each of which offers a somewhat different model than another. For instance, with Subversion, "commit" means a registration of source code changes to the central (and solitary) repository, whereas, with Git, the same word means a registration of source code changes to a particular member of a set of distributed repositories. "Commit" is in the vocabulary of ubiquitous language of each VCS. And if so, this difference in the meaning should be the reflection of a difference of the underlying domain models of the two VCSs.
From this example, we can see that the difference between these two models comes from the difference in approach to solve problems in the same domain. A domain model is not a model of a domain itself. Instead, it actually is a model of a solution for the domain. It was not before each VCS was given birth that each model was recognized.
This idea is quite simple and straightforward. Let us take spreadsheet software as another example. They share a unique domain model consisting of sheets, cells, formula and so on. This domain model did not exist before the birth of VisiCalc and evolved after that. Although we may use spreadsheets for our accounting calculation, it is not that accounting domain had the spreadsheet concept in its history, but the concept and model were engineered to solve problems in a broad domain, which incidentally includes a part of accounting.
Two Domain Models
Traditionally, domain models tend to be seen as "analysis models", which are subjects of analysis, not design. However, our new vision that a domain model is a model of a solution challenges this view. A solution is something we design, even if we analyze designs of the existing solutions from time to time.
"Analysis" is rather a passive word in that it treats things which already exist, while a design creates something that did not exist before. In traditional OO approaches, an analysis model tend to be viewed as an abstraction of the real world, which already exists and naturally is not a subject of design, while a design governs the functionalities and the structures of software we (as engineers) build. From the engineering perspective, this separation looks very natural.
The point here is that from a domain-driven designer's perspective, a "real world" would consist of two subcomponents - physical and informational.
In the physical world, people work, things move, and money is earned. They are what we imagine by the word "business". To let businesses move on, we record and exchange information - orders, invoices, inventory records, financial statements, and so on. This is another component of the real world - information processing.
Accordingly, a domain model is divided into two distinct models: physical world models and informational models.
With such a world view, we would consider the physical world models as usually just given and uncontrollable for us, while we can design most parts of the informational models.
Two Domain Models and OOA
In his DDD book, Evans repeatedly speaks about initial superficial models and deep models. In his example from a container shipping application, the superficial model contains physical entities like Ships and Containers, while the deep model contains such abstract concepts as Vessel Voyages and Bills of Lading (e.g. p.189 "Deep Models"). The distinction between these models is at least partially not of the "depth" but of the "subject" of each model - physical and informational.
Evans tells a story which is suggestive on how traditional OOA ignores this distinction:
Whenever new object modelers showed up on the project, what was their first suggestion? The missing classes: ship and container. They were smart people. They just hadn't gone through the processes of discovery. (p.190)
With the idea of two domain models, we understand this would not have been only because "they just hadn't gone through the processes of discovery", but also because the body of knowledge of OOA (or at least its generally accepted understanding) has lacked the distinction of physical world models and informational models.
However, this ignorance is not surprising, because the distinction between the two models is not always apparent. It is because information has infiltrated into every corner of our world and passes as if they were physical. Are bank checks physical or informational? How do we distinguish them from truly physical objects? Or, do we have to do so at all? I have no definitive answer here. In many cases you can safely omit checks from your informational models (as if they were physical), while you might have to include "cash in hand" in your models (which refers to bank bills, coins, and checks, collectively) instead. But it is not a dogma. We have to use our judgment. Nevertheless, drawing a line between physical and informational models could still bring insights in modeling, because in the end what we have to represent with our software are informational models, not physical business models.
An Implication of DDD
Ubiquitous language and model-driven design are two practices at the heart of DDD. In the light of two domain models, ubiquitous language could be understood as a device to separate an informational model from a physical world model and make it subject of design, not analysis. It extends the realm of us, developers. And model-driven design urges us to keep software structure to be exact reflection of the informational model. It prevents our profession from dissolving into analysts and developers.
Thus, design of informational models is in our hands. Informational models had been designed even before our information technology age. Double-entry book-keeping system is a remarkable instance of those models. Train operation diagrams and music scores are other examples. These models had been designed by people on the "business-side" (because there was no IT-side). By contrast, the responsibility to design informational models seems to be floating in our IT age. Our IT has been too awkward for business people. Therefore they often have felt uncomfortable designing informational models which are to be implemented with IT. Analysts might be the people in charge. But they analyze, do not design. Furthermore, it is difficult to design if you are disconnected from feedbacks that implementation would provide (this is also what model-driven design cares). We, developers are in the best position to accept this responsibility, off course with a tight cooperation with business-side people.
Now an essential act of our profession is the information design underpinned by the software design. This is what DDD implies.
Kei Sugimoto @sugimoto_kei Programmer and Designer of fusion_place, a management infrastructure software http://www.fusions.co.jp
1 note
·
View note
Text
Practical Symfony #27: コンパイルタイムファクトリ(Compile Time Factories)
この記事はSymfony Advent Calendar 2015 8日目の記事です。前日の記事は@__tai2__さんの「DQLのJOIN WITH構文を使えば、無用な関係を定義せずにテーブルの結合ができる」でした。
ファクトリ(Factories)は、オブジェクトの生成(Creation)に関するデザインパターンで、オブジェクトまたはオブジェクトグラフの組み立て方法についての知識(構成の知識)を集約するものです。Eric Evans氏(@ericevans0)の提唱するドメイン駆動設計(Domain-Driven Design: DDD)のビルディングブロックの1つとしても知られています。
論理的にはファクトリは以下のような構造を持ちます。
クラスまたはメソッドによるファクトリ
クラスまたはメソッドを使ったファクトリはPHP + Symfonyの環境における基本型といえます。その構造は上記の図と同様になります。生成されるオブジェクトのバリアントはファクトリに与える引数の違いから生じます。変数値がランタイム(runtime)にならないと決まらない場合に特に有用ですが、クライアントコードでファクトリクラスを直接使うとクライアントとファクトリが結合する点には注意が必要です。
変数の値は構成の知識ではない
バリアントが少ない場合、異なる変数値の組を持つそれぞれのメソッドを定義すればよいと思うかもしれません。しかし、この場合は構成の知識がそれぞれのメソッドに重複して存在することに留意しなければいけません。例えば、生成対象クラスに可変点が追加された場合を想像してみてください。構成の知識と変数値の組は定義されるタイミングが異なるため、異なる場所に配置される方がより適切であるといえるのです。
DIコンテナを使ったファクトリ
DIコンテナはSymfonyフレームワークを支える基盤です。構成の知識を集約するという観点から見ると、DIコンテナはファクトリの一種といえます。生成されるオブジェクトのバリアントはDIコンテナのサービス定義の違いから生じます。変数値がソースコード記述時(source time)やアプリケーションのデプロイ時(deploy time)に定まるような場合に効果を発揮します。その構造は論理的には先ほどのものと変わりませんが、物理的にはクライアントがファクトリに依存しない点で異なります。
DIコンテナを使ったファクトリでは、変数値はSymfonyアプリケーションのキャッシュクリア時に生成されるDIコンテナクラスのメソッド内に埋め込まれます。DIコンテナの生成(コンパイル)が行われるタイミングのことを私たちはコンパイルタイム(compile time)と呼んでいます。
ドメイン特化言語として表現される可変性
Symfonyの設定ファイルapp/config/config.yml等のコードは、問題ドメインにおける可変性の表現です。そこではソフトウェアの内部的な用語ではなく、そのドメインのクライアントとソフトウェアの間で共通に使われる用語(と構造)が使われます。ドメイン特化言語(Domain-Specific Language: DSL)は解決ドメインにおける実装コンポーネントの可変性を別の形で表現したものといえます。
Note このあたりの話は書籍「ジェネレーティブプログラミング」のp.146「5.9.7 コンフィギュレーションDSL」に詳しく書かれていますので興味のある方は是非ご覧ください。
DIコンテナを使ったファクトリでオブジェクトのバリアントを扱う場合、変数値はサービス定義やパラメーターではなくconfig.ymlで記述されるのが一般的です。なぜなら、config.ymlが配置されるパスapp/config/config.ymlに表されているように変数値はドメイン(フレームワーク)ではなくアプリケーションに属するものだからです。
問題:変数値は事前にわからないためファクトリサービスを定義できない
さて、ここで1つ問題があります。変数値は事前にわからないため、ソースコードを書いているタイミングではファクトリサービスを定義できないのです。
解決:エクステンション��コンパイラーパスを使ってサービス定義を生成する
この問題はメタプログラミングを使ってDIコンテナのサービスを定義することで解決できます。Symfonyにはそのようなプログラミングを行う場所として、エクステンション(Symfony\Component\DependencyInjection\Extension\ExtensionInterface)とコンパイラーパス(Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface)があります。例としてPHPMentorsWorkflowerBundleのコードを見てみましょう。
Workflowerでは1つ以上のワークフロー定義ファイル(BPMNファイル)を持つコンテキストを1つ以上定義することができます。それぞれのコンテキストはBPMNファイルを配置するディレクトリを1つ持ちます。PHPMentors\Workflower\Definition\Bpmn2WorkflowRepositoryオブジェクトはコンテキスト毎のBPMNファイルのパスを保持し、findById()によってワークフローID(ファイル名から拡張子を取り除いたもの)に対応するPHPMentors\Workflower\Workflow\Workflowオブジェクトを返します。このPHPMentors\Workflower\Definition\Bpmn2WorkflowRepositoryのサービス定義を生成するコードは以下のようになります。
PHPMentors\WorkflowerBundle\DependencyInjection\PHPMentorsWorkflowerExtension:
<?php ... namespace PHPMentors\WorkflowerBundle\DependencyInjection; ... class PHPMentorsWorkflowerExtension extends Extension { ... /** * @param array $config * @param ContainerBuilder $container */ private function transformConfigToContainer(array $config, ContainerBuilder $container) { ... foreach ($config['workflow_contexts'] as $workflowContextId => $workflowContext) { $workflowContextIdHash = sha1($workflowContextId); $bpmn2WorkflowRepositoryDefinition = new DefinitionDecorator('phpmentors_workflower.bpmn2_workflow_repository'); $bpmn2WorkflowRepositoryServiceId = 'phpmentors_workflower.bpmn2_workflow_repository.'.$workflowContextIdHash; $container->setDefinition($bpmn2WorkflowRepositoryServiceId, $bpmn2WorkflowRepositoryDefinition); $definitionFiles = Finder::create() ->files() ->in($workflowContext['definition_dir']) ->depth('== 0') ->sortByName() ; foreach ($definitionFiles as $definitionFile) { $workflowId = Bpmn2File::getWorkflowId($definitionFile->getFilename()); $bpmn2FileDefinition = new DefinitionDecorator('phpmentors_workflower.bpmn2_file'); $bpmn2FileDefinition->setArguments(array($definitionFile->getPathname())); $bpmn2FileServiceId = 'phpmentors_workflower.bpmn2_file.'.sha1($workflowContextId.$workflowId); $container->setDefinition($bpmn2FileServiceId, $bpmn2FileDefinition); $bpmn2WorkflowRepositoryDefinition->addMethodCall('add', array(new Reference($bpmn2FileServiceId))); $processDefinition = new DefinitionDecorator('phpmentors_workflower.process'); $processDefinition->setArguments(array(pathinfo($definitionFile->getFilename(), PATHINFO_FILENAME), new Reference($bpmn2WorkflowRepositoryServiceId))); $processServiceId = 'phpmentors_workflower.process.'.sha1($workflowContextId.$workflowId); $container->setDefinition($processServiceId, $processDefinition); } } } ...
エクステンションとコンパイラーパスのどちらを使うべきかはケース���イケースですが、やることは変わりません。いずれの場合においても、DIコンテナのコンパイル時にファクトリが動作するように見えることから、私はこのような解決策をコンパイルタイムファクトリ(Compile Time Factories)と名付け、ソフトウェアパターンとして積極的に活用しています。
おわりに
コンパイルタイムファクトリを使うと、バリアントの定義をアプリケーションに委ねつつ、構成の知識をドメイン(フレームワーク)に集約することができます。また、コンパイルタイムファクトリを使うことで、従来必要とされたファクトリクラスの多くは不要になります。それは単なる機構を超えて、関連するドメインモデルにもいくらかの影響を与えることでしょう。James Coplien氏(@jcoplien)が書籍「マルチパラダイムデザイン」で述べたように、解決ドメインの構造が問題ドメインの構造を変化させるのですから。
参考
Factory Pattern | Object Oriented Design
ジェネレーティブプログラミング (IT Architects’Archive CLASSIC MODER)
新装版 マルチパラダイムデザイン
0 notes
Text
コネクタとしての BEAR.Resource について考えたこと
BEAR.Sunday Advent Calendar 2015 6日目の記事です。(※記事の公開が予定よりも遅れてしまいました。すみません。)
RESTfulフレームワークである BEAR.Sunday を使う機会がありました。 使ってみて考えたことを書きたいと思います。この記事では、まずコネクタとしてのリソース(BEAR.Resource)について説明します。リソースの単位を
1. 名詞的情報にした場合と、 2. ドメインユースケースにした場合
についてそれぞれ特徴を検討します。後者については説明用に動作するスニペットを組みました。
BEAR.Resource とは
BEAR.Sunday を構成するオブジェクトフレームワークの1つに BEAR.Resource があります。READMEには下記の記述があります。
BEAR.Resource はオブジェクトがリソースの振る舞いを持つHypermediaフレームワークです。 クライアントーサーバー、統一インターフェイス、ステートレス、相互接続したリソース表現、レイヤードコンポーネント等の RESTのWebサービスの特徴をオブジェクトに持たせる事ができます。 既存のドメインモデルやアプリケーションの持つ情報を柔軟で長期運用を可能にするために、 アプリケーションをRESTセントリックなものにしAPI駆動開発を可能にします。
リソースオブジェクトは、WebのURIと同じような page://self/index などのURIを持ち、HTTPのメソッド(GET/POST/PUT/DELETE)に準じたインターフェイスを持ちます。
コネクタとビジネスコンポーネントの関係
BEARでWebページを作る場合、「ページリソース」が「アプリケーションリソース」を呼び出す形になります。 アプリケーションリソースを使う意図について、フレームワーク作者の郡山さんに質問したところ、下記のような回答を頂きました。
Mike Amundsen(@mamund) さんの来日講演で、継続開発可能性のために、アプリケーションは情報を接続するコネクティングレイヤーとビジネスロジックのためのコンポーネントレイヤーの二層が必要だというのがありました。 BEARのリソースは前者の情報の接続のためのレイヤーです。
※コネクタ(接続レイヤー)とコンポーネントについての詳細は、資料 RESTとAPI設計のためのガイド / A Guide to REST and API Design eBook に記述があります。
アプリケーションリソースの基準は何か?
では、アプリケーションリソースの粒度、最小単位(= 再利用の単位)を何にするのか?考えてみたいと思います。
パターン1:リソースを名詞的情報とする
たとえば、公式チュートリアルの例だと下記です。
ref. bearsunday/MyVendor.Weekday
業務上意味のある名詞である情報をリソースにするパターンです。この基準を採る場合の特徴について、下記のように考えました。
メリット:BEAR.Resource の諸機能(URI、リンク、外部アプリケーションのインポート、キャッシュ等)を細粒度から使える
課題:OOP、責務駆動設計、ドメイン駆動設計と蓄積されてきたような、型制約を活かした設計を採り入れる場合の実装方法?
たとえば、下記のような問題の構造をそのまま意図としてコードに表すのがDDDのビルディングブロックの考え方でした。クラスの型、名前、振る舞い等で部品の責務を限定することでリッチな語彙を作っていきます。
エンティティをリソースに、ユースケースをリソースに、サービスは?、URI制約から型への変換が要る・・・と実装を考えたときにうまくおさまらないように私は感じました。
パターン2:リソースをドメインユースケースとする
ユースケースクラスをリソースにインジェクトするパターンです。「Domain Kata」 のサンプルアプリケーションを見て、サンプルのモデル部分をBEARアプリケーションから呼び出すスニペットを実装してみました。(Doctrine 注入については文末掲載の参考リンクを参照させて頂き、助かりました。ありがとうございます。)
kumamidori/SnippetUserRegistration
メリット:URIでユースケースの再利用が可能、モデルの意図性
課題:ユースケースから利用される側のクラスでは BEAR.Resource の諸機能がデフォルトでは使えない
2パターン挙げましたが、前者パターンで部分的にサービスをリソースにインジェクトする形もあります。 最後に、まとめ・・・としたいところですが、結論のようなものは現時点ではありません。 この記事を書くにあたって、こんな記事(奥さんに REST をどう説明したかというと…)を発見してのんびり読んだりして、楽しかったです・・・。
関連記事
ドメインモデルのための型「Domain Kata」を使ってみました
Kata #1 - サービス(SERVICES)
参考リンク
BEAR.SundayでDoctrine2のORMを使ってみた(改) — A Day in Serenity (Reloaded) — PHP, FuelPHP, Linux or something
BEAR.SundayでDoctrine2のORMを使ってみた - Qiita
0 notes
Text
Symfonyの本「基本からしっかり学ぶSymfony2入門」を執筆しました
この記事は、Symfonyアドベントカレンダー2015の6日目の記事です。昨日は@okapon_ponさんの「Symfonyでdebug環境を最適化しコードを追いやすくする」でした。
12月16日付けで、技術評論社様より『基本からしっかり学ぶSymfony2入門』が出版されます。最初に企画書を書いたのが2014年の3月で、そこから出版まで2年近く時間がかかりました。この本ではSymfony 2.7を対象としていますが、書き始めた頃はまだSymfony 2.3の時代でした。今ではすでに2.8と3.0もリリースされているので、バージョンの進み具合だけとっても執筆に結構長くかかってしまったと感じます(苦労させられた点でもありますが)。執筆は、私(後藤)とカルテットコミュニケーションズの金本さんとの共著になっています。
技術評論社 書籍紹介ページ
すでにAmazonでご予約頂いている方も多数いらっしゃるようですが、今回、紙版と同時に電子書籍版も12月16日付けで同時発売となります。 また、技術評論社の電子書籍サイト「Gihyo Digital Publishing」では、12月11日から先行販売いたします。ぜひご購入ください。 電子書籍版の提供媒体は以下になります。
Gihyo Digital Publishing
ヨドバシドットコム
Amazon Kindle
楽天ブックス(楽天Kobo)
honto
書籍出版を記念して、12月13日(日曜日)に東京都内でSymfonyユーザー会主催のMeetupイベントが開催される予定です。このイベント向けに技術評論社様から『基本からしっかり学ぶSymfony2入門』を何冊かご提供頂けることになりました。参加した方は本をGETできるかもしれません!
この記事では、私が著者として考えるこの本の特徴と、本の執筆作業のちょっとした工夫について紹介します。
特徴1:Symfonyの外側から内側までを見渡せる
『基本からしっかり学ぶSymfony2入門』は、次のような章構成になっています。
第1章 PHPとフレームワーク PHPとフレームワークについての概要と、Symfonyの外面的な特徴などを解説しています。
第2章 Symfonyの基礎 Symfonyが持つ構成要素を、Symfony公式のデモアプリケーションのコードを追いながら順に解説します。Symfonyを使ってアプリケーションを開発していくのに必要な要素について、それぞれの役割や関係性をスッキリと把握できます。
第3章 アプリケーションの開発1 ページ作成の基本を身につける Symfonyの中で、MVCのための基本要素であるルーティングとコントローラ/アクション、および基礎的なテンプレート作成機能を解説しています。
第4章 アプリケーションの開発2 テンプレートの整理とフォーム 実務で必要となる高度なテンプレートの整理方法や、フォームの実装方法を解説しています。
第5章 管理機能の開発1 データベースとの連携 データベースを利用するためのDoctrineの機能について、基礎となる考え方から丁寧に解説しています。
第6章 管理機能の開発2 フォーム入力チェックとユーザ認証の実装 フォームの入力チェックや、管理者向けのいくつかのページの実装など、ここまでで解説した機能を総合的に利用した実装について解説します。
第7章 管理機能の拡張とAPIの開発 バンドルを利用して様々な機能を組み込んでいく方法を解説しています。RestBundleを使ったAPIの実装や、APIドキュメント、CORS対応などをバンドルで組み込む方法を解説しています。
第8章 サービスコンテナを使った開発 Symfonyの根幹であるサービスコンテナについて、さまざまな角度から踏み込んで解説しています。
第9章 テストの実装 実装したコードのユニットテストやファンクショナルテスト、およびモックやフィクスチャの利用方法などについて解説しています。
第2章でSymfonyの世界を概観することで、読者の頭の中に基礎的な地図が描かれるようになっています。以降の章は、頭の中にできた地図上の点と点のつながりを実際に辿るような感覚で読み進んで行くことができます。そして後半の第8章で、Symfonyの中核を支えるサービスコンテナにたどり着きます。このような流れを辿ることで、Symfonyがもつフレームワークとしてのしくみと、Symfonyの描く世界観とを、読者がつかめるようになっています。
また、次のような工夫も盛り込んであります。
概念的な解説には図解も使ってイメージで理解できるように
各章の末尾にチェックポイント(簡単なテスト)を用意して、章の内容を振り返って理解を確認できるように
特徴2:サンプルコードのGitリポジトリ
プログラミングの実習書で手を動かしながら学んでいくスタイルのものは、すでにたくさん存在します。プログラミングの技術を身につけるには、自分で動かしながら学ぶのが一番だというのは、経験のあるプログラマの共通認識で、本を見ながらソースコードを手で「写経」していくスタイルを好む方も多くいます。しかし、慣れていないうちは、タイプミスなどを探すだけで日が暮れてしまい、本当に学ぶべき事柄になかなか集中できません。
本書では、掲載しているリストのコードはすべてダウンロードで配布します。さらに本書では、サンプルアプリケーションのGitリポジトリも公開しています。このGitリポジトリは、本書内での解説に合わせてリスト1つずつ、順にコミットを刻んでありますので、コミットで差分を見たり、1つずつチェックアウトして進めたりという風に使えます。
サンプルコードを自分でいろいろ修正するうちに、何かが原因で動かなくなってしまった経験が誰にでもあるかもしれません。このような時に、本の一番最初からリストを入力しなおすのはとても不毛です。Gitリポジトリがあれば、該当するリストまでのコミットをチェックアウトするだけで、任意のリストの時点に戻すことができますので、安心して、自分の好きなようにコードを修正して「壊しながら」学ぶことができます。
壊してよいオモチャ(Breakable Toys) - アプレンティスシップ・パターン
その他
現場で実際に使っている役立つTIPSを所々で紹介
Symfonyチートシートとして、様々なコマンド、関数のメソッド等の一覧表を付録に収録
本文や脚注で紹介したリンク先について、わざわざ手でURLを入力しなくてよいように、リンク一覧をダウンロードファイル内に収録
執筆作業について
執筆作業は、最近の技術書執筆では割と一般的ですが、
原稿はmarkdown
GitHub(編集担当さんのリポジトリ)にpush
issueで進捗や問い合わせ確認
という基本スタイルで進めました。
markdown→HTMLのコンパイル環境
markdown形式はプログラマには人気ですが、実を言うと私自身は、markdownでの原稿執筆はあまり捗りません。世の中には、markdownをリアルタイムでプレビューできるエディタが多数あり、いくつも試しました。しかし、本の章レベルの分量ともなると、安定性や見やすさなどが気になってしまい、私の感覚に合うものがありませんでした。あまりに執筆が捗らないため、まずは執筆用のエディタを自分で作ろうなどと、変な道へ進みかけた時期もありましたが、結局はJavaScriptエコシステムにあるmarkdownパーサーを使って自動的にHTMLにコンパイルする環境を作るというところに落ち着きました。 HTMLへのコンパイルには、grunt-markdown(内部でmarkedを利用)を使いました。
執筆の進捗感
そこそこのボリュームを執筆していくのは、私のような文章の素人にはいろいろと大変です���最初に全体の構成(章以下2レベルくらいの見出し)を考えた後は、ある程度頭から順に書いていくのですが、途中で筆が全く進まない個所がでてきたりします。そのようなところは飛ばして後を書いたり、また、ふとしたときに思いついた個所だけ書き進めたり、といったことをスクランブル状態でやっています(あまりプロフェッショナルな進め方じゃないのが、お恥ずかしいですが)。
このような書き方であちこち取り組んでいる時に、一体どれくらい執筆が進捗しているのか、書いている私自身がフィードバックとして欲しくなります。筆の進みが悪い時はなおさらです。普段のプログラミングで、調子が出ないときに軽めのissueに手を付けて、片付けたら調子が出てくるようなのと同じです。そこで、少しでも執筆の進捗を可視化しようと、上で作成したmarkdown→HTMLへのコンパイル時に、各章ごとの文字数を日付毎にJSON形式のログに記録するようにしました。 これによって、毎日少しでも数字が増えていくことを励みにしながら、なんとか執筆を継続できました。
見出しや図、用語チェック
HTMLへのコンパイル時に、markdownのパース結果から以下を出力するようにしました。
見出し一覧
図一覧
リスト一覧
表一覧
また、azuさんの公開されている技術用語の表記ルール違反チェックも実行するようにしました。
お礼
本書の執筆には、たくさんの方にご協力頂きました。 特に、一部執筆も協力いただいた金本さん、かなり早期にたくさんのレビューコメントを頂いた山根さんのお二人の助力なしでは、本書を最後まで書き上げることはできなかったと思います。 ありがとうございました。また、日本のSymfonyコミュニティのみなさんにも、いろいろとお手伝いいただきました。
そして、素晴らしいプロダクトを開発し続けているSymfonyコントリビューターの皆さんにも、改めて感謝を伝えたいです。ありがとうございます!!
PS:本書の内容に対する質問はGitHubのissueにて受け付けます。 こちらでの読者へのサポートなど、Symfonyユーザーの皆様からご協力いただけたら幸いです。
リンク
基本からしっかり学ぶSymfony2入門 (Amazon)
0 notes
Text
PHPカンファレス2015 PHPメンターズセミナー「モデルを設計せよ!―ドメイン駆動設計を超えて」参加レポート
2015年10月03日にPHPカンファレンス2015内で、ミニカンファレンスとして開催されたPHPメンターズセミナー「モデルを設計せよ!―ドメイン駆動設計を超えて」に参加してきました。
モデルを設計せよ!―ドメイン駆動設計を超えて
視点
PHPメンターズ 後藤秀宣 @hidenorigoto
セッションのテーマは「視点」。いくつかの実例から、参加者が視点そのものについて実際に考えることができる体験的な講演でした。後藤さんのスライドに関しては公開は無しとのことです。このセミナーでは、この「視点」という言葉が全体を通じてのテーマになっていると思います。
特に印象深いのが「コップを空にする」話です。既にたくさんの知識を身につけた修行僧が、高名な僧侶のもとに学びに行きます。修行僧がひとしきり知識を披露した後、高名な僧侶は空の茶碗にお茶をそそぎ、そそぎ続けて、茶碗からお茶が溢れてしまってもそれをとめません。
高名な僧侶は修行僧に、身につけたあふれんばかりの知識を脇において、頭のなかをいったん空にして、新しい知識を吸収する大切さを教えたかったのです。後藤さんは、いったんこれまで積み上げた知識は飲み干して、そこにこのセミナーで得られる知識をたくさん注いで帰ってもらいたいと話されていました。
フォーム検討に対する概念フォーム理論からのアプローチ
名古屋経済大学 経営学部教授 中西昌武
フォームというのは帳票(Webシステム等では画面)のことです。リレーショナルデータベースモデルにはコッド博士による理論化・体系化がなされているのに、フォームの構造に関してはそれに相当する数学的基礎がない。ならばその理論を作ってしまおうということでフォーム構造の数学的な基礎を構築されています。
帳票の裏にある論理テーブルの構造をノードとパスとみなして、その繋がり方とアクセスするための経路(これがフォームの構造として表現される)を行列として定式化できます。これにより、グラフ理論を用いて生成可能なフォームパターンを網羅的に示すことができるというような理論でした。
確かにフォームをどのように構成するかなどは、実業務の中でも経験によって作っている部分もかなりあると思うのですが、それがちゃんと数学的に定式化できて、パターンを数え上げることもできるとしたら、例えばユーザはフォームの選択肢の中から特定のフォームパターンを選んで、あとは自動的に画面が生成されるということも可能になってきます。
また、フォームを選びとるということは、背後にある論理データモデルに対するどういう見方を選びとるかということであって、その選択したものが、ちゃんと論理的に妥当なことが数学的に保証されている、ということは非常に重要なことだと感じました。
中西先生は、以前にIT勉強宴会で講演されていて、その時のレポート記事の方も参考にして下さい。
ジェネレーティブ・プログラミングの世界 | IT勉強宴会blog
PHPメンターズ -> 第29回 IT勉強宴会in名古屋「ジェネレーティブ・プログラミングの世界」参加レポート
興味を持たれた方は中西先生のこれまでの論文・論考も是非ご覧ください。現在、中西先生は情報システム学会で「超上流工程における要求分析への科学的アプローチ」研究会を開催されています。研究会への参加をご希望の方は第3回勉強会のご案内をご覧ください。
ドメイン駆動設計 ~ユーザー、モデル、エンジニアの新たな関係~
株式会社フュージョンズ 杉本啓 @sugimoto_kei
ドメイン駆動設計 ~ユーザー、モデル、エンジニアの新たな関係~ from 啓 杉本
ドメイン駆動設計に対する見方をいくつかの視点の切り替えで明快に整理した名演だったと思います。
エリック・エヴァンスのDDD本は示唆深く、ドメインモデルの設計に関して深い洞察を得られる本だと思うのですが、読み解くのがとても難しい本だとも思っています。杉本さんの講演(資料)を横においてエバンスのDDDを読みなおしてみるとまた新たな発見があるかもしれません。
一つ目の視点の切り替えは、ドメインモデル自体が存在する領域をどこに置くかということです。ドメインのモデルですから、字義通りに捉えるとドメイン領域に関するモデルと考えてしまいそうです。
しかし杉本さんは、ソースコード管理システム(GitとSubversion)の例を用いて、例えばコミットという概念はドメインの領域にではなくむしろ具体的なツールの側のソリューションから現れてくる概念だと説明します。そこから、ドメインモデルはドメイン(問題領域)の側から自然に立ち現われてくる(見つけだすとでも言ったらいいのでしょうか)ものではなく、むしろソリューションの中から我々が意図的に設計しようとするところから現れる解決領域のためのモデルとして捉えます��
DDD本の中で船舶/コンテナが出てくる海運事業モデルの中で、船荷証券(B/L)というモデルを見出す場面がありますが、これはモデルの問題領域を深く考察することで得られるものではなく、貿易実務の中で伝統的に開発されて普及してきた情報処理に関する事務処理パターン(船荷証券は法制度化もされています)、つまり管理過程に着目することで当然のように出てくるモデルということになります。
似たような問題領域と情報処理モデルの関係には、会計分野における複式簿記、鉄道業務におけるダイヤグラムなどがあります。
ドメインモデルは、解決領域のモデルであるというのが杉本さんの指摘だと思います。
(船荷証券に関する議論は本ブログの記事PHPメンターズ -> Practical DDD #3: モデルの深さで詳しく議論されています。)
二つ目の視点の切り替えは、このようにドメインモデルを眺める視点を移すことが、分析と設計の線引きを変えることにつながるという点です。これにより ドメイン駆動設計を、1.エンジニアリング の対象を広げながら、2.その広がったエンジニアリング活動内部での分裂を防ぐ、つまりソリューションのために作り出されたモデルと実装モデルの乖離を防ぐものだと捉えることができるようになります。
このように整理すると、各種の分析手法―分析モデルから設計モデルに落としこむ一般的なオブジェクト指向分析や、管理過程に着目して情報の流れや帳簿組織を表すデータモデルを構築する現代的なデータ指向分析と構造を比較できて面白かったです。
そして三つ目の視点の切り替えは、エバンスのDDD本の構成に関するものです。確かにエバンスのDDD本を頭から読んでいると、第2部においていきなり実装寄りの個別の実装パターンに入るようでおやっと思ってしまいます。
あるいは、実装者の立場からすると馴染みのある用語や実践しやすいプログラムの構成要素が出てきて、思わず飛びつきそうになります。プログラムの中にエンティティやリポジトリを登場させることがドメイン駆動設計だと間違った解釈をしてしまう場合もあるかもしれません。
この点に関して、杉本さんは、DDD本を構成するパターンランゲージを「原則のレイヤ」と「実践のレイヤ」にわけ、前者をオブジェクト指向に依存しない普遍的な部分、後者をオブジェクト指向でドメイン駆動設計を実践するためのパターン群とみることを提案しました。
そして、前者は原則的なものなので、オブジェクト指向だけではなく、データ指向分析などの様々なパラダイムの実践手法に適用することが可能になると説明します。
このような3つの視点の切り替えを通して、エバンスのDDD本を読み直すと他にも色々な発見がありそうなので、近いうちに一通り読み直してみたいと思います。
Frameworks We Live By: Design by day-to-day framework development: Multi-paradigm design in practice
PHPメンターズ 久保敦啓 @iteman
Frameworks We Live By: Design by day-to-day framework development: Multi-paradigm design in practice from Atsuhiro Kubo
James O. Coplienのマルチパラダイムデザインは1998年に最初の版が上梓された本なので、実はエリック・エヴァンスのドメイン駆動設計よりも先発の書籍になります。
にも関わらず、問題ドメインの概念に根ざした分析/設計/実装における単一のモデルの構築を目指すという目標は同じでありながら、そこに至るための方法論に関してはCopeのマルチパラダイムデザインの方がエヴァンスのドメイン駆動設計にはなかった設計手法を明示している、というのが久保さんの主張です。
マルチパラダイムデザインでは、人間が物事を認識するときの認知モデルに基づいて問題ドメインを分析していきます。久保さんの発表で出てくる、古典的カテゴリー理論と新しいカテゴリー理論の違いは、物事を生得的で客観的な事象として捉えようとするのか、経験に基づいた具体的・身体的な認知からの拡張によって得られるものなのかという違いだと思います。
Copeはここでいう認知に関しては後者の見方を基礎にしていて、問題ドメイン分析においてそういった認知機構を元に問題をサブドメインに分割して、そうして得た対象に対して、共通性分析、可変性分析を進めていきます。ここで重視されるのはやはり「審美眼、洞察、経験」によるものであって、例えば「よく知られたサブドメインは設計の優れたスタートポイント」などを考えるとよくわかります。
そのうえで、解決ドメイン分析、ドメインモデル設計(変換分析)に移るわけですが、ここでは問題ドメインの構造と解決ドメインの構造のマッピングをしながら分析していきます。そして解決ドメインの構造で問題ドメインの構造を再定義していきます。これは、何度か繰り返されるプロセスになります。つまり、解決ドメインが提供する抽象(構造)-型、クラス、継承、リレーション、エンティティ、値、パターンといった一群の文法要素やイディオム-が問題ドメインを洗練していくのです。すなわち、解決ドメインに対して利用することのできるパラダイムを用いて問題ドメインを再構築していくのです。
これは、先ほどの杉本さんの解決領域のためのドメインモデルにも繋がっていて、マルチパラダイムデザインのドメインモデル設計(変換分析)で再定義、洗練されたドメインモデルは、エリック・エヴァンスのDDDが目指している単一のモデルと等価なものになります。
これらのマルチパラダイムデザインの議論から久保さんは一歩進んで、問題分析で目の前にあるニーズを解決する特定の分析から得られる知見よりももっと広い視野で対象とするドメインのフレームワークを作るという視点を持ち込むことで、幅広い領域に適用できる共通性を発見することが出来るといいます。
ここで言うフレームワークは一般的に知られる、SymfonyやRuby on RailsのようなWebアプリケーションフレームワークだけではなく(もちろん、それらもある特定のドメインを対象としたフレームワークです)、様々な問題領域のドメインに対して適用されるフレームワークのことを指しているのだと思います。
例えば、発表の中であったワークフローエンジンもそうですし、ルールエンジン的なものや、もっとアプリ特有のドメイン用途のフレームワークも考えていいと思います。
それらのフレームワーク開発を通してドメインについて日常的に考え指向することで、プログラマー自身が設計者となって、実装の中でドメインモデルを洗練させていくモデラーとなるというのが久保さんから一番大きく受け取ったメッセージです。最後の「Code the Domain! You are a Domain Coder!」、ドメインをコードにせよ、あなたがドメインコーダーなのだという言葉は、ドメイン駆動設計を超えて、さらにマルチパラダイムデザインを超えて、その先にあるものとしてとても希望の持てるものだと思いました。
Coding We Live By ~モデルと実装の今と未来~
PHPメンターズ 後藤秀宣 @hidenorigoto
この講演で後藤さんは、久保さんまでの講演で辿り着いたドメインモデルを設計しながら実装する、実装しながら設計するということを、もう一度興味深い実例を示して強調します。
これは、後藤さんが実際に取り組んだアプリケーションなのですが、幾つかのリストを組合せてデータを生成する機能が既存機能としてありました。それは、元ネタのリストからいったん全パターンを組み上げて「分割」するという構造になっていたそうです。業務側の人もその機能のことを「分割」と呼んでいました。それ自体はとても自然な設計で作りも悪くはなかったのですが、多数の機能追加や変更の結果、プログラムの変更がとても大変な状況になっていたようです。そこで、後藤さんが入った時に、それまでの他のアプリケーションでの経験から「部分集合を作る」「集合の直積を作る」ということとその集合演算で処理を表現できることに気付いて、実際にその演算をするためのライブラリを実装しました。そして、その集合演算をサブドメインとして切り出すことで、処理を簡素化するとともに変更に対して柔軟に対応することができるようになったということです。
このような実践を行うためには2つの条件、すなわち、既に後藤さんの頭のなかのツール箱にそういう解法に対するアイデアがあったことと、後藤さん自身がそのアイデアを実装してそれが有効であることを証明することができたということが重要であったと思います。
セミナー全体を通して「視点」の切り替えが新しい気づきや理解を促してくれて、ドメイン駆動設計を横糸に、ドメインとコードというものが強く結びついて、実装者がドメインモデラーなのだという主張をはっきりと感じ取れるようになりました。
後藤さんは「視点」の重要性を示し、中西先生は、フォーム構造という要求分析の要素にある視点(フォームを選びとるということ)から導き出される構造の数学的理論的な土台を与えてくれました。杉本さんは、ドメイン駆動設計を題材にとり、今までとは違った視点でそれを解釈し直すことで、ドメイン駆動設計をより納得行くかたちで再定義してくれました。久保さんは、そのドメイン駆動設計の先にあるマルチパラダイムデザインによる設計からさらにフレームワークによる設計への道筋を、自らの経験と成果から、これも視点を変えて設計活動を捉え直すことで、示してくれました。また、フレームワークと捉え直したドメインの設計活動の中で、実装の中でドメインモデルを設計する、実装という活動そのものが設計なのだというメッセージを提示してくれます。
そして、最後に「ドメインモデルを設計しながら実装する、ドメインモデルを実装しながら設計する」という後藤さんの言葉でセミナーは結ばれました。
セミナー全体の感想
久保さんのセッションや質疑応答の中で、ドメインエキスパートと並立する存在としてのドメインモデラーという言葉がでたのですが、ドメインモデラーは、杉本さんの言葉で言えば解決領域(情報管理過程)の専門家としてドメインエキスパートと協同してドメインモデルを作っていきます。
そのためには、特定の領域の情報管理過程、情報モデルについて精通している必要があります。みなさんも、実装者であれば何らかの分野(業務モデルとも限りません、それはソーシャルゲームの場合もあるでしょうし、組み込み領域のプログラムの場合もあると思います)についての情報モデルの専門家であることが多いと思います。その解決領域に関する知見を深めて、道具箱に使えるツールを増やしておけば、それを実装の現場で応用することが出来る場面も増えるのではないかと思いました。
当日、その後の関連ツイートについて
当日のツイートやその後の関連ツイートがPHPカンファレス2015 PHPメンターズセミナー「モデルを設計せよ!―ドメイン駆動設計を超えて」 - Togetterまとめにまとめられています。こちらも是非ご覧ください。特に最後の杉本さん(@sugimoto_kei)による���メイン駆動設計の根底にある意図の考察は必見です!
#design#ddd#multiparadigm.design#event#eric.evans#james.coplien#generative.programming#dsl#framework#nakanishi.masatake#sugimoto.kei#practical.ddd
1 note
·
View note
Text
Debasish Ghosh氏のブログ記事「ドメイン駆動設計:可変性の管理」を翻訳しました
ドメイン駆動設計が、今、世界的に盛り上がりを見せています。2016年1月には、Domain-Driven Design Europeが開催されるそうです。このイベントのスピーカー&ワークショップ講師の1人として、Jim Coplien氏の名前が載っています。Jim Coplien氏は、日本だと、「組織パターン」の著者として、また、DCIアーキテクチャの人として有名ですが、ドメインエンジニアリングの研究者でもあります。
Eric Evans氏のDDD本は、サブタイトルが、「ソフトウェアの核心にある複雑さに立ち向かう」となっています。複雑さとは何か、それを管理する技術とは何か、古くて新しい問題です。Jim Coplien氏の著作「マルチパラダイムデザイン」では、ドメインとは何か、どのように設計を行うべきかについて書かれています(残念ながら絶版です。)
Debasish Ghosh(デバシッシュ・ゴーシュ)氏のDDDとマルチパラダイムデザインを関連付け��ブログ記事「Domain Driven Design : Managing Variability」を翻訳しました。以下に掲載します。(※デバシッシュ氏は、ScalaMatsuri2016に応募されています!)。
Note Debasish Ghosh氏は書籍「実践プログラミングDSL」(原著: DSLs in Action)の著者として知られています。彼は現在Functional and Reactive Domain Modelingを執筆中です。こちらは2016年春に出版予定だそうですが、MEAPを利用して一足先に読むこともできます。
ドメイン駆動設計:可変性の管理
原文URL:http://debasishg.blogspot.jp/2006/09/domain-driven-design-managing.html
2006年9月18日(月)
Springのユーザーたちが、ドメイン駆動設計について語り始めました。彼らはきちんと取り組んでおり、良い議論をしています。2006年に行われたSpringOneでは、ドメイン駆動設計に関する3つのセッションがありました。 AOPの人として知られているRamnivas Laddad氏(最近「Interface 21」メンバーに加わりました)は、DIとAOPが、DDDの最も良いところを引き出すのに有効であること―最小限のサービス層、豊かなドメイン層、流れるようなインターフェイスについて発表しました。 「Interface 21」とGrailsで知られるSteven Devijver氏は、サービス層にある複雑性を管理するためにデザインパターンを利用することについて語り、リッチなドメインモデリングに関する議論を行いました。 レイヤー化、AOP、IoC等による関心の分離のような重要なアスペクトについて彼は強調しました。 不変性は、モデルの正当性を維持するために重要であること。 つまり、クラスはイミュータブルに作り、できるだけ外部へ漏れ出さないように制御に努めること。 実装の内部をさらけ出してはならないこと―パブリックなセッターはNGであること。 要するに、ドメインモデルは、ドメインエキスパートが話せるドメインの言語にだけ公開されているべきだということです。 インターフェイスとパブリックな契約が、ユビキタス言語を語るように作ること。 ドメインモデリング技法に関する別のセッションでは、「Interface 21」のKeith Donald氏が、ドメインの蒸留、ドメインモデルの中に獲得した知識を抽象化するためのテクニックについて話しました。 全体を通して、SpringユーザたちはDDDファンのイベントを楽しんでいるようでした!
可変性!可変性!
ドメインモデルを、柔軟に、生成的(ジェネレ―ティブ)に作るということは、モデルにおいて可変性を管理することに尽きます。 そのモデルの可変部分をうまく管理することによって、より構成しやすく、カスタマイズしやすく、柔軟にすることができるようになります。 以前の記事では、ジェネレーターとしてはたらいて基盤を提供する構成の知識について書きました。 構成の知識を最大限に活用するのがこの可変性です。 設計のアスペクト、固有の実装、ライフサイクル、スコープの決まったインスタンス、アルゴリズム、ストラテジー等は、Java / C++のコードベースの中で埋没させたくはないものなのです。
ストラテジーパターンで示すと…
ここ数年、ストラテジーパターン実装の多くのバリエーションが、モデルの可変性を管理するために使われてきたと思います。 GOFは C++ で2つのバリエーションを検討しています。 1つは、実行時ポリモーフィズムと仮想関数を使うもの、もう1つは、テンプレートとコンパイル時ポリモーフィズムを使うものです。 それぞれにメリットがあり、特定のコンテキストにある特定の問題を解決するのに用いることができます。
下記スニペットに、ストラテジーデザインパターンを実行時ポリモーフィズムで適用して、繰延利息計算(accrual calculation)ドメインプロセスを外部化するやり方を示します。
class AccrualCalculation { private CalculationStrategy strategy; // セッターインジェクション public void setStrategy(CalculationStrategy strategy) { this.strategy = strategy; } // ストラテジーへの委譲 public BigDecimal calculate(...) { return strategy.calculate(...); } } interface CalculationStrategy { BigDecimal calculate(...); } class DefaultCalculationStrategy implements CalculationStrategy { public BigDecimal calculate(...) { // 実装 } }
// 構成の知識を通して可変性を外部化する <bean id="calcStrategy" class="com.x.y.DefaultCalculationStrategy"/> <bean id="accrual" class="com.x.y.AccrualCalculation"> <property name="strategy"> <ref bean="calcStrategy"/> </property> </bean>
ジェネリクスを使って実装した同じパターンと比べてみましょう。 繰延利息計算クラスがストラテジーでパラメーター化されるように設計しています。
class AccrualCalculation<S extends CalculationStrategy> { private S strategy; // ストラテジーへの委譲 public BigDecimal calculate(...) { return strategy.calculate(...); } } // 使い方 interest = new AccrualCalculation<DefaultCalculationStrategy>().calculate(...);
トレイトテクニックのelse-if-then型(この名前、いいですね!)は、C++コミュニティで使われており、拡張性を持たせつつ型レベルで可変性を管理することができます。 トレイトクラスとトレイトテンプレートは、メタプログラミング能力に秀でていただけではなくて、様々なC++アプリケーションの開発に使われてきました。 コンパイル時ストラテジーの別なバリエーションとして、Alexandrescu氏のポリシーベース設計があります。 ポリシークラスは、実装をいくつでも持つことができ、コンパイル時ストラテジーをドメインモデルにプラグインするのに最適な選択肢を提供してくれます。
ストラテジーデザインパターンは、最適な機構を提供することで、ドメインモデルにおける粗粒度の可変性をカプセル化します。 ストラテジーの実装をIoCコンテナやその他のジェネレーターへと外部化することにより、モデルがプラガブルであるようにします(構成の知識として)。
細粒度の可変性とテンプレートメソッド
テンプレートメソッドデザインパターンは、アルゴリズム、ストラテジーといった細粒度の可変性を、共通性フレームワークの範囲内でプラグインすることを目的としています。 ドメインプロセスの大きなレベルでのフロ���が不変である場合、モデルでこのパターンを使います。 こまごました物をカスタマイズできるように、フックをかけて外部化するわけです。 私はルールベースの大規模な金融アプリケーションをモデリングした時に、このパターンをうまく効果的に活用できました。 下記は、テンプレートメソッドデザインパターンを使って、細粒度の可変性を実装するサンプルです。 ここで、繰延利息計算アルゴリズムはfinalであり、フックの箇所は派生クラスによって追加されます。 フックは基底クラスの抽象メソッドとして管理されます。
abstract class AccrualCalculation { public final BigDecimal calculate(...) { // フック int days = calculateAccruedDays(...); if (days != 0) { // 不変のロジック } else { // フック boolean stat = isDebitAccrualAllowed(...); if (stat) { // 不変のロジック } // 不変のロジック } } protected abstract int calculateAccruedDays(...); protected abstract boolean isDebitAccrualAllowed(...); }
負の可変性
Jim Coplien氏は、設計、分析領域において、この用語を著作マルチパラダイムデザインの中で初めて紹介しました。 共通の継承階層にまとめられるメンバー群にあるうちの一部のメンバーが、他の点においては共通であるのに、一部の点でだけは基底クラスの仮定を破壊してしまうような時、これを負の可変性と呼びます。 Cope(訳注:James O. Coplien のこと)は、負の可変性は、基礎である共通性を侵害してバリエーションのルールを壊す、ルールの例外になる、と述べています。 負の可変性を実装するためのテクニックは少なくありません。 たとえば、C++ におけるテンプレートの特殊化や、条件コンパイル等です。 ブリッジデザインパターンもこの問題をうまく扱えることが知られています。 Coplien の「マルチパラダイムデザイン」に、負の可変性について詳しく書かれています。 この師範の深い洞察をぜひ読んでみて下さい。 実務の場で、ドメインモデルが負の可変性の振る舞いを示すことはよくあります。 そのようなとき、適したパターンやイディオムを使って、正しく取り扱う必要があります。
参考文献
マルチパラダイムデザイン(金澤典子, オブジェクトの広場2002年2月号)
Multi-Paradigm Design(James O. Coplien, July 8, 2000)
関連記事
Pieceの中のSymfony #3: Dependency Injectionコンポーネント―構成の知識とDIコンテナ
「ドメインモデリングにおける関数型パターン―仕様パターン」を翻訳しました
3 notes
·
View notes
Text
PHPカンファレンス2015 PHPメンターズセミナー枠のご案内
2015年10月3日に開催されるPHPカンファレンス2015で、午前・午後の約5時間の枠内でPHPメンターズによる複数の講演からなる、PHPメンターズセミナーを開催します。このセミナー枠のテーマは、
『モデルを設計せよ!ードメイン駆動設計を超えて』
です。
このセミナーに参加ご希望の方は、PHPカンファレンス���の参加登録とは別に、セミナーの参加登録ページにて登録をお願いいたします。セミナーのプログラムや注意事項なども、参加登録ページの方に掲載しておりますので、あわせてご参照ください。
このセミナーの見どころは、
「メンターズ of メンターズ」として、私たちPHPメンターズの師匠お二人による講演
モデリング、設計、プログラミングとドメイン駆動設計とのつながりなどについての講演
です。特に、私たちPHPメンターズがここ数年間、ソフトウェアの設計などについて学びを深める過程で、さまざまな形で私たちをガイドしてくださった師匠方を、このような大きなカンファレンスで参加者の皆さんにご紹介できることは、私たちにとって喜ばしいことです。このセミナーに参加された方が、師匠の話に実際に触れることで、システム/ソフトウェアの開発に対して新たな視点や期待、可能性を見出すきっかけになればと思っています。
是非ご参加ください。 (参加登録ページ)
1 note
·
View note
Text
第40回IT勉強宴会モデリング競演2でDDDのモデリングについて発表しました
2015年4月25日に大阪で開催された関西IT勉強宴会で、DDDのモデリングについて発表しました。
第40回IT勉強宴会【DDD,UML,DOA競演モデリング発表会2】
DDD,UML,DOA競演モデリング発表会2<第40回IT勉強宴会in大阪> - IT勉強宴会blog
発表の録画(YouTube)
モデリングの標準問題から学べること: 設計者の発言
発表で利用したスライドと、実装したコードのリポジトリリンクは以下です。
phpmentors-jp/flower-shop
当日の内容については、togetterのまとめをご参照ください。
今回この勉強会で私の現時点のDDDについて発表する機会ができたのは、とてもよかったです。モデリングという視点と経験の豊富な方々のあつまる勉強会で、いろいろなフィードバックも頂けてとても貴重でした。
また、昨日は私を含めて3つの発表があったわけですが、井田さんの問題設定とアプローチや話の組み立て方などものすごく勉強になりますし、TH法の堀越さん、石井さんの話でも使い込んできたパターンの形などとても納得感がありました。井田さん、堀越さん、石井さん、ありがとうございました。
そして毎度このような勉強会の開催にご尽力されている佐野さん、本当にありがとうございました。
3 notes
·
View notes
Text
「ドメインモデリングにおける関数型パターン―仕様パターン」を翻訳しました
書籍「実践ドメイン駆動設計 (Object Oriented Selection)」が出版されて、ドメイン駆動設計(DDD)の知名度が上がってきているようです。
そのDDDに関連する分野の1つとして、DSL(ドメイン特化言語)を挙げることができると思います。
デバシッシュ・ゴーシュさん(Debasish Ghosh)は、DSLのエキスパートで、「実践プログラミングDSL」(原題 "DSLs in Action")という本を出されています。この本の第1章のタイトルは、「ドメインの言葉を話す方法を学ぶ」となっています。
デバシッシュさんのブログ記事のうちの1つ、"Functional Patterns in Domain Modeling - The Specification Pattern" に惹かれて、翻訳をしました。翻訳記事の公開について、著者ご本人から快諾頂けたため、以下に掲載させて頂きます。
ドメインモデリングにおける関数型パターン―仕様パターン
原文URL:http://debasishg.blogspot.in/2014/03/functional-patterns-in-domain-modeling.html
2014年3月31日月曜
ドメインをモデリングするときは、ドメインのエンティティと振る舞いをモデル化する。エリック・エヴァンスが書籍「ドメイン駆動設計」で述べたように、焦点を合わせるべきはドメインそれ自体だ。設計、実装したモデルは、ユビキタス言語を語っていなければならない。実装にの都合により、偶有的な複雑性が多々生じても、ドメインの本質を捉えることができるようにするためだ。表現力の豊かなモデルにするには、拡張可能であることも必要である。そして、私たちが拡張性について語るとき、関連する性質として合成性がある。
関数は、オブジェクトよりも自然に合成を行う。そこで、この記事では、ドメイン駆動設計のコアを成すパターンのうちの1つを実装するために、関数型プログラミングのイディオムを用いるー 仕様パターンだ。 仕様パターンで最も多いユースケースは、ドメインのバリデーションを実装することである。エリックのDDD本には、仕様パターンについて次のように書かれている:
仕様の用途は複数あるが、最も基本的な概念を伝えているのは、どんなオブジェクトでも評価して、定義された基準を満たしているかどうかを調べるという使い方である。
仕様は述語として定義され、それによって、業務ルール同士をブール論理を使って互いに連鎖させて結合できるようになる。このように合成の概念があるので、このパターンについて語るときは合成仕様(Composite Specification)として語ることができるだろう。DDDにおける各種の文献では、これをCompositeデザインパターンを使って実装しており、ごく一般的にはクラス階層とコンポジションが用いられていた。この記事では、その代わりに関数合成を使う。
仕様はどこにあるのか?
モデルを設計する際によくある問題として、集約ルートやエンティティのバリデーションを行うコードをどこに置くのか、という話がある。
エンティティの中でバリデーションをするか? これはダメだ。エンティティが肥大化してしまう。同じエンティティのコアでも、コンテキストによってバリデーションを変えたいことがある。
インターフェイスの一部としてバリデーションをするか? JSONを使って、その外側でエンティティを構築するかもしれない。たしかに、ある種のバリデーションはインターフェイスに属すると言えるし、そこにバリデーションを置くことに違和感は無い。
しかし、もっとも興味深いバリデーションは、ドメインレイヤーに属するものだ。業務のバリデーション(あるいは仕様)であり、エリック・エヴァンスが他のオブジェクトの状態に関する制約をのべるものと定義している。 業務ルールとして、エンティティを次の処理へ渡す前にバリデーションをかけなければならない。
単純な例で考えよう。注文(Order)エンティティとそのモデルがあるとする。新しい注文は、処理工程に入る前に、下記のドメインの「仕様」を満たすものとする。
妥当(valid)な注文でなければならず、ドメインが必要とする正当な日付、正当な商品品目、といった制約に従わなければならない。
正しい権限で承認(approved)されていなければならない。処理工程の次段階に進めるのはその場合だけだ。
顧客がブラックリストに載っていないことを保証するため、状態を審査しなければならない。
注文可能かどうかを調べるため、商品品目の在庫を確認しなければならない。
個々の手順に分かれていて、注文の処理工程に沿って順次完了していく。そのようにして、注文の前に、注文実行の用意が整っているどうかを検証する。どこかでエラーがあると、注文は処理工程から外れて、そこで処理が終わる。そして、私たちが設計するモデルは、この順序を知っている必要があるし、手順の一部として経るべき制約はすべて課す必要がある。
注文を変化させるのは手順だけである―ここは重要なポイントだ。すべての仕様は、最初の注文のコピーを入力として受け取り、ドメインルールを検証後、処理工程の次の手順へ進ませて良いかどうか判定する。
実装へ……
私たちがここまでで学んできたことをふまえて、実装に落としこんでみよう。
注文は、少なくとも今回の操作の流れにおいては、不変のエンティティにできる。
すべての仕様は1つの注文を必要とする。これがトリックになっていて、仕様に注文インスタンスを順次渡すことで、APIをシンプルに保つことができる。
関数型プログラミングの原則により、上記の処理手順を式としてどのようにモデル化できるか。結果を最後まで合成可能に保ち、注文完了後の次工程へ渡すにはどうするか(次工程については、いずれまた別の記事で議論しよう)。
すべての関数は似たシグネチャを持っているようだ―私たちは関数をたがいに合成する必要がある。
あれこれと説明や理論を持ち出すよりも、まずは基本的なビルディングブロックを使ってドメインエキスパートととりまとめた内容を実装していこう。
type ValidationStatus[S] = \/[String, S] type ReaderTStatus[A, S] = ReaderT[ValidationStatus, A, S] object ReaderTStatus extends KleisliInstances with KleisliFunctions { def apply[A, S](f: A => ValidationStatus[S]): ReaderTStatus[A, S] = kleisli(f) }
ValidationStatusは、どの関数からも結果として返す型を定義している。それは状態S、または何らかの異���を報告するエラー文字列となる。正確にはscalazで実装されているEither型(右側が正)である。
この実装が優れているのは、すべてのメソッドで注文パラメータが繰り返されることなく処理手順が呼び出されるからだ。そのようにするイディオムの1つとして、Readerモナドが使われる。そして、私たち��すでにモナド―\/はモナドだ―を持っている。そこで、処理結果をモナド変換子を使って積み上げていく。ReaderTがこの作業を受け持つ。結果同士を結びつけてくれるありがたい型としてReaderTStatusを定義する。
次のステップはReaderTStatusの実装で、クライスリと呼ばれる別の抽象を使う。scalazライブラリを使って、クライスリの言葉でReaderTを実装する。実装の詳細に立ち入る話はしないでおく―もし興味があるのであれば、ユージーンによる優れた論文を参照してほしい。
さて、サンプルの仕様はどのようになるのか?
話に入る前に、基本的な抽象を用意する(わかりやすさのため、ごく単純にしてある)。
// 基底の抽象 sealed trait Item { def itemCode: String } // サンプル実装 case class ItemA(itemCode: String, desc: Option[String], minPurchaseUnit: Int) extends Item case class ItemB(itemCode: String, desc: Option[String], nutritionInfo: String) extends Item case class LineItem(item: Item, quantity: Int) case class Customer(custId: String, name: String, category: Int) // 注文のスケルトン case class Order(orderNo: String, orderDate: Date, customer: Customer, lineItems: List[LineItem])
そして、下記が注文オブジェクトの制約を検査する仕様である。
// 基本的なバリデーション private def validate = ReaderTStatus[Order, Boolean] {order => if (order.lineItems isEmpty) left(s"Validation failed for order $order") else right(true) }
これは説明用の例に過ぎないので、ドメインルールが多く含まれているわけではない。 重要なのは、関数を実装するために定義した型をどう使っているかだ。 注文は関数に対する暗黙の引数ではない―それはカリー化される。関数はReaderTStatusを返す。ReaderTStatus自体はモナドである。したがって、別の仕様と併せて処理工程を手順化していくことができる。つまり、決められた順序の要求を、式指向プログラミングの枠組みの中で解決できるのだ。
収集したドメイン知識に基づく仕様はほかにもあった。
private def approve = ReaderTStatus[Order, Boolean] {order => right(true) } private def checkCustomerStatus(customer: Customer) = ReaderTStatus[Order, Boolean] {order => right(true) } private def checkInventory = ReaderTStatus[Order, Boolean] {order => right(true) }
これらを互いにつなぐこと
しかし、ドメインが与える操作順序を表すには、これらのピースをどうつなげば良いのだろう。モデルで合成性による利点を享受するにはどうするのか? これは非常に簡単だ。合成に適した型定義という難しい仕事はすでに終わっている。
以下のisReadyForFulfilmentメソッドでは「合成仕様」を定義していて、for内包表記(for-comprehension)でくるまれた個々の仕様すべてを順番に呼び出している。
def isReadyForFulfilment(order: Order) = { val s = for { _ <- validate _ <- approve _ <- checkCustomerStatus(order.customer) c <- checkInventory } yield c s(order) }
このように、モナディックな結合により、順序立てられた一連の処理を、抽象の合成性を保ちつつ実装することができる。次回は、注文の後工程を合成可能にする方法、エンティティの情報を読むだけでなく可変とするやり方についても見ていこう。もちろん、関数型のアプローチで。
翻訳後記
この記事は、初学者の私が、背伸びをして勉強しながら訳したものです。推敲を重ねましたが、誤訳等が含まれている可能性はあります。もし何かありましたら、ご指摘頂けると幸いです。
関連記事
Practical DDD #1: Specificationパターンの例
ドメインモデルのための型「Domain Kata」を使ってみました
参考
Modegramming Style: Scala Tips / Validation (10) - applicative
実務者のためのかんたんScalaz
6 notes
·
View notes