男爵が書く

DDD、オブジェクト指向、技術書の感想など

ユースケースシナリオを中心に据えたドメインモデルの育て方

この記事はドメイン駆動設計 Advent Calendar 2020 - Qiitaの19日目です。 昨日はたなかこういち (@Tanaka9230) | Twitterさんの『DDDに関する論の主戦軸を整理してみた(2020年版) - Qiita』でした。

今日はドメイン駆動設計における主要な関心事の一つである「いかにドメインモデルを作って、育てていくか」という話を書きたいと思います。

エリック・エヴァンスの『ドメイン駆動設計』やヴォーン・ヴァーノンの『実践ドメイン駆動設計』には、ソフトウェアが扱う問題領域に存在する知識やルールをソフトウェアの要素(クラスなど)で直接表現することの価値とその実践方法が書かれています。ドメインモデルは問題領域に対する理解を表現したものであり、まずこれがなければドメイン駆動設計のプラクティスは実践できません。では開発チームがドメインモデルに到達するためのプラクティスにはどのようなものがあるのでしょうか?

その答えは世の中にいくつもあると思いますが、今回紹介するのは「ユースケース」を中心に据えた方法論です。ユースケースと聞いて多くの人が思い浮かべるのはユースケース図かもしれませんが、ここで重要となるのはユースケースシナリオ(ユースケース記述)と呼ばれる文章表現のほうです。ドメイン駆動設計はユビキタス言語を認識して言葉を大事にする設計手法です。ユースケースシナリオは自然言語で書かれるので、ユビキタス言語を直接用いてシステムの仕様を表現することができます。

自然言語で書かれているので、エンジニア以外でもほとんど予備知識無しに読み解くことが可能です。ドメイン駆動設計が開発者の中で完結した手法ではなく、顧客やプロダクトオーナー、ビジネスメンバーを巻き込んで行う必要がある以上、設計ドキュメントが誰にでも理解できる形式になっていることが非常に大きな意味を持ちます。ユースケースシナリオを中心に据えた手法は、ソフトウェアに関わる全ての人を設計の深いところまで連れて行くことができるのです。この性質はビジネス領域とテクノロジー領域を行き来して進化していくドメイン駆動設計をスムーズに回していく助けとなるはずです。

それでは、筆者が自分の現場でどのように実践しているのかを順を追って説明していきます。

続きを読む

ScalaMatsuri 2020でPofEAAについて話しました

去る10月の17日、ScalaMatsuri 2020で登壇しました。今年はオンライン開催だったため、自宅とコワーキングスペースからZoomとDiscordでの参加。オンラインでの大型カンファレンスは参加するのも登壇するのも初めてだったのですが、オンラインならでは工夫が随所に凝らされていて存分に楽しめました。

scalamatsuri.org

CfPが採択されたのが今年の3月末で、そこから登壇資料の作成やスタッフの方との接続テストを行ってから当日を迎えました。会場までの移動時間がかからないので、開会の挨拶にも余裕を持って参加できました。

お昼は近場の行きつけのカフェでランチ。スマホかとじゅんさんのセッションを視聴しながら食事を摂れるのも、オンライン開催ならではですね。

自分は15時からの40分枠で、『PofEAAで考えるSaaSバックエンドの作り方』と題して発表させていただきました。ScalaMatsuriは海外からの参加者も多い国際カンファレンスなので、拙い英語でなんとか資料を作りました。最初から英語で考えて、後から日本語訳をつけるほうが案外スムーズに進みました。Google翻訳で「英語→日本語」の変換をチェックしながらの作業でした。

speakerdeck.com

PofEAAネタはPHPカンファレンス福岡2018でDoctrine2の話をして以来です。今回はORMだけでなく、アプリケーション全体に様々なパターンを適用するお話です。ほとんどがAlpで実践した内容となっております。オフラインのカンファレンスのようなダイレクトな反応は味わえなかったのですが、Discordに質問用と雑談用のルームが用意されており、話しながらリアクションを確認できるのは良かったです。

お楽しみの懇親会では、日頃ScalikeJDBCでお世話になっている@seratch_jaさんや、最近何かとご縁のある@todokrさんなど、Scala界隈で活躍する方々のお話が聴けて貴重な時間でした(ほぼ聴き専と化していました)。Discordの部屋を大量に作って任意のところにいって会話するというやり方は、懇親会会場で「輪」に入っていくあの感覚に近いものがあったと思います。

今回は準備期間中に大きな方針転換を迫られ、スタッフやスポンサーの方々も色々と大変だったと思います。それでも開催に漕ぎ着けてくれたことに感謝しつつ、来年もまたこの祭りに参加できることを楽しみにしています。

Object-Oriented Conferenceで契約による設計について話してきました

去る先週の日曜日、Object-Oriented Conferenceに参加してきました。 オブジェクト指向好きとしてはド直球なテーマで、去年名前を聞いてからずっと楽しみにしていました。

ooc.dev

今の時代にあえてオブジェクト指向をメインテーマにするというのは、とても意義のあることだと思います。ドメイン駆動設計やマイクロサービスアーキテクチャがブームになる中で、そこに至るまでに先人達が歩んできた道筋を理解することなく、新しい概念や道具を使いこなすことは困難です。「何故」ドメイン駆動設計なのか、「何故」マイクロサービスなのか。オブジェクト指向を探求していけば、自然とその動機が見えてきます。

「時代の流れだから◯◯を使うべき」と言う人もいるでしょう。しかし、大抵の開発現場に必要なのは全く新しい何かではなく、これまで使いこなせていなかった道具に立ち返ることだと常々思います。それができて初めて、次のステップに進めるはずです。そうでなければ、新しいものにすぐに飛びついては失敗するというループを永遠に繰り返すことになります。

昨今、PHP界隈でもオブジェクト指向の話題が目立つようになってきました。関数型の要素が話題になりがちなScalaも、本格的なオブジェクト指向の機能を備えています。今オブジェクト指向を学ぶことは、ただ古い時代に思いを馳せることではなく、明日から使える古くて新しい道具を発掘することになるのです。

続きを読む

Scalaでイミュータブルなエンティティを実装する

この記事はScala Advent Calendar 2019 - Qiitaの13日目の記事です。

Scala界隈にはドメイン駆動設計を実践されている、または導入を検討されている方が多いかと思います。筆者が携わっているScalebaseプロジェクトでも、バックエンドAPIの実装にScalaを採用しつつドメイン駆動設計を念頭に開発を進めています。

www.scalebase.com

今回はその試行錯誤の経験の中から、Case Classを用いたイミュータブルなエンティティの特性について、感じたことを共有したいと思います。

ミュータブルなエンティティ

エリック・エヴァンスのドメイン駆動設計』では、システムが同一性を考慮しなければならないエンティティと、同一性を考慮しなくてよい値オブジェクトを区別して設計することが強調されています。エンティティは概念的に「変化するもの(ミュータブル)」であり、値オブジェクトは一度生まれたら「変化しないもの(イミュータブル)」です。

同一性を考慮する必要があるというのは、例えば現実世界の人間のように、身長、体重、年齢、健康状態、あるいは名前が変わったとしても、「同一の人間である」と捉えるべき存在であるということです。しかし、システム上は必ずしも人間がエンティティであるとは限りません。例えば映画館の来場者の数や属性を記録するだけのシステムでは、人間を表す値オブジェクトを設計するのが妥当な判断になるかもしれません。何をエンティティとして扱うべきかは、作っているアプリケーションによって異なるのです。

別の例としては、「銀行口座」もシステム上でエンティティとして扱われることが多い概念でしょう。今あなたは「口座管理システム」の設計を任されているとします。このシステムでは各口座の同一性を認識し、どの口座に、どの通貨が、いくら預けられているかを記録しておく必要があります。

f:id:dnskimox:20191213020903p:plain

一般的には同一性を確認するために一意な識別子(ID)を設け、データベースのユニーク制約などを用いて、システム内に同一のIDを持つエンティティがただ一つであることを保証することになるでしょう。上のUMLは「名前」と「金額」を表す値のグループを、それぞれ値オブジェクトとして定義することを示しています。また、口座IDも専用のクラスを設けて値オブジェクトにしています。まずはこれをScalaのクラスで表現してみましょう。

class Account(
  val id: AccountId,                               // 口座のID
  private var ownerName: Name,                     // 名義人名
  private var balances: Map[String, Money] = Map() // 通貨毎の口座の残高
) {
  assert(balances.forall(_._2.value >= 0), "残高が0未満になることはない")

  def getOwnerName: Name              = ownerName
  def getBalances: Map[String, Money] = balances
  def changeOwnerName(newName: Name): Unit = {
    ownerName = newName
  }
  def addMoney(newMoney: Money): Unit = {
    val existingMoney = balances.getOrElse(newMoney.code, Money(newMoney.code, 0))
    balances = balances + (newMoney.code -> existingMoney.plus(newMoney))
  }
}

case class AccountId(value: Int)                     // 口座IDを表すVO
case class Name(firstName: String, lastName: String) // 名前を表すVO
case class Money(code: String, value: Double) { // 金額を表すVO
  def plus(other: Money): Money = {
    require(other.code == code, "同じ通貨のみ加算できる")
    copy(value = value + other.value)
  }
}

IDが変化してしまうと同一性を認識できなくなるので、そこだけは定数とし、他のプロパティは全て変数としています。この形は「概念的に」ミュータブルであるエンティティを、コード上でもミュータブルなものとして表現しています。インスタンスを作り、リポジトリに永続化するコードは以下のようなイメージです。

val account1 = new Account(AccountId(1), Name("太郎", "山田"), List())
account1.addMoney(Money("JPY", 100.0))
accountRepository.store(account1)

ミュータブルな実装であれば、復元したエンティティに対して変更を加え、再度保存するというようなコードが素直に書けます。

val account1 = accountRepository.findById(AccountId(1))
account1.changeOwnerName(Name("次郎", "山田"))
account1.addMoney(Money("USD", 1.0))
account1.addMoney(Money("EUR", 2.0))
accountRepository.store(account1)

さらにリポジトリIdentity Mapパターンを組み込めば、同一のIDのエンティティがスコープの中にただ一つしか存在しないように保証することも可能です。もし同一のIDのエンティティが複数同時に存在するようなコードを書いてしまった場合、どれを保存すれば最新の状態が記録されるか判別が困難になるため、これは重要なポイントです。

www.martinfowler.com

続きを読む

PofEAAで考える値オブジェクトの永続化あれこれ

この記事はドメイン駆動設計#1 Advent Calendar 2019 - Qiitaの3日目の記事です。

エリック・エヴァンス氏の『ドメイン駆動設計』に端を発したDDDの設計哲学では、システムが同一性を認識しなければならないエンティティと、同一性を認識しなくて良い値オブジェクトを区別して設計することが重要であるとされています。例としてよく使われるのが「貨幣オブジェクト」です。

// 貨幣
class Money {
  String code; // 通貨コード
  Double value; // いくらかを表す数値
}

疑似コードなので雰囲気で読んでください。このMoneyクラスを使って「100円」というインスタンスを2つ作ったとします。システムはこれらのインスタンスが同一の貨幣を指しているのか、それとも偶然値が一致している(等価である)だけなのかをどのように判別すれば良いでしょうか? ここで「判別しなくて良い」と言えるシステムなら、貨幣オブジェクトを値オブジェクトとして扱うことが出来ます。極端な例として、日本中の貨幣にIDを振って管理する「貨幣流通管理システム」のようなものであれば、そうはいかないでしょう。

貨幣のようなものは、道端にポンと置かれていることはあまりありません。道端に落ちている貨幣は、誰のものかわからなくなります。システム内でも同様で、永続化対象となる値オブジェクトは何らかのエンティティの中に入っているはずです。IDを持ったエンティティの中に入っていなければ、永続化した値オブジェクトを再度取り出す手がかりが無くなってしまうためです。

// 財布
class Wallet {
  String id; // 財布のID
  Money money; // 財布の中身
}

では、このエンティティ内の値オブジェクトはどのようにデーターベースに永続化されるのでしょうか? オブジェクトをデーターベース、特にリレーショナル・データベースに永続化する際には、オブジェクトの形とテーブル構造を必ずしも一致させられないという問題があります(インピーダンスミスマッチ)。Walletのような複合的なオブジェクトを保存するには、何らかの実装方針を選択しなければなりません。

これついてはマーティン・ファウラー氏が『エンタープライズアプリケーションアーキテクチャパターン』(PofEAA)の中で答えてくれています。『ドメイン駆動設計』の出版より少し前の本ですが、値オブジェクトというアイデア自体はオブジェクト指向界隈にそれ以前からあったものなのです。PofEAAは実装上の課題一つに対していくつかの解決策を挙げ、それぞれどのような長所・短所があるのかを解説するというスタイルで書かれています。値オブジェクトの永続化に関しても、3つのデザインパターンが紹介されています。

小さなオブジェクトを一つだけ

まず上記に挙げたようにWalletオブジェクトの中に一つだけMoneyオブジェクトが入っているようなケースでは、Embedded Valueパターンが有力な候補になるでしょう。

www.martinfowler.com

Embedded Valueパターンは、親となるエンティティを永続化するテーブルに、値オブジェクトの各プロパティを分解して入れてしまうという方法です。例えば、walletsテーブルは以下のような定義になります。

CREATE TABLE wallets (
  id VARCHAR(32) PRIMARY KEY COMMENT '財布のID',
  money_code CHAR(3) COMMENT '通貨コード',
  money_value FLOAT COMMENT 'いくらかを表す数値'
);

永続化する際にはWalletオブジェクトの各プロパティと、Moneyオブジェクトの各プロパティをそれぞれ対応するカラムに入れます。そして復元する際には、まずMoneyオブジェクトのプロパティを取り出して組み立て、次にWalletオブジェクトのプロパティを取り出して、先に作っておいたMoneyオブジェクトの参照を持たせれば、元通りのWalletオブジェクトが手に入ります。この辺りの具体的な実装方法は、DDD界隈ではリポジトリの話題として扱われます。PofEAAの中でも様々なパターンが紹介されているので、気になる方はぜひ読んでみてください。

Embedded Valueのメリットは、エンティティの中にある小さな値オブジェクトのために個別のテーブルを作らなくて良くなる点にあります。また、エンティティを取得する際にテーブルのJOINが不要になるため、パフォーマンスにもいい影響を与えるでしょう。

続きを読む

PHPカンファレンス北海道2019に参加していました

ブログを書くまでがカンファレンスということで、もう二週間以上も前のことですがPHPカンファレンス北海道2019に参加していました。今回は初めてスタッフとしての参加でした。

phpcon.hokkaido.jp

スタッフをやることを決めたのは去年の6月末、PHPカンファレンス福岡2018の興奮冷めやらぬ頃でした。PHPerKaigi2018でカンファレンスにハマり、一般参加、登壇者と経験して、次はスタッフをやってみたいと思っていた矢先のことです。

キックオフで石鍋亭に行った時の写真が今年の2月の撮影だったので、半年強の準備期間があったことになります。最初はじわじわと、徐々に加速しながら計画が形作られていく様を見ることが出来ていい勉強になりました。運営経験豊富な歴戦のスタッフも多く、「何を考えるべきか」がわかっているというのがとても安心感があったのを覚えています。

自分はCfPの作成や登壇者の対応など、これまでの経験から想像しやすい仕事を受け持ちました。PHPの現場でカンファレンス運営の話を度々聴いていたとはいえ、それ以外のところは本当にわからないことだらけの状態で、周りに頼りきりでした。普段の本業は積み上げてきた経験の上でやっているので、こういう機会にレベル1の状態で仕事をすると、初心に返る感じがしていいですね。次の機会があれば、もう少し動けるようになると思いたい。

php-genba.shin1x1.com

当日は一階の受付とアンカンファレンスの担当で、入場のラッシュの後は結構暇がありました。今思えばこの隙に3階のメイン会場の様子を見に行かせてもらえばよかったです。結局一度も目にしないまま終わってしまって、もったいないことをしました。セッションは全部面白かったと思います。聴けなかったですけど。

今回の会場は1階と3階に分かれていて、人の移動が起きづらいのではないかという懸念がありました。実際午前中は1階に人が少なかったのですか、スポンサーセッションが始まる頃から徐々に流れができ始め、アンカンファレンスのほうも観覧席の急遽補充が必要になるほどの盛り上がりを見せてくれました。それにしてもこのアンカンファレンス、密度が非常に濃いですね。登壇してくれた方はありがとうございました!

正直、登壇者参加のときより緊張していたのですか、蓋を開けてみれば大きなトラブルもなく、参加者数も目標を達成し、みんなが楽しめたようで大成功だったと言っていいんじゃないでしょうか。当日までどんな雰囲気のカンファレンスになるのか本当に想像がつかなくて、スタッフに出来るのは場を用意することまでなのだということを実感しました。「カンファレンスは一般参加者、登壇者、スポンサー、スタッフ全員で作っている」という当たり前の原点を言葉ではなく心で理解できたと思います。PHPカンファレンス北海道2019に関わっていただいた全ての方々に、お疲れ様でした!

アルプ株式会社に入社しました

2月1日からアルプ株式会社で働いています。社会人になってから4社目、2年3ヶ月ぶり3度目の転職です。

どんな会社か? 

ググってもまだWantedlyとコーポレートサイトくらいしか出てこないと思うのですが、サブスクリプションビジネス向けの決済基盤・契約管理のSaaSを作っています。元pixiv代表の伊藤さんの会社と言ったほうがピンとくる人が多いかもしれません。開発言語は主にScalaとTypeScriptです。実はScalaMatsuri 2019のスポンサー欄にも名前があります。

www.wantedly.com

なぜ転職したのか?

前職のインフィニットループは文句なしに働きやすい会社でした。入社直後から色々やりたがる新人を煙たがりもせず、いくつかのプロジェクトのアーキテクチャ開発プロセスに大いに口を出させてもらえました。カンファレンスへの参加や登壇のサポートもしていただいて、コミュニティで活動する楽しさを知るきっかけにもなりました。お陰様で今では立派なカンファレンスジャンキーです。

www.infiniteloop.co.jp

www.infiniteloop.co.jp

www.infiniteloop.co.jp

転職の動機は端的に言えば、「自分が次にジョインすべきプロジェクトはこれだ」という強烈な確信を抱くに至ったからです。意識的に転職先を探したのではなく、次にいっしょに働きたいチームがたまたま社外にあったという感覚でした。気持ちが固まったのは去年の11月末頃、折しもインフィニットループで携わっていたプロジェクトのリリースが迫っていた頃合いで、次に社内でどう動くか考えていた時分でもありました。

決済基盤との因縁

思えば前々職のpixivにいた頃は決済にまつわるコードばかり書いていた気がします。新しいサービスを作る度、新しい決済方法を追加する度に外部の決済システムのドキュメントを読み込み、サービスに組み込む作業を何度となく繰り返しました。サービス数×決済方法の数だけ実装が増えていくのが辛くなった頃、自社サービス向けの決済基盤をマイクロサービスとして切り出すプロジェクトを立ち上げました。この時、ビジネスサイドの采配を取ってくれたのが伊藤さんでした。伊藤さんとはそれ以前にもBOOTHの立ち上げなどで一緒に仕事をしていて、自分にとってはマネージャーの理想像とも言える人物の一人です。アルプの話を聞いた時、勝手ながらこれは自分たちのプロジェクトの続きだと感じました。しかも今度相手にするのは世の全てのサービスで、扱う問題領域も遥かに複雑になるとなれば、心に火がつくのも無理からぬ話だと思います。

これからどうしたいか

まずはサービスのリリースが最大の課題ですが、同じくらい重要なのは組織の文化を作っていくことだと思います。成熟した会社の性質を変えるのは容易ではないですが、最初期のチーム作りに参加できるのであれば、ここで蒔いた種は会社の成長とともに広がっていくはずです。自分は引き続き札幌からの参加になります。リモートワークという条件は枷にもなりますが、オフィスという場所と固定化された時間に縛られないチームを作ることができれば、それは良い文化の土壌になるのではないかと考えています。

本当の意味での創業期に立ち会うのはこれが初めてです。やるからには今までに経験したことのない、新しい形の組織に育てていきたいと思っています。そして、それが可能だと思える最高のメンバーが集っているのも楽しみなところです。

 

*追記:欲しいものが思いつかなかったのでウィッシュリストを載せていなかったのですが、妻との協議の末、こちらのリストが完成しました。

http://amzn.asia/f1e5lbl