都元ダイスケ IT-PRESS このページをアンテナに追加 RSSフィード

最近は会社ブログしか書いてません。

2011-04-07

[][]僕と地豆DDD

Domain-Driven Design: Tackling Complexity in the Heart of Software

Domain-Driven Design: Tackling Complexity in the Heart of Software

Domain-Driven Design: Tackling Complexity in the Heart of Software というソフトウェア設計に関する本がある。

この設計の考え方は、略してDDDと呼ばれたが、同時に「DDD難民」という言葉さえも生み出したらしい。ハードカバーによる560ページ、6000円を超える、洋書である。今思えば怖い物知らずだった。知らなかったとは言え、よくもまぁこの本を手にとったものだ…。

この本との出会いは、2009年9月に遡る*1。当時も相変わらず、Jiemamyの設計に頭を悩ませていた。この頃、自分は既にオブジェクト指向という考え方に傾倒していたのだが、正直なところ、現実に直面する "ソフトウェアの複雑さ" に対しては、オブジェクト指向のパワーだけでは太刀打ちできない、ということにも薄々感づき始めていた頃でもある。

そんな悩みを、コミッタ間で共有してはいたのだが、やはり思うように進まない。そんな時、「大ちゃんは、きっと(DDDの考え方が)好きだと思うよ」と、この本を薦めてくれたのが id:ashigeru である。

俺は当時、洋書なんて一冊も持っていなかった。英語と言えば、Web上で仕方なく調べるドキュメントの類がせいぜいだった。ただ、だんだん「自分の知りたい情報は、日本語で書かれていない」という事実に気づき*2始めていた自分は、やっぱり英語ももっと読めなきゃなぁ、とも考えていた。

そんな経緯で、初めて手に入れた洋書、それがDDD本であった。この本を読む目的は「DDDの考え方を身に付ける」ことと「英語の自信をつける」ことだったわけだ。

結果から言おう。DDD本は、初めて手にする洋書としてはお薦めできないw 内容が難しい上に、話の流れも難しい。なぜ突然裁判に訴えられた話が出てくるのか*3、理解するのに数時間かかった。普通だったら絶対に挫折する。自画自賛も甚だしいが、根性で読んだ俺エライわ…。かなり時間かかったけど。まぁ、その分自信も実力も付いたので、根性大好きな人には勧めてもいいかもしれないw この経験のお陰で、他の本が簡単に読める気がする。

しばらく読み進めて、id:ashigeru の思惑通りDDDが気に入った俺は、今度は上司 id:j5ik2o にこの本を買って読ませる、という暴挙に出る。この本の内容を理解して、俺と共感してくれる変態はこの人しかいないだろう、と。まぁ、思惑通り上司はこの本に魅了され、翻訳レビュー謝辞にまで名を連ねるに至った。

まさに

f:id:daisuke-m:20110407151615j:image:h500

俺は俺で、JiemamyDDDの考え方を参考にしながら設計し、v0.3を作ったりした。あまり知られてはいないが、Jiemamy Project では dddbase というライブラリがあったりする*4。また、著者のEric EvansがDDDのexampleとして作ったTime And Money Libraryをフォークし独自にメンテして、baseunits ライブラリとして整備したりもしてみた。このように、自分が手がけたプロダクトの設計と実装に計り知れない影響を与えたことは間違いない。

まぁ、多くの人にとって、DDD本を読む目的は英語慣れではないだろう。ただでさえ難しい設計実践の書籍である。慣れ親しんだ日本語という言語で的確に意味を掴みながら理解を進めると良いだろう。

間もなく、(色んな意味で)破壊力のあるこの書籍の邦訳版が世に出る。邦題は「エリック・エヴァンスのドメイン駆動設計」である。前述のように「オブジェクト指向が好きだが、それだけでは何か足りない」と感じている、悩める開発者にお薦めしたい。

一足お先に献本頂きました。どうもありがとうございます。

*1:今、Amazonの原著ページを参照したら、親切にも「お客様は、2009/9/30にこの商品を注文しました」と表示されていた。

*2:この事実を実感する第一段階は、ググったら自分が出てくる現象であるw

*3:出てくるんすよ、突然…w

*4:汎用ライブラリなので、どこで使ってみても良いかもね。

2010-12-22

[][]Baseunits Library

さて、Java Advent Calendar -ja 2010 : ATND 10日目。昨日は、id:yuroyoro でした。二日連続で真っ黒な魔術が紹介されたので、ここは真っ白で実用的な奴をひとつ。

最近Domain Driven Design(DDD)っていう設計手法が、自分の周辺一部で話題になっている。当然、賛否両論なんだけども*1、個人的には好きな考え方でして。ま、詳細は色々な方がブログに書いているので割愛します。興味あれば本読んでみましょう。洋書*2だけどw

Domain-Driven Design: Tackling Complexity in the Heart of Software

Domain-Driven Design: Tackling Complexity in the Heart of Software


で、前置きはここまで。本題いきます。

Javaで「時間」や「日付」を扱う時、みんなどうしてます? まぁ、大抵 java.util.Date とか java.util.Calendar を使うんじゃないかな、と思います。でもこのJavaの時間ライブラリっつーのは、意外とあちこちでdisられがちです。Deprecatedなメソッドだらけだし、Mutable(可変)だったり、継承関係が無茶だったり。まぁ正直使いにくい。

例えば2010年12月22日っていう暦日(日付)を扱いたいだけなのに、「2010/12/22 00:00.00 を java.util.Date で扱って時間部分を無視」とかしてませんか? 時間要素のない、単なる「暦日(ex. 2010/12/22等)」クラス欲しくないですか? 逆に日付要素のない、単なる「時刻(ex. 16:10等)」クラス欲しくないですか? Immutable(不変)なクラスとか欲しくないですか? 「期間(ex. 2010/09/20〜2010/12/22等)」とか「時間量(ex. 3分間, 4ヶ月間等)」クラス欲しくないですか?(ry

昔から、なぜApache commons-dateみたいなライブラリが無いのか、不思議でならんかった。まぁ今でも不思議だ。commons-langにDateUtilsってクラスがあって、少々便利なユーティリティを提供してくれているけど、まぁ全く根本的な解決になっていない感じ。

しかしまぁ、このイケてなさはJavaユーザの間ではほぼ共通認識ではあるようで。JodaTimeやら、それを元にしたJSR-310やらで、日時関連のAPIを整備しようという流れはあるわけです。ただ、このJodaTimeも結構曲者。いわゆる過モデリングなんだと思いますが、あらゆる可能性とニーズに対応しようとするあまり、通常は要らない概念がもりもり入っていて、まぁそれはそれで使いにくいらしい。使ったことないけど。

で。突然だがTime and Money LibraryっていうOSSプロジェクトがあるのです。Javaにおける時間及び金額をモデリングしたクラスライブラリだ。DDDの実例コードという名目で世に出たコード(DDD著者=Time and Money作者=Eric Evans)だけど、個人的に、普通にライブラリとしても有用なコードだと感じた。

しかし残念なことに、Domain Language, Inc.のページを見るところによると「このプロジェクトはもはやアクティブではない(Although this project is no longer active)」と明記されちゃっている。実例コードであることが大事で、実際に使うことをもう既にあまり意識しておらず、メンテナンスを続ける感じじゃないのだと思う。

あぁ、もったいない。中身を見てみると、DDD的に深く考えぬかれた設計が満載ではないか。ということで、このコードをいろいろいじくってみた。

まずね、Javadoc無いとかあり得ないから。スザケンナって思いながら全部にひと通りドキュメントつけました。日本語*3ですけどね。俺が意味を取り違えていて、間違ったことを書いているかもしれないし、ぶっちゃけ未だに意図がよくわからないクラス*4もあるw しかし、それでもとても便利だと思う。

あと、元コードはJava1.4対応だったのでジェネリクス使ってなかったんですね。で、自分なんかはJava5が出た後にJavaを始めた人間なんて、ジェネリクスの無いコードなんて怖くて触れねーよ、的なビビリな訳ですよ。というわけで必要なモンに型パラメータつけました。

また、メソッド名に統一感がない部分があったり、テスト書き足してみたり、FindBugs掛けてみたら赤いところがバグってたので直してみたり。あ、あんな機能欲しいww とか、普通にクラスも追加したりしてます。オーバーホールっていうか、ずいぶん改造しやがったな、というのが近いかもしれない。

というわけで、再構成したものを派生プロダクトとして公開します。あー、OSSってすばらしい。そしてこの派生プロダクトはBaseunits Libraryと名づけますよ。みんな覚えといてね!

まぁ、論よりコードだな。

基本的な日付演算

元プロダクトが舶来なので、アメリカーンな例ですが。

// 暦日仕様。ある暦日がその仕様を満たすかどうか、とかチェックできる
// ここでは「毎年1/15」という仕様、つまりここではキング牧師の誕生日。
// (他にも「毎月第二木曜日」とかもいける。
//  さらに、その日が祝日だったら前営業日ずらしとかも可能。)
DateSpecification mlkBirthday = DateSpecification.fixed(1, 15);

// で、2005/1/15という日付のインスタンスを作る
CalendarDate jan15_2005 = CalendarDate.from(2005, 1, 15);
// この日はMLKの誕生日ですか? → true
assertThat(mlkBirthday.isSatisfiedBy(jan15_2005), is(true));

// 2005年の中(期間=CalendarInterval)で、キング牧師の誕生日を引っ張って来る。
// (仕様を満たす暦日を探す)
CalendarDate mlk2005 = mlkBirthday.firstOccurrenceIn(CalendarInterval.year(2005));
assertThat(mlk2005, is(jan15_2005));

// 誕生日〜逝去日の暦日期間をつくって、その範囲で仕様を満たす暦日を順次取得するイテレータ
CalendarInterval mlkLifetime = CalendarInterval.inclusive(1929, 1, 15, 1968, 4, 4);
Iterator<CalendarDate> mlkBirthdays = mlkBirthday.iterateOver(mlkLifetime);
assertThat(mlkBirthdays.next(), is(CalendarDate.from(1929, 1, 15)));
assertThat(mlkBirthdays.next(), is(CalendarDate.from(1930, 1, 15)));

// っていうか、そもそもキング牧師って何日間生きたの?
assertThat(mlkLifetime.length(), is(Duration.days(14325)));

営業日を考慮するカレンダー

何に使うかピンと来ない? んじゃ、日本のビジネスに密着した例いってみましょう。

BusinessCalendar calendar = new BusinessCalendar();

// 祝日の登録(この登録は、JapaneseBusinessCalendar とかサブクラスつくってコンストラクタで処理しちゃうといいかも)
calendar.addHolidaySpec(DateSpecification.fixed(1, 1)); // 元旦
calendar.addHolidaySpec(DateSpecification.nthOccuranceOfWeekdayInMonth(1, DayOfWeek.MONDAY, 2)); // 成人の日
calendar.addHolidaySpec(DateSpecification.fixed(2, 11)); // 建国記念日
calendar.addHoliday(CalendarDate.from(2010, 3, 21)); // 春分の日(毎年違うので、ひとまず今年分を単独で登録)
calendar.addHolidaySpec(DateSpecification.fixed(4, 29)); // 昭和の日
calendar.addHolidaySpec(DateSpecification.fixed(5, 3)); // 憲法記念日
calendar.addHolidaySpec(DateSpecification.fixed(5, 4)); // みどりの日
calendar.addHolidaySpec(DateSpecification.fixed(5, 5)); // こどもの日
calendar.addHolidaySpec(DateSpecification.nthOccuranceOfWeekdayInMonth(7, DayOfWeek.MONDAY, 3)); // 海の日
calendar.addHolidaySpec(DateSpecification.nthOccuranceOfWeekdayInMonth(9, DayOfWeek.MONDAY, 3)); // 敬老の日
calendar.addHoliday(CalendarDate.from(2010, 9, 23)); // 秋分の日(毎年違うので、ひとまず今年分を単独で登録)
calendar.addHolidaySpec(DateSpecification.nthOccuranceOfWeekdayInMonth(10, DayOfWeek.MONDAY, 2)); // 体育の日
calendar.addHolidaySpec(DateSpecification.fixed(11, 3)); // 文化の日
calendar.addHolidaySpec(DateSpecification.fixed(11, 23)); // 勤労感謝の日
calendar.addHolidaySpec(DateSpecification.fixed(12, 23)); // 天皇誕生日

// それぞれの日が「営業日」にあたるかどうかチェック。
assertThat(calendar.isBusinessDay(CalendarDate.from(2010, 10, 8)), is(true)); // 金曜日
assertThat(calendar.isBusinessDay(CalendarDate.from(2010, 10, 9)), is(false)); // 土曜日
assertThat(calendar.isBusinessDay(CalendarDate.from(2010, 10, 10)), is(false)); // 日曜日
assertThat(calendar.isBusinessDay(CalendarDate.from(2010, 10, 11)), is(false)); // 月曜日だけど体育の日
assertThat(calendar.isBusinessDay(CalendarDate.from(2010, 10, 12)), is(true)); // 火曜日

assertThat(calendar.isBusinessDay(CalendarDate.from(2010, 11, 22)), is(true)); // 月曜日
assertThat(calendar.isBusinessDay(CalendarDate.from(2010, 11, 23)), is(false)); // 火曜日だけど勤労感謝の日
assertThat(calendar.isBusinessDay(CalendarDate.from(2010, 11, 24)), is(true)); // 水曜日

// 振替休日(「国民の祝日」が日曜日にあたる場合、その直後の「国民の祝日」でない日を休日とする)とか、
// 国民の休日(「国民の祝日」と次の「国民の祝日」の間隔が中1日しかなくその中日(なかび)が
//「国民の祝日」でない場合、その日を休日とする)
// なんかには、まだ対応していないけど、DateSpecificationを上手く実装すればどうにかならんかな、と思っている。

// 2010/10/1〜2010/11/30までの間で、営業日だけを返すイテレータ
Iterator<CalendarDate> itr =
        calendar.businessDaysOnly(CalendarInterval.inclusive(2010, 10, 1, 2010, 11, 30).daysIterator());

// 1つ1つ検証するの大変なので、とりあえず文字列にしちゃうよ
StringBuilder sb = new StringBuilder();
while (itr.hasNext()) {
    CalendarDate calendarDate = itr.next();
    sb.append(calendarDate).append(" ");
}

// 土日祝日を除いた日が列挙されている
assertThat(sb.toString(), is("2010-10-01 " +
        "2010-10-04 2010-10-05 2010-10-06 2010-10-07 2010-10-08 " +
        "2010-10-12 2010-10-13 2010-10-14 2010-10-15 " +
        "2010-10-18 2010-10-19 2010-10-20 2010-10-21 2010-10-22 " +
        "2010-10-25 2010-10-26 2010-10-27 2010-10-28 2010-10-29 " +
        "2010-11-01 2010-11-02 2010-11-04 2010-11-05 " +
        "2010-11-08 2010-11-09 2010-11-10 2010-11-11 2010-11-12 " +
        "2010-11-15 2010-11-16 2010-11-17 2010-11-18 2010-11-19 " +
        "2010-11-22 2010-11-24 2010-11-25 2010-11-26 " +
        "2010-11-29 2010-11-30 "));

業態によるカスタマイズも結構できる感じ。

// 不動産屋さんって、日曜祝日も営業してるけど、水曜日はお休みだよね
// この場合isHolidayがtrueでも、isBusinessDayがfalseだとは限らない
BusinessCalendar estateCalendar = new BusinessCalendar() {

    @Override
    public boolean isBusinessDay(CalendarDate day) {
        Validate.notNull(day);
        DayOfWeek dow = day.dayOfWeek();
        return dow != DayOfWeek.WEDNESDAY;
        // デフォルト実装は return isWeekend(day) == false && isHoliday(day) == false;
    }
};

その他

自分が注目してるのは時間ライブラリの方なので、金額は省略。まぁ、同じノリですよ。

まぁ、こんな事やってたから「区間」があーだこーだとか言い出した訳なんですね、俺。

基本情報

使ってみようか、と思ったらこちら。

v1.x系 v2.x系
プロジェクトサイト http://maven.tricreo.jp/site/baseunits http://maven.xet.jp/site/baseunits
Mavenリポジトリ http://maven.tricreo.jp/release http://maven.xet.jp/release
Maven groupId jp.tricreo jp.xet
Maven artifactId baseunits baseunits
ソース置き場 https://github.com/tricreo/baseunits/ https://github.com/dai0304/baseunits/
ライセンス Apache License v2.0 Apache License v2.0

今後

今はひとまず v1.0 をリリースしてあります。単体テストも結構書きましたし、今後もきっちりメンテナンスを続けていく予定*5です。というわけで比較的安心して長期的に使ってもらえるライブラリだと思います。

ただ、今の時点でかなり安定したプロダクトだと思うので、みんながイメージするOSSほど頻繁なアップデートは無いかもしれません。決してメンテナンスしないという意味ではなく、すでに結構成熟しているので触る必要性が小さい、という意味で。

しかし、バグや要望などありましたら、どしどしお寄せください*6バグ対応はもちろんのこと、要望に関しても方針と矛盾せず、比較的対応が楽なものであれば早めの対応をしたいと思っています。

まぁ、githubなんで、適当にいじってプル・リクエストくれても良いですし。臨機応変に対応します。このエントリのコメントでもOK。ML等は盛り上がったら考えます。クローズなやりとりをお望みでしたら個人的にメールでも。

まとめ

Immutable重要。ということで、明日は最速Eclipse魔術師id:Yamashiro0217 です。

*1:正直、ヘビーウェイトな考え方だとは思う。

*2:訳本情報もあります。 http://twitter.com/#!/kohsei/status/17106115712000000

*3:たまにオリジナルの英語docが残ってるけど。

*4:moneyパッケージ内のクラス。

*5:万一会社がこのプロダクトを放り出しやがったら、Jiemamy Projectが受け皿になりますw

*6:基本方針もあるので、全てに応えられる訳ではないですが。