ドメイン駆動設計の道標
この記事は 2016年 第2のドワンゴアドベントカレンダー、20日目の記事です。
ドメイン駆動設計に関して悩める若者に送るポエムを書いていたら長くなりました。 20日目なはずなのに今日は 12/25 ですが、お察しください。
TL;DR
- ドメイン駆動設計には3つの顏がある
- それは「哲学」「戦略」「戦術」である
- 「戦術」にスポットがあたりがちだが、まず「哲学」とコアの「戦略」から理解する
- プロダクトにおけるドメインモデルの全体像を描いてから「戦術」を検討しよう
- ドメイン駆動設計をどの程度取り入れるかの 「ドメイン駆動設計の適用レベル」について
はじめに
ドメイン駆動設計(DDD)、以前と比較して認知が上がってきたのか、よく「DDD やってるんですか?」 「DDD ってどうはじめればいいんですか?」と聞かれることがあります。そしてこの時にまず話に上がるのが、エンティティ、集約、レイヤードアーキテクチャといった設計パターンの内容が多いです。
DDD は学習しはじめに陥る良く罠として、レイヤードアーキテクチャなどの設計パターンのみ、つまりは一部の「戦術」のみを DDD として認識している事が多くある様に思います。 新しく DDD を取り入れるにあたっては、戦術のみではない DDD のアウトラインを認識し、その上で自身のプロダクトでどこまで取り入れるか、「DDD の適用レベル」を考えられると、DDD を現実のプロダクトにより取り入れやすくなります。
この記事では、DDD 本を読んだもののいまいち消化不良を起こしている様な方に向けて、DDD のアウトライン、そして実践における DDD の適用レベルという考え方について紹介します。
尚、この記事の中心はあくまで私の捉えた DDD の実践思想であり、エリック本人の意図とずれている可能性もあります。
対象読者
- DDD 本を読んだがもやっとしている人
- DDD を取り入れてはいるものの、実は設計手法としか認識していない人
- ! DDD そのものについては詳しく説明をしない為、DDD を全く知らない人向けには書いていません
ドメイン駆動設計とは
DDD について簡単に説明します。 ひとことで言うと「ドメイン(問題解決領域)を中心に据えたソフトウェアの設計哲学、手法」です。 ほぼままですが、重要なのはソフトウェア開発の中心とするのは、技術や UI でもなく、ドメインである、ということです。
では、ドメインとはなんでしょうか。
ドメインは、業務領域と表現されているものも多いですが、最近らしい言い方をすれば、「提供するプロダクトが扱う問題解決領域」のことを指します。
とすると、「提供するプロダクトが扱う問題解決領域を中心に据えたソフトウェアの設計哲学、手法」となるのですが、もう少し具体的に説明するとどの様な定義になるのか。今年開催された DDDEU 2016 というブリュッセルで開かれたイベントでは、著書のエリック本人が DDD の明確な定義は難しいと前置きした上で、DDD とは以下4つの原則からなると説明しています。
What is Domain Driven Design?
- Focus on the core complexity and opportunity in the domain
- Explore models in a collaboration of domain experts and software experts
- Write software that expresses those models explicitly
- Speak ubiquitous language within a bounded context
動画の発言も含め、意訳すると以下の様な意味になるでしょう.
ドメイン駆動設計とは何か?
この「DDD とは何なのか?」という説明から明確に認識すべきは、DDD = 設計手法ではないという事です。 勿論、設計手法の話も含まれますが、DDD の根幹は、「現実の複雑な問題解決領域をどの様にソフトウェアに落としこむか」というテーマの、組織、開発プロセス、設計論に及ぶソフトウェア開発哲学であり、その探求の過程で生まれたモデリングの戦略、戦術のパターン、思想の集まりです。
DDD のコンセプトを正しく理解し、実践、議論を深めるには、よくスポットがあたりがちなレイヤードアーキテクチャ、エンティティなどの設計手法、つまり戦術だけではない事、そして、言及の対象もソフトウェアそのものについてだけでなく、組織、開発プロセス論まで含む、ソフトウェア開発哲学であるという事をしっかりと認識する必要があります。
ドメイン駆動設計の3つの顏
先程、DDD のことを設計手法だけではない、ソフトウェア哲学でもあると表現しました。 DDD には3つの顏があると考えており、それは「哲学」「戦略」「戦術」としての DDD です。
具体的には、DDD の思想は大きく以下の様に分類できます。
- 哲学 … 「現実の複雑な問題解決領域をどの様にソフトウェアに落としこむか」というドメインモデリングと、それを取りまく組織論、開発プロセスをテーマとしたソフトウェア開発の哲学としての側面
- 戦略 … 原典第1部、第4部に代表される、ドメインモデリングに繋げるための戦略レベルのパターン、思想としての側面 (ユビキタス言語/境界づけられたコンテキスト/モデル駆動設計等)
- 戦術 … 原典第2部に代表される、モデルをソフトウェアとして具体的に表現する戦術レベルのパターン、思想としての側面 (エンティティ/リポジトリ/レイヤードアーキテクチャ等) ※ ここでいう戦略、戦術は軍事用語としての抽象度というよりは、原典における概念の区分けに基づいた意味で使っています
このアウトラインを元に DDD というコンセプトを認識していれば、例えばレイヤードアーキテクチャやエンティティといった戦術的パターン = DDD だという誤解もなくなります。戦術は、オブジェクト指向、関数指向などのパラダイムの変化や、環境によって大いに変わりえる為、それ自体は DDD の本質ではありません。
事実、エリック自身も前述した DDDEU 2016 では関数指向プログラミングの登場により、エンティティなど一部パターンは変わりうると話しています。動画内では具体的な言及が少なかった為、どの様な変化を想定しているかは不明ですが、おそらく Actor モデルで集約が表現できるとかそういった意味だと思います。
まとめると、「哲学」「戦略」としての DDD を理解し、自プロダクトにおけるドメインモデルの全体像を描いてから、はじめて「戦術」を検討すればよいのです。DDDEU などの海外の動向を見ていると、エリックの哲学や戦略を引き継ぎながら、より発展した戦術の議論に進んでいる様に思います。
ドメイン駆動設計の適用レベル
この3つの軸で DDD を捉えると、哲学、戦略面では、思想自体の汎用性は高くあらゆるプロダクトに取り入れる事はできます。
仮に戦術を完全には採用せず隔離されたドメインモデルが構築されていなかったとしても、プロジェクトメンバー内で共通のメンタルモデルが構築されており、そのドメインにおける問題がソフトウェアによって解決できていればそれだけでも大きな意味があります。
勿論、冒頭に紹介した DDD の原則を守っていない状態は厳密には「DDD を実践している」とは言えないでしょう。 が、個人的には、意図的に原則や戦術を捨て「DDD の思想を取り入れる」という考え方でも十分に有用だと考えています。
最後に普段私がプロダクトの設計時に考慮している「DDD の適用レベル」というコンセプトについて紹介します。
レベル1: IA としてのドメイン駆動設計を取り入れる
このレベルでは、幾つかの戦略レベルの思想を取り入れ「ドメインの領域を定め、ユビキタス言語を構築する」までを実施するものです。 大きく以下のパターンを利用してアーキテクチャを考慮します。
これはドメイン駆動設計というよりは、責務の境界線を意識した情報設計(IA)をしましょうというレベルの話です。 毛色は全く異なりますが、こちらの様な本も参考になるかもしれません。
今日からはじめる情報設計 -センスメイキングするための7ステップ
- 作者: アビー・コバート,長谷川敦士,安藤幸央
- 出版社/メーカー: ビー・エヌ・エヌ新社
- 発売日: 2015/10/22
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る
ここで重要なのはユビキタス言語は「用語集」ではないという事も言えます。 言葉は他の言葉との関係性により、意味が決まります。その為、二次元的な用語集ではなく、それらの関係性が分かるダイアグラムもつくるのが望ましいです。
レベル1は、「モデルとコードが一致していること」は重視せず、プロダクトのメンタルモデルの形成と、そのモデルによる一貫性あるユーザー体験の提供を重視し、語彙の統一はしつつも、ソフトウェアは問題解決のソリューションとしてのみ機能させるイメージです。
レベル2: トラディショナルな OOP ベースのドメイン駆動設計を取り入れる
いわば守破離の守。 DDD 原典や、実践ドメイン駆動設計などで触れられている OOP ベースのトラディショナルな DDD を実践するレベルです。 レベル1に加え、大きく以下のパターンを取り入れます。
実践にあたっては DDD 原典だけでは具体性に欠け、コードに落とし込むにはかなりの試行錯誤が予想されます。 実践ドメイン駆動設計など、コードレベルに落としこまれた書籍も参考にすると良いと思います。
実践ドメイン駆動設計 (Object Oriented SELECTION)
- 作者: ヴァーン・ヴァーノン,高木正弘
- 出版社/メーカー: 翔泳社
- 発売日: 2015/03/17
- メディア: 大型本
- この商品を含むブログ (4件) を見る
またこのレベルの注意としては、コードと切り離せなくなる都合上、技術やフレームワークなどとの相性問題、そして実施する場合のコストがどうしても発生します。 Android/iOS での開発、Ruby on Rails での開発、そのプロダクト、ドメインにおいて、戦術レベルでの DDD の厳密な適用が必要なのかよく考える事も重要です。
レベル3: より発展的なドメイン駆動設計を取り入れる
守破離の破、離の段階です。 ドメイン駆動設計の哲学、一部のコアとなる戦略、思想を引き継ぎ、関数指向のアプローチ、イベント指向のアプローチなど、新しい戦術を適用するレベルです。
Event Sourcing, CQRS, Functional / Reactive Domain Modeling, DCI など、他のコンセプトとドメイン駆動を組み合わせる様な実践方法が多く登場しています。
関数指向やアクターモデルを利用した実践例としては、以下の書籍が参考になります。
Functional and Reactive Domain Modeling
- 作者: Debasish Ghosh
- 出版社/メーカー: Manning Pubns Co
- 発売日: 2016/10/24
- メディア: ペーパーバック
- この商品を含むブログを見る
Reactive Messaging Patterns with the Actor Model: Applications and Integration in Scala and Akka
- 作者: Vaughn Vernon
- 出版社/メーカー: Addison-Wesley Professional
- 発売日: 2015/07/13
- メディア: Kindle版
- この商品を含むブログを見る
境界づけられたコンテキスト、コンテキストマップなどの戦略的設計や、集約、ドメインイベントなどの戦術的設計をどの様にすればよいかを、 戦略、戦術という明確な区分けの中での具体的な手法を知りたければこの書籍が参考になります。
Domain-Driven Design Distilled
- 作者: Vaughn Vernon
- 出版社/メーカー: Addison-Wesley Professional
- 発売日: 2016/06/01
- メディア: Kindle版
- この商品を含むブログを見る
まとめ
もし今携わっているプロダクトでメンバー間全く共通の概念がつくれていないと思うのであれば、まずレベル1の IA としての DDD をまずやってみましょう。 少なくとも情報、概念のカオスからは離れ、UI やコードで利用される語彙も整えられてくるはずです。
そして、その上でプロダクトが複雑なドメインを扱う場合や、長期的に保守が予想され技術的な外的変化から中心となるコードを守りたい場合は、レベル2を検討しましょう。
最後に OOP を採用していない場合や、旧来のアーキテクチャで対応できないスケーラビリティを要求される場合など、より発展した活用が必要であればレベル3に挑戦しましょう。
いずれにせよ、DDD の根幹は「現実の複雑な問題解決領域をどの様にソフトウェアに落としこむか、変化させていくか」という哲学であり、このテーマに取りかかったのならあなたも智を求める立派なドメイン駆動の哲学者です。 先人達のパターンを参考にしつつも、具体的な実践方法は自ら模索していくというマインドが必要で、その行為も含めて DDD の実践だと捉えています。
この記事が DDD を実践していく上での助けになれば幸いです。
おわり。
技術的負債を抱えた状態で技術者がすべきこと
この Qiita のエントリに触発され、元文章の目的はさておき、技術的負債についての自分の考えを書いてみようと思う。
技術的負債の定義や、問題は下記エントリを参照して頂くとして、なかでも最後の「技術者がすべきこと」について、「自分だったらこの様にアプローチするか」ということを書く。
技術者がすべきこと
大前提
開発前にステークホルダー(ここではプロジェクトの責任者とする)に、プロジェクトの性質として何を重視するかを認識、選択してもらう。
- 短期的な価値実現を最優先とし、初速を重視、ソフトウェアの健全さ、プロダクトの成長速度を犠牲にする
- 長期的な価値実現を最優先とし、ソフトウェアの健全さ、プロダクトの成長速度を重視し、初速を犠牲にする
1 を選択するという事について、健全でない状態や、成長速度が犠牲になった状態がどの様なものかを、プロジェクトが走り出す前に十分に認識してもらう必要がある。
…とはいえ、人生は 0 か 1 で語れる様な綺麗な世界ではない。
仮に 2 の認識の元、動きだしたプロジェクトであっても、予測できない現象が発生したり、不完全な人によってコードが書かれる限り、負債というものはどうしても発生する。
負債が目の前にある。
ただステークホルダーはその事実を認識していないか、または理解してもらえない様な場合にどうすればいいのか。
負債を明らかにする為に負債の総量を見積る
元記事でも言及されている様に負債には様々な種類があり、モノにより定量化が困難な負債も存在する。
不確実性の高い新規開発において時間ベースの見積りが意味を無さないのと同様に、大量の不可解なコード、存在しないドキュメント等の負債を抱えたプロダクトがチームに与える影響を絶対的な単位で定量化しようともまず無理だろう。
必要なのは、まさに作業をしている感覚を信頼できるチームによる相対的な見積りを行い、感覚ベースでスクラム等で採用されている 1,3,5,8… 等のポイントで負債の大きさを見積る。 そして、今目の前で曖昧になっている「技術的負債」というものを数値で明確化する。
まずは自分達が負債をどの程度抱えているのか、その負債が与えるインパクトがどれ程か、という感覚を定量化し、チームとして負債を認識する事が重要だと思う。
数値の規模感を共有する為にストーリーにする
負債を相対的に数値化したところで、既に共通認識が形成されているか、余程理解あるステークホルダーでない限り、「ひー、80ポイントもあるんだ。今すぐ返済しよう」とは言ってくれないだろう。
最初に必要なのは、やはり技術的負債がどの様なものであり、それがステークホルダーにどの様な影響を与えるか、これを理解してもらいやすい単位でストーリーとして話せる様に整理する必要がある。
※ ストーリーの粒度に関しては、開発内部の負債の整理は細かくやってもいいと思うが、対ステークホルダーの説明としてはかなりざっくり、スクラムでいうエピックレベルでいいのかなとは思う
サービス開発におけるストーリーとは違い、すべてのストーリーに対して詳細に説明する必要はおそらくなく(おそらく負債について全部理解したいという動機がそもそもない)、幾つか内容として理解してもらいやすく、影響が大きいものをピックアップし、それらのストーリーに与えられたポイントの規模感さえ掴んでもらえれば、あとは負債のポイントの総量が明確であれば、「同様の問題がこれだけあるのか…」という風に想像がつきやすい。
もしかすると、理解あるステークホルダーであれば、ここで語られたストーリーの時点で、技術的負債の返済について具体的な計画を話し合える状態になる事もあるかもしれない。
それでもダメならコスト換算する
結局、総量を見積り、ストーリーによって負債が抱える現状と、それが与える影響を共有できたところで、会社として負債を返済することの Go がでないと意味がない。
ありそうなパターンとしてはプロジェクトチーム内ではその問題を共有できても、チーム内だけでは負債を返却するだけの期間をリソースを確保する権限がなく、上層部を説得する様な必要がある場合も容易に考えられる。
その場合は、特定のインパクトある負債を抱えた状態のストーリーをプレゼンテーションするのは同様だが、自分達が見積ったポイントあたりのコストというのも算出し、提示することで、負債がどの様にコスト等のビジネスインパクトを与えるのかを、上層部も納得しやすい理由を作ることができればよいと思う。
コストの算出に関しては、例えば現状のチームのおよそ人件費をベースに、いくつかの負債のストーリーを負債を抱えた状態と、返済した場合の人件費を比較してみて、ポイントあたりのコストを算出し、あとは負債の総量とかけあわせて、負債を抱えた状態で開発を続ける事がいかに経済的な合理性がない事を共有(多少でっちあげる事が)できればいい。
共通認識にする
上記に書いた様な事が共通認識として形成されれば、健全な体制を作るのは比較的容易で、負債のポイントあたりのイメージが形成できている為、開発過程で負債が発生しようが、定期的に技術的負債を見積る、共有するというマイルストーンを設けておけば、あとはサービスに要求されるスピードに応じた返済プランをプロジェクトチーム内で検討できればよいし、「負債のポイントが50ポイントに達したら新しい要求は受けつけられない。返却フェーズとする」という様な決まりでもいいだろう。
もし、それでもまた負債が無視され、スピードだけが求められる様なことがあったら、負債が及ぼす影響のストーリーと、数値化されたその時点での負債の総量を示し、再び負債を返却すう必要がある時期だという事を主張するプレゼンテーションの場が必要かもしれない。
人と人が関わる開発においては、そもそも技術的負債という概念や、負債のポイントあたりの規模感などの共通認識の形成こそが重要で、一旦共通認識が形成されれば、コミュニケーションコストが減り、開発効率は上がり、健全なソフトウェア開発ができるのではないかなと思う。
まとめ
以上、技術者がすべきこと、として考えていることをまとめると、
- プロジェクトを開始する前に、何を優先し、何を犠牲にするか、ステークホルダーに十分に認識を共有する
- 負債を定期的に観測し、ステークホルダーと共有できる単位で数値化し、ストーリーで語り、理解を得る
- 技術的負債における影響と、ポイントあたりの規模感を共通認識とし、閾値などを設けて仕組み化する
というあたりかなと考えている。
捕捉
こんな面倒なことせずとも、話して終わればそれでよい。
ただし、チームで相対的なポイントにより負債の総量を常に把握するのはよいプラクティスだと思う。
Scala で IO コンテキストの共有を implicit 以外で解決する方法 (0)
※注: 解のないチラシの裏です。
Scala でレイヤードアーキテクチャを採用し実装する場合、データベース接続等の IO のセッションをどう引き回すか、というのが問題になる。
単純に引数として引き回せば、全レイヤー貫通して DbSession 等の特定の永続化層への依存が生まれるし、かといって永続化層に隠蔽するとアプリケーション層からの柔軟なトランザクション管理が出来なくなる。
以前、実現の方法に多少の違いはあれど、よくありそうな implicit conversion で解決するパターンの記事を書いた。
この話題は少しだけ ScalaMatsuri 2日目の最後の Scaling Scala というアンカファレンスでも話しにでた。
#ScalaMatsuri DDDでリポジトリパターンを記述する際にConnection/Transactionをインフラ層で制御するが、ContextをImplicitで引き回すところは議論がある。
— Kimura Sotaro (@kimutansk) 2014, 9月 7
「議論がある」。 確かに。
implicit で引き回すのは、宣言の冗長性と、レイヤーをまたがる依存性の観点などで美しくないと思いつつ、基本は implicit でやっている。
何故なら今の知識レベルではその他の方法は知らない & 思いつかないから。
id:xuwei_k さん曰く、それ Reader Monad でできるよ、ただし、Monad Transformer なども定義したりしないといけなかったり、そこまでチームでやるかといったらやらない、という様なニュアンスのことをそのセッション内で言っていた気がする。
※ 全然違ったらごめんなさい
で、キーワードに反応してなんとなく記憶に残っていた ScalaDays 2014 でやっていた Reader Monad に関するセッションについて言及してみたものの…
ScalaDay 2014 で ReaderMonad 使った Repository パターンの実装例があった気がする The Reader Monad for Dependency Injection/ https://t.co/xo3AAIeR8u #ScalaMatsuri
— takuyaf (@tlync) 2014, 9月 7
ちゃんと見たら全然関係なく静的な DI を Reader Monad でやる方法を解説していただけで、セッション中のコード例でも Context は Implicit で引き回す、という現実を間のあたりにした。
※ 下記の Jason さんもセッション中に「なんか良い方法知ってる?」という様なことを言っていた
僕が求めているのは Reader Monad ではないのか、と思いかけたが、以下のリンクを見て、やはり Reader Monad の考え方でいける気がすると思い直した。
https://groups.google.com/d/msg/scalaz/W6jiZUu5jaU/UBUGZjgnW7MJ
Strictly speaking, you should be using an IO-like Monad to do your database code. But that's OK because you can use ReaderT[IO, Config]
Typically I would define something like (if I understand you correctly):
case class Config(controllers: Controllers, services: Services, repos: Repositories)
Then run your program thru Reader[Config, _]. If you do use ReaderT with IO, then it's useful to do something like this:
type Program[+A] = ReaderT[IO, Config, A]
You can then define type constructors in a module:
object Program { def pointA = .... }
上記の URL に記載されているが、次の新しい道を差し示してくれるヒントが下記の動画かもしれない。
Teaching an old dog new tricks: wrapping an imperative API in a functional one https://skillsmatter.com/skillscasts/4943-teaching-an-old-dog-new-tricks-wrapping-an-imperative-api-in-a-functional-one#showModal?modal-signup-complete
説明では、この人が勝手にだろうけど Trampolining IO と呼んでいて、結局 Applicative functor のことらしいが、まだ見てないので実際のとこなんなのか良く分かってない。
何か進捗あれば次の記事 (1) を書く。
ScalaMatsuri 2014 で「国技と Scala」というタイトルで発表しました
国技と Scala (Japan's national sport and Scala) // Speaker Deck
ドワンゴグループにおける、ニコニコ以外の Scala 開発事例として、日本相撲協会公式アプリのバックエンドに Scala/Play Framework を利用した事例について発表してきた。
言いたかったのは主に2点
- 長期運用の可能性があり、1万行越えだす様な規模であれば、静的型付けな環境は高い保守性と心の平安をもたらす
- Scala のプラクティス、ノウハウなどで、既にやっている人にとっては常識となっている様な暗黙知も積極的に公開していき、Scala のエコシステム、コミュニティをもっと盛り上げていきましょうということ(自分もふくめ)
相撲という限られたマーケットであり、スケールさせる為にはとか、分散処理とかの Scala っぽい Reactive Programming 方面な話しではなく、単純に Scala/Play をスマートフォンアプリ開発のバックエンドとして利用した事例について発表した。 その開発する中で考えた設計判断、プラクティス、ライブラリの所感について触れ、基本的には、運用が必要なシステムで1万、2万行越えだすと、静的型付けであることによる保守性の高さは、結果的にコンパイル時間等を払拭できるほどの安全性、生産性、心の平安を生むと思っていて、要は静的型付けである事は非常に価値があって、特にテストが難しいテンプレート(twirl)の静的型付けは素晴しいという事を言いたかった。
また、昔から Scala コミュニティで情報共有している方にとっては当たり前になっているであろうけど、新参者は調べても良く分からない…といった、Scala での例外処理、無停止デプロイや静的コード解析などについても触れ、自身の現時点の考えとして、この様にやればよいのではないか、と考えているプラクティスを紹介した。 そして、話した内容というのはやはりあまり表に出ていない情報が多いので、Scala 新参者は得たノウハウや書いたコードを積極的に書いて、Scala コミュニティやエコシステムの更に発展させる為にもっと盛りあげていきましょうということを偉そうに言った。
ScalaMatsuri スタッフの皆さん、本当にお疲れ様でした & ありがとうございました!
Play JSON Tips
この記事は Play framework 2.x Scala Advent Calendar 2013 の19日目の記事です。
昨日登録状況を見たらまだ空いてる & 下記のツイートを見て某せらさん無双もそろそろキツそうだったので衝動的にポチりました。後悔はしていません。
明日は誰かお願いします... - Play ドキュメントを Skinny で書くと - The template engine - seratch http://t.co/Bj4aoRhaBY
— Kazuhiro Sera (@seratch) 2013, 12月 18
そして人生において割と飲み会の優先度が高いのでこの記事も既に24時を回っております。すいません。
そんな状況でネタがないので Play JSON まわりの自分が困った小ネタの紹介です。
日付を ISO8601 形式で出力する
日付型で一般的に使われている JodaTime の DateTime を JSON 出力すると、デフォルトで Unix Timestamp なのですが、ISO8601 形式で出力したい事もあります。 Play JSON の Writes.scala に jodaDateFormat という関数が定義されており、それを使えば簡単にできます
import play.api.libs.json._ import org.joda.time._ print(Json.toJson(new DateTime(2013, 12, 19, 0, 0))) // -> 1387378800000 implicit val w = Writes.jodaDateWrites("yyyyMMdd'T'HHmmss.SSSZ") print(Json.toJson(new DateTime(2013, 12, 19, 0, 0))) // -> "20131219T000000.000+0900"
Writes には他にも jodaLocalDateWrites なども定義されているので、他の日付系の型も同様の方法で対応できます。
値クラスをオブジェクトでなく単一の値として出力する
論理的に単一の値しか持たないものであっても、Scala では型安全性の為や他の理由から値クラスとして定義する事があります。 その場合、普通に JSON コンビネータを使うと JSObject が生成されてしまいますが、以下の様にすれば単一の JsValue として出力できます。
import play.api.libs.json._ case class UserId(id: Int) extends AnyVal case class User(id: UserId) implicit val userIdJsonWriter = Writes{ (userId: UserId) => JsNumber(userId.id) } implicit val userJsonWriter = Json.writes[User] Json.toJson(User(UserId(1))) // -> res0: play.api.libs.json.JsValue = {"id":1}}
え、何が変わってるか分からないという方向けに普通にやるとこうなるよという例が以下。
import play.api.libs.json._ case class UserId(id: Int) extends AnyVal case class User(id: UserId) implicit val userIdJsonWriter = Json.writes[UserId] implicit val userJsonWriter = Json.writes[User] Json.toJson(User(UserId(1))) // -> res0: play.api.libs.json.JsValue = {"id":{"id":1}}
尚、例では値クラスで書いていますが、シングルフィールドなのが良く値クラスであるだけであり、仕組み的には値クラスでなくても、あるクラスインスタンスを単一の JsValue として出力したい時に使えます。
いずれの例ももっとおいしい書き方があるかもしれません。
教えてください。
明日の Advent Calendar は今のところ /dev/null さんです。 (誰もいない)
Scala における Repository の実装パターンを考える -模索篇-
この記事は Scala Advent Calendar 2013 12/11 の記事です。
昨日は @chiral さんの Sprayの簡単な紹介 - アドファイブ日記 でした。
ちゃんと見ましたか? 見てね!
で、この記事は Scala で DDD の Repository の実装を書く場合どう書けばいいのか、その一例を考えてみたというお話しです。
(主軸が Scala より DDD な気がするがキニシナイ)
ただし、書いてる人の関数型成分含有率は 1% 位な為、関数型ガチ勢から見た時いろいろ思うところがある可能性は高いです。 マサカリお待ちしております。
DDD 関連の概念については特に説明しないので適当にググってください。
実現したい要件
Repository の実装パターンを考えるにあたり、達成したい要件としては以下の2点。
1. ドメイン層を特定の永続化技術から独立させる
Repository というのは I/O が絡む為、特に何も気にせず設計すると、どうしても I/O 関連の技術詳細への依存が生まれます。
DDD 本で Eric 神は理想の Repository 実装として以下の様に言及しています。
理想は、クライアントから(クライアントの開発者からではないが)内部の働きをすべて隠蔽し、それによってクライアントコードが、データの格納箇所に関わらず同じになることだ。 … 略 … 格納と取り出し、および問い合わせの仕組みをカプセル化することが、リポジトリの実装における基本的な機能である。
この辺りをどう実現するかは Implementing Domain Driven Design や ddd-sample 等でも言及、紹介されているのでさらっと流しますが、依存性逆転の原則を適用して、ドメイン層はインタフェースのみ依存する様にするのが良く見るパターンです。
ScalikeJdbc を利用した Repository を例にするとこんな感じ。
一見すると上手く分割できた様に見えますが、これにはまだ明らかな問題があります。
セッション管理が Repository 内で閉じている為、複数のリポジトリをまたがるトランザクションを管理する事ができません。
1 を実現したが故に、純粋無垢な Repository のインターフェースを介して、どの様にトランザクション管理をすればよいか、という新たな課題が生まれた訳です。
2. トランザクション管理をアプリケーション層から行う
DDD 本で Eric 神はトランザクション管理についても以下の様に言及しています。
リポジトリはデータベースに対する挿入と削除を行うが通常は何もコミットしない。例えば、保存した後にはコミットしたくなるが、おそらくクライアントには、作業ユニット(unit of work)を正しく開始し、コミットするためのコンテキストがある。トランザクション管理は、リポジトリを手を出さないでいる方が単純になる。
この記事は模索編ですし、Eric 神も Repository の実装はインフラストラクチャ層により多岐に渡ると言及している事もあり、正直なところ Scala と、今 Scala 界隈に存在するライブラリでどの様にするのが実現するのが良いかはまだ見えてません。
ただ、ひとつのやり方として Implicit Parameter を使えば、Boilerplate なコードによる冗長性を可能な限り排除しつつも、ドメイン層のインターフェースは永続化の詳細を抽象化したままアプリケーション層からトランザクション管理をする事ができます。
コードにするとこんな感じです。
なんかこんなんでいいんだろうか感はありますが、一応 1,2 共に要件は達成できました。
ただ、まだこれも問題があってトランザクション管理の仕組みは ScalikeJDBC と密結合になっている為、アプリケーション層のコードは永続化の詳細から切り離せていません。
アプリケーション層からも永続化の詳細から完全に切り離すには、トランザクション管理の抽象レイヤも作ればいいとは思いますが、トランザクションのブロック内だけを見ればインフラ層の永続化詳細から切り離せているので、このレベルで割り切るのもありな気はしてます。
最後に重要なことを言っておくと、この様な Implicit Parameter で環境を引き回すのは id:xuwei さんが定期観測されている関数型ガチ勢的にはアンチパターンな様です。
関数型ガチ勢から見たScalaのアンチパターン - scalaとか・・・
正直どんなデメリットや、代替手段があるかコードレベルで全然思いつかないので、そろそろ Monad っておいしそうな名前だよねとか言っている場合じゃないと実感します。
所感
- DDD は若干ポエムなのでコードレベルに落としこむのが大変。で、Java ベースではなく、今ある技術や考え方を使ってシンプルかつエレガントに実現するプラクティスやパターンを共有する必要性を感じる。
- Implicit の多用は良くないのは分かっているけど、各レイヤーのグルーとして使うのはアリだと思っている。
- Scala はプログラマの考え方を広げてくれる素晴らしい言語
なんか最後の方は無理矢理感がありますが、続編はすごいH本をちゃんと理解した後にまたやるかもしれません。 明日は k4200@github さんです!
終わったー! 酒だー!! (現在時刻 12/11 26:30)
Play 2.2 と ScalaTest 2.0 を使う
Play デフォルトのテスティングフレームワークは Specs2 だけど、理解のしやすさ、シンプルさに欠けていて、API の醜さみたいなものが気になってしまったので、よりクリーンな API を保っている ScalaTest が使えるか試してみた。
結果としては、依存性に追加するだけで特に問題なくいける。
以下、内容は適当なテストコードと build.sbt。
ScalaTest 2.0 RC1 with Play 2.2
Jenkins と連携する時も、build.sbt で指定している -u の方の JUnit 形式の XML を指定すれば OK。
Functional Test として場合は running(FakeApplication()) が美しくないのでなんとかしたいところですね。