Quantcast
Channel: hachi8833の記事一覧|TechRacho by BPS株式会社
Viewing all articles
Browse latest Browse all 1759

Rails: 私の好きなコード(1)大胆かつ的確なドメイン駆動開発(翻訳)

$
0
0

概要

原著者の許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。一部の画像は原文と配置を変えています。

Rails: 私の好きなコード(1)大胆かつ的確なドメイン駆動開発(翻訳)

私がほぼ3年前に37signalsで働き始めたときに手がけた作業のひとつは、Basecamp用のGitリポジトリをcloneすることでした。いろいろやっているうちに、以下のメソッドが目に止まりました。

module Person::Tombstonable
  ...
  def decease
    case
    when deceasable?
      erect_tombstone
      remove_administratorships
      remove_accesses_later
      self
    when deceased?
      nil
    else
      raise ArgumentError, "an account owner cannot be removed. You must transfer ownership first"
    end
  end
end

Basecampアプリにおけるpersonは、UserやClientといった特定の種別を表現するdelegated type属性(#39341)を持ちます。指定のアカウントからpersonを削除すると、Basecampはそれをプレースホルダに置き換えるので、関連データは変更されることなく引き続き機能します。

私はドメイン駆動設計(DDD: Domain-Driven Design)やドメイン概念をコードに反映することの重要性についてはひととおり精通していましたが、これほど意識的に実践している例を見たのはこれが初めてでした。「personを削除するときにpersonをプレースホルダーに置き換える」ぐらいまでなら自分でもやれそうですが、「personを削除するときに墓石を建てる(erect tombstone)」というのは実にうまい方法ですね。

客観的に見れば、この概念は明確かつ簡潔で、間違えようもないほど雄弁です。主観的に見れば、この命名にはまるでpersonality(人格)やsoul(魂)のような大胆不敵な要素があります。こういう命名をコードで使うのはありでしょうか?もちろんありですし、うまくハマりさえすればコードは著しく強化されます。私にとっての「aha!」体験でした。


別の例として、HEYのスクリーニングシステムを紹介します。このシステムは、ユーザーにメールを送信したい連絡先からリクエストされた「クリアランス嘆願書」をユーザーが審査する形を取ります。

ここにも大胆不敵な要素を見ることができます。嘆願書(petition)は形式的であることを示唆するので、要求(request)とは別物です。HEYの仕様におけるスクリーニングは形式張った形で行われます。他の人は、ユーザーの許可なしに受信トレイのメールを取得できないようになっています。

「検査官(examiner)は、請願者(petitioner)からのクリアランス嘆願書(clearance petition)を承認しなければならない」という概念は、このシステムが行っていることを他の人にくっきりと明確に説明する方法になっており、以下のコードもまさしくこの概念を反映したものになっています。

class Contact < ApplicationRecord
  include Petitioner
   ...
end
module Contact::Petitioner
  extend ActiveSupport::Concern

  included do
    has_many :clearance_petitions, foreign_key: "petitioner_id", class_name: "Clearance", dependent: :destroy
  end
   ...
end
class User < ApplicationRecord
  include Examiner
end
module User::Examiner
  extend ActiveSupport::Concern

  included do
    has_many :clearances, foreign_key: "examiner_id", class_name: "Clearance", dependent: :destroy
  end

  def approve(contacts)
    ...
  end

  ...
end

ここでのconcernsの使い方を見ていると、DCIアーキテクチャパターンにおける”Role”が思い出されます。DCI(Data, Context, Interaction)は興味深いアイデアに満ち溢れた提案のひとつですが、なかなかうまくコードに反映されません。ここでのconcernsの使い方は、Roleの実装としてかなり実用的です。


私が一筋縄では行かないモデルを構築するときのお気に入りのツールは「プレーンテキストで説明文を書く」というものです。私がHEYでメール分析システムの改良を手がけていたとき、新しいドメインモデルがどのようなものになるかについて自分用のメモを書きました。以下は、そのときのメモ(上)と、システムが構築されたときのプルリクに含めた説明文(下)です。メモ書きは自分のための思考ツールとして書いたものなので、内容とその正確さは無関係です。しかし、複雑なシステムについて考えるときプレーンテキストで書いたのは素晴らしい出発点と言えます。そんなとき、辞書は大事なパートナーとなります。

ドメイン
システムへの入力はAnalysis::InboundEmailになる。今後より多くの要素を取り入れたくなったら別のエンティティを作成することにする。
Analysisは一連のAnalysis::Rulesを含む。あるルールが実行されるとき、AnalysisAnalysis::InboundEmailを受け取ってAnalysis::Insightsのリストを返す。
Analysisの実行結果はAnalysis::Resultであり、そこに一連のAnalysis::Rulesを含む。
アナリシスがOKでなかった場合は、それの元となるActionMailbox::InboundEmailに沿ってアナリシスを保存する。これによって、エンティティが作成される前であってもアクションが動作可能になる。
1個のAnalysisInsightsにはコード(AnalysisInsightDecision)が含まれる。決定には、種別(type)や大きさ(magnitude)がある。種別はbouncespamになる可能性がある。
ある決定について、一連のルールの集約された大きさが1より大きい場合は、その決定がアナリシスの結果となる。

ドメインモデル
以下の4つのエンティティがある。

  • アナリシスのあるRuleは、指定のメールに対してInsightを返す
  • Insightは、アクションの種別(現在はokrejectspam)や重みといった属性を決定し、アナリシスの結果をトレースする
  • Analysisは一連のRuleを含む。アナリシスを実行すると、ルールによるすべてのインサイト(洞察)を収集してResultを返す。
  • Resultは、指定のアナリシスに関するすべてのインサイトをグループ化する

Resultは、ポリモーフィック関連付けによってActionMailbox::InboundEmailに関連付けられる。Resultを保存するのは、メールがOKでない場合に限ることにする。なお、代わりにReceiptレベルに追加することも検討したが、受信メールレベルで行う方が、同じシステムでメールをバウンスできるので理にかなっていると思う。

重み付けシステムがあるのでアナリシスの判定にファジーさを加えることが可能だ(今すぐ活用するわけではないが)。アナリシスを実行するときに、指定のkindの重みセットが1.0以上にならない限り、すべてのルールは上から順に実行される。現時点ではすべてのインサイトが同じ1.0の重みを持つことになる。

画像の元テキスト

HEYもBasecampも、最初のコミット時点からドメイン駆動設計にがっつり賭けています。もちろん、だからといって隅々まで光り輝くほど完璧になるわけではありませんが、総じてコードベースを楽しみながら読めるようになります。

多くの書籍が、いかにして優れたドメインモデルを作り出すかを主題に据えていますが、私が37signalsのコードベースから学んだのは「潔癖症になるな」、そして「いつもにも増して大胆であれ」ということです。

本記事は、『私の好きなコード』シリーズの記事です。

関連記事

素のRailsは十分に豊かである(翻訳)

Rails: ActiveRecord::DelegatedType APIドキュメント(翻訳)

The post Rails: 私の好きなコード(1)大胆かつ的確なドメイン駆動開発(翻訳) first appeared on TechRacho.


Viewing all articles
Browse latest Browse all 1759

Trending Articles