- 投稿日:2020-02-05T23:56:00+09:00
業務改善をしよう!目次編
はじめに
2020年を迎え早一ヶ月が経ちました。
私も一年と少し居た現場を離れ、2020年から新たな現場に向かうこととなりました。
そのためこの一月、新たな現場に慣れるために様々な業務を見て聞いてきました。なんだこの面倒くさい業務は!?
そう感じる業務が多くありました。
前の現場が業務効率化に積極的だったのと、そこで働く人達の意欲も凄かったため、どうしても比較して見てしまっているのかもしれませんが…ただ…これはチャンスなのでは?
前の現場にいたこともあって、おかげで ShellScript の開発を独自で行う機会があったり、API の開発をしていたため通信周りの知識も増えました。
何より、そうした人たちに囲まれていたおかげで業務の効率化に対し積極的な意識がついていました。
まだまだ経験の多くない私ですが、これは貢献あるいはアピールするチャンスなのではと。というわけで、今の現場で感じた無駄な業務の改善案や、実際になんとかした Script 等を書き記していこうと思います。
この記事は主に改善案等を書き記す目次的な役割を持たせる予定です!
それぞれの記事を作成次第こちらにリンクを張っていこうと思います。目次
- 3媒体全てに同じような内容を登録するのが面倒くさい!
- 列挙型の一覧をExcel形式で出力するためにcsvファイルをアップロードする!?
- リリース時にsqlファイルを逐一指定して実行するのが面倒くさい!
3媒体全てに同じような内容を登録するのが面倒くさい!
ここでいう3媒体というのは下記のタスク管理ツールのことです。
- BackLog(お客さんとのやり取り用)
- Trello(チーム内のタスク管理)
- Kintone(会社内のプロジェクト管理)これらに同じような内容を登録する必要があるのです。
コピーすれば良いとはいえ、案件ごとに登録をするので面倒!なんとかしたい!
というわけで、それぞれの API を叩いて Script にやってもらおうと考えております。
ただし、チーム内の人にも手軽に使ってもらうことを前提とするため、Windows という関係上 PowerShell で書こうと思っています。列挙型の一覧をExcel形式で出力するためにcsvファイルをアップロードする!?
動きだけ先に紹介します。
現在現場ではバッチ処理を週次で動かし、業務データをエクセル形式で出力しています。
(2020/2/6 追記)
→日次だし、出力もcsvでした…というかただファイル移動しているだけ…なにそれ…?出力対象は以下のとおりです。
- 各種テーブル
- 列挙型のコード一覧
テーブルはまだしも列挙型…?
何はともあれなるほど、出力する必要があるのならあるでいいでしょう。私「もし列挙型に追加対応があった場合、何かリリース時に必要な作業とかあったりしますか?」
チームメンバー「ああ、その場合は出力用にcsvファイルを用意してあるから、リリース時にそれを修正してアップロードしてね」…!?
え、列挙型のコード一覧をExcel形式で出力するためにcsvファイルを用意するの???
というかそもそも列挙型って業務内で変わることないよね?なんで週次でExcelファイル出力してるの???
そのアップロードするcsvファイル、Excelファイルにすればよくない???とまあ、今動いているものを止めるのは私程度の新米では難しいので、この一々csvを修正する手間をなくしたい…
というわけで、jarファイルからクラスファイルをサルベージしてcsvファイルを生成する Script なんかを書けないかなぁと。
最初はそんなことできるの…?と思っていましたが、案外いけそうです!リリース時にsqlファイルを逐一指定して実行するのが面倒くさい!
マスタやテーブル自体に変更があった場合、もちろんですが DDL あるいは DML を各環境に適用する必要があります。
それを Linux の画面で、アップロードしておいた sql ファイルをポチポチ名前を指定しては実行を繰り返しています。
そこまでの手間ではないのですが、なんだか面倒くさいなぁと。
それならば Script で指定のディレクトリに格納されている sql ファイルを全て実行するよう組んでしまおうと考えました。
ただリリース作業という手前、慎重に考える必要があると感じています。
これに関してはチームリーダの希望を聞きつつ進めていく予定です。忘れてはいけない大事なこと
今回私にとっては非効率だと感じることが多かったため業務効率化に動こうとしていますが、何より大事なのは一緒に働くチームの方々の意思です。
チームリーダからは何か改善点等あればあったらドンドン言ってほしいとは言われているものの、経験も少ない若造にあれは非効率、だからこうしようと、トントン進められたら気分がいいものではないでしょう。
一部のことに関しては流石にどうだろうと思うので改善する方向で動いていますが、メンバーの意思を無視することはチームの崩壊を招きます。
チーム内の調和を保ちつつ、業務効率化を目指して行動したいと思います。
- 投稿日:2020-02-05T22:59:39+09:00
"色違いポケモンシミュレータ"
乱数を使う練習①
アイテムなしで色違い孵化厳選を行った場合のシミュレーションimport java.util.Random;
public class Irochigai
{
public static void main(String[] args)
{
System.out.println("色違いシミュレータ");Random r = new Random(); int randomValue = r.nextInt(4096); if (randomValue == 4096) { System.out.println("Irochi"); } else { System.out.println("normal"); } System.out.println(randomValue); }}
- 投稿日:2020-02-05T21:35:06+09:00
【Java】BeanValidationでURL形式をチェックする場合HibernateValidatorの@URLは使わない方がよいかもしれない
内容は大体タイトルの通りです。
理由
org.hibernate.validator.internal.constraintvalidators.hv.URLValidator
の実装では「http:
」と入力しただけの状態でもバリデーションが通ってしまいます。
「入力される内容はhttp
/https
といったプロトコルだけ、最低限ドメインまで入っていてほしい」など、URLはURLでも限られた内容しか入力されない場合は@Pattern
アノテーションを使って正規表現でチェックした方が良いでしょう。補足
Maven Repository: org.hibernate.validator » hibernate-validator » 6.1.2.Finalで検証しました。
- 投稿日:2020-02-05T21:09:05+09:00
Google OAuth 2.0 認証を使ったGoogle Sign-Inの実装(サーバー編)
概要
今回はこちらの記事の続きです。
Google OAuth 2.0 認証を使ったGoogle Sign-Inの実装(JS編)
クライアントサイドでログインしてバックエンドサーバーで認証といった感じでやります。前回の記事でユーザー情報などをレスポンスで取得することができました。
ただこれをこのままサーバーサイドに送ったりするのは危険です。
例えば、ユーザーIDをサーバーに送信する際にユーザーを偽装できちゃったりします。。なので、その代わりに検証可能なIDトークンを使用して、サーバー側でサインインしているユーザー情報を安全に取得します。
さっそく実装していきましょう。
フロントサイドの実装
まずJSの部分を書きかえます。
JavaScriptfunction onSignIn(googleUser) { var id_token = googleUser.getAuthResponse().id_token; // IDトークンを取得 // IDトークンをサーバー側に送る処理 }ここのサーバー側に送る処理はAjax使ったり、hidden属性で送ったりいろいろなやり方があると思いますが、
今回はAjaxのXMLHttpRequestを使用してHTTPリクエストを発行する方法でサーバーとの通信をやって来ます。先ほどの処理に追加して
javaScriptvar req = new XMLHttpRequest(); req.open('POST', '[URL]’); req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); req.onload = function() { req.send('idtoken=' + id_token);こんな感じでサーバー側に送信しましょう。
[URL]の部分にはサーバー側のURLを入れましょう。サーバー側の実装
今回は、JavaのSpring bootを使って実装していきます。
まずは依存関係の追加です。
build.gradledependencies { implementation("com.google.api-client:google-api-client:1.30.5") }Googleクライアントライブラリを使えば簡単にIDトークンの検証ができます。
JavaGoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder( new NetHttpTransport(), JacksonFactory.getDefaultInstance()) .setAudience(Collections.singletonList("YOUR_CLIENT_ID.apps.googleusercontent.com")) .build(); var idtokenStriing = getIdToken(); // 取得したIDトークン GoogleIdToken idToken = verifier.verify(idTokenString); // IDトークン検証YOUR_CLIENT_IDには自分のクライアントIDを入れてください。
もしGoogleクライアントライブラリを使わない場合はGoogleの公開鍵(PEM形式)を使用して、トークンの署名を検証する方法もありますが、GoogleでもGoogleクライアントライブラリを使って検証することを推奨しているので使いましょう。
あとはユーザー情報を取得するだけです。
JavaPayload payload = idToken.getPayload(); String userId = payload.getSubject(); // ユーザーID String email = payload.getEmail(); // ユーザーメールアドレス String name = (String) payload.get("name"); // ユーザー名 String pictureUrl = (String) payload.get("picture"); // ユーザープロフィール画像こんな感じで取得できちゃいます。
まとめ
今はいろんなサイトでOAuth認証使ってるところが増えてますね。
今回はGoogleですがFacebookやAppleなどのOAuth認証もまたあげていこうと思います。
- 投稿日:2020-02-05T15:51:52+09:00
コンストラクタインジェクションだと単体テストが楽なんじゃない?って話
はじめに
Springでコード書いてるときに絶対使うアレ、
@Autowired
です。
最近はフィールドインジェクションじゃなくて、コンストラクタインジェクションのほうが推奨されているらしいのです。そもそもなんでコンストラクタインジェクションが推奨されているのかは、ググってみてください。
いろいろ理由がありますが、その中で一番納得したのは「あれ?これって単体テスト楽なんじゃね??」って思ったので、それについて書いてみました。モック作るのめんどくさい
テスト対象のクラス
サンプルが適当すぎですが、こんなメールを送るクラスがあったとして。
MailService.javaimport org.springframework.mail.MailSender; import org.springframework.mail.SimpleMailMessage; @RestController @RequestMapping(path="/mail") public class MailService { @Autowired MailSender mailSender; @PostMapping public void send() { SimpleMailMessage msg = new SimpleMailMessage(); msg.setFrom("noreply@example.com"); msg.setTo("to@example.com"); msg.setSubject("Hello World"); msg.setText("Welcome to my world."); mailSender.send(msg); } }テストコード
- 単体テストなので、実際にメールは送らない。
- MailSenderに渡してるFromやらなんやらがちゃんと合ってるか?
というテストコードを書いてみます。
MailServiceTest.java@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest public class MailServiceTest { @InjectMocks MailService mailService; @Mock MailSender mailSender; @Captor ArgumentCaptor<SimpleMailMessage> msgCaptor; @Test public void test() { // テスト対象メソッドの実行 mailService.send(); // テスト対象メソッドの中で、mailSender#sendが1回実行されていること // ついでに引数をキャプチャ verify(mailSender, times(1)).send(msgCaptor.capture()); // 引数の中身を検証 SimpleMailMessage msg = msgCaptor.getValue(); assertThat(msg.getFrom(), is("noreply@example.com")); assertNotNull(msg.getTo()); assertThat(msg.getTo().length, is(1)); assertThat(msg.getTo()[0], is("to@example.com")); assertThat(msg.getSubject(), is("Hello World")); assertThat(msg.getText(), is("Welcome to my world.")); } }フィールドインジェクションしているクラスだと、こんな書き方でしょうか。
慣れていればいいですが、Captorとか毎回書き方忘れるしコメントないと(コメントあっても)絶対あとで読めないし、なんかそもそもめんどくさい・・・。コンストラクタインジェクションにしてみる
テスト対象のクラス(修正箇所だけ)
MailService.javapublic class MailService { private final MailSender mailSender; @Autowired public MailService(MailSender mailSender) { this.mailSender = mailSender; } // 以下略テストコード
- テスト用のモッククラスとして独自で実装したものを突っ込めるので、テスト対象と関係ないクラスの動作を好きなようにできる。
- モックの動作が、Javaのコードとして見える。
@RunWith
も@SpringBootTest
もいらない。MailServiceTest.javapublic class MailServiceTest { /** * MailSenderのモッククラスを独自に作っちゃう */ private class MailSenderMock implements MailSender { SimpleMailMessage simpleMessage; @Override public void send(SimpleMailMessage simpleMessage) throws MailException { this.simpleMessage = simpleMessage; } @Override public void send(SimpleMailMessage... simpleMessages) throws MailException { } } @Test public void test() { // モッククラスを渡してインスタンスを作る MailSenderMock mailSenderMock = new MailSenderMock(); MailService mailService = new MailService(mailSenderMock); // テスト対象メソッドの実行 mailService.send(); // 検証 SimpleMailMessage msg = mailSenderMock.simpleMessage; assertThat(msg.getFrom(), is("noreply@example.com")); assertNotNull(msg.getTo()); assertThat(msg.getTo().length, is(1)); assertThat(msg.getTo()[0], is("to@example.com")); assertThat(msg.getSubject(), is("Hello World")); assertThat(msg.getText(), is("Welcome to my world.")); } }サンプル上、モッククラスはインナークラスとして書いてますが、個別にクラスファイル作っても良いです。
Mockito.mockで作ってもいいですけどね。Mockitoめんどくさい、って言っていたのが本末転倒ですが・・・
(何もしなくて良い、ってクラスならMockito.mock()でも良いかも。)サンプルのテスト対象クラスが簡単すぎるので大差ないようにも見えますが、Mockitoでモック化しなくていいのは楽だなぁと思いました。
また、SpringBootに依存しなくなるので、プロパティの設定やDBが無いなどの理由でJUnitが動かないというのも無くなります。こんなことも
例えば3つくらい
@Autowired
していて、1つだけ単体テスト用に動作を変えるようなこともできます。
この場合は、@SpringBootTest
にして、動作は変えないクラスをテストクラスの中で@Autowired
します。この場合はSpringBootに依存しちゃいますけどね。
Controllerから結合テストっぽくテストコード書きたいけど邪魔なクラスある!なんて時は、このやり方も良いと思います。MailServiceTest.java@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest public class MailServiceTest { @Autowired HogeSerice hogeService; @Autowired FugaService fugaService; private class MailSenderMock { // 略 } @Test public void test() { // モッククラスを渡してインスタンスを作る MailSenderMock mailSenderMock = new MailSenderMock(); MailService mailService = new MailService(mailSenderMock, hogeService, fugaService); // 略
- 投稿日:2020-02-05T15:01:24+09:00
Eclipseで"1.Xより下のソース・レベルで許可されていません"とエラーが出た際の対処方法
ブランチを切ると稀に起きる現象
職場でブランチを切って作業しようとした際に、ビルドすると何故かエラーが下の画像のように大量に発生するときがあります。備忘録も兼ねて解決方法を書きます
よく見てみると'<>'オペレーターは1.7より下のソース・レベルでは許可されていません
とのこと。
エラー表示されている箇所にマウスオーバーしてみると、どうやらJREのバージョンの問題のようです。
解決方法
解決方法はとっても簡単です。まずはEclipseのナビゲーターに表示されているプロジェクトを右クリックします。その中でプロパティをクリックします(赤枠)
プロパティ画面で左のリストの中にあるJavaのコンパイラーをクリックしてみるとあら不思議、実行環境は1.8の筈なのに何故か1.5になっています。
なのでコンパイラー準拠レベルを実行環境の1.8に戻してから適用して閉じるをクリックしましょう。その際にビルドを行う旨のインフォメーションが出ますのでそのままOKを押します。
ビルドが完了したら先程出ていたエラーは消えていると思います。これで以上です
- 投稿日:2020-02-05T15:01:24+09:00
【Java】Eclipseで"1.Xより下のソース・レベルで許可されていません"とエラーが出た際の対処方法
ブランチを切ると稀に起きる現象
職場でブランチを切って作業しようとした際に、ビルドすると何故かエラーが下の画像のように大量に発生するときがあります。備忘録も兼ねて解決方法を書きます
よく見てみると'<>'オペレーターは1.7より下のソース・レベルでは許可されていません
とのこと。
エラー表示されている箇所にマウスオーバーしてみると、どうやらJREのバージョンの問題のようです。
解決方法
解決方法はとっても簡単です。まずはEclipseのナビゲーターに表示されているプロジェクトを右クリックします。その中でプロパティをクリックします(赤枠)
プロパティ画面で左のリストの中にあるJavaのコンパイラーをクリックしてみるとあら不思議、実行環境は1.8の筈なのに何故か1.5になっています。
なのでコンパイラー準拠レベルを実行環境の1.8に戻してから適用して閉じるをクリックしましょう。その際にビルドを行う旨のインフォメーションが出ますのでそのままOKを押します。
ビルドが完了したら先程出ていたエラーは消えていると思います。これで以上です
- 投稿日:2020-02-05T10:10:49+09:00
『Domain-Driven Design Tackling Complexity in the Heart of Software - Eric Evans』を読んで要点を翻訳してみた
domain-driven-design
Domain-Driven Design Tackling Complexity in the Heart of Software - Eric Evans
(NOTE: Description with brackets like this means my own thoughts, not translation)Index
- FOREWORD
- PREFACE
- I. Putting the Domain Model to Work
- II. The Building Blocks of a Model-Driven Design
- III. Refactoring Toward Deeper Insight
- IV. Strategic Design
- GLOSSARY
- AGGREGATE
- analysis pattern
- ASSERTION
- BOUNDED CONTEXT
- client
- cohesion
- command
- CONCEPTUAL CONTOUR
- context
- CONTEXT MAP
- CORE DOMAIN
- declarative design
- deep model
- design pattern
- distillation
- domain
- domain layer
- ENTITY
- FACTORY
- function
- immutable
- implicit concept
- INTENTION-REVEALING INTERFACE
- invariant(上記では不変性と訳している)
- iteration
- large-scale structure
- LAYERED ARCHITECTURE
- life cycle
- model
- MODEL-DRIVEN DESIGN
- modeling paradigm
- REPOSITORY
- responsibility
- SERVICE
- side effect
- SIDE-EFFECT-FREE FUNCTION
- STANDALONE CLASS
- stateless
- strategic design
- supple design
- UBIQUITOUS LANGUAGE
- unification
- VALUE OBJECT
- WHOLE VALUE
FOREWORD
- 概念と実装を一緒にする主要な理由は、ドメインモデルがDomain ExpertsとDeveloperを結ぶユビキタス言語を提供するため
- どんな優れたドメインモデラーでさえも、最初のリリースの後にベストなアイデアを得る
PREFACE
- 成功に共通する特徴は、設計の繰り返しを通じて進化したリッチなドメインモデルだった
- XPはAgileプロセスで有名であり、この本では設計とプロセスの議論の基礎としてXPを使う ### XP
- リファクタリングによってデザインを改善できることを、XPのプロセスでは仮定する
- 変更と不確実性に対処する能力を重視する
- 初回1回限りの設計はしない
- 方針を素早く変えるために、コミュニケーションとプロジェクトの能力改善に注力する
- "simplest thing that could work" をプロジェクトのどの段階でも用いて以下を行うことで、究極的には真に求める設計に到達する
- 継続的なリファクタリング
- 多くの小さな設計の改善 ### DDDの前提
- デプロイはiterativeであること
- (プロジェクトの最後まで)DeveloperとDomain Expertが近い関係にあること
I. Putting the Domain Model to Work
- ドメインモデルとは、綿密に組織された業務知識の選択的な抽象である
- モデルと実装は密に結びついている
- モデルは全てのチームメンバーが使う言語の支柱である
- モデルは、業務知識を濃縮したもの
Chap.1 Crunching Knowledge
- 最初にやることは、Domainの仕様を聞いて、ドメインモデルのシナリオのラフなスケッチを書くこと
- Domainの仕様の認識間違いを指摘して直してもらいながら、その都度スケッチを書き直す(一回では書けないので、何度も書くこと)
- 本文では 6回 書き直している
- スケッチを書く時は、自分が理解していないことを恐れず聞いて、理解を深めること
- 本文では「これがまだ分からないのだが」と、何度もDomain Expertに繰り返し質問している
- Domain ExpertからもDeveloperが作ったラフなスケッチに対して質問をしている「これは何を示すのか」
- スケッチが固まったら、クラス図(クラス名、属性、操作を最小単位として、集約や継承などの関係性を描いたもの)を書く
- 次に、Domainだけコードを書く、Infrastructureは書かない
- コードを書いたらDomain Expertに確認してもらい、具体的な認識の間違いを正していく
- Domain Expertと密に会話し、モデルに不要な仕様(実装)を削ぎ落とす
- 仕様を全て正確に満たすことができなければ、再度0から新たにモデルを作り、実装する
- 継続的にDomainの知識を得ることが大切
- 人の断片的な知識や、断片的なドキュメントなど、基本的にDomainの知識はまとまっていないため
Ingredients of Effective Modeling
- モデルと実装を結びつけよ
- モデルに基づく言語を育てよ
- モデルは複雑な問題を解くために知識リッチであれ
- モデルを精製せよ(要らないものは削ぎ落とせ、さもないと間違って重要なものが削ぎ落とされてしまう)
- ブレインストーミングと実験を繰り返せ
- モデルの表現を口に出して言えば、それが正しいかどうかすぐテストできる
- 船と貨物を運ぶシステムを開発する。(実際のビジネスでそうなのだが)直前のキャンセルに備えて、実際の積載量の110%を積むことができる
- 明示的かつ分別されていないため、メソッドの最初に110%を超えるかどうかのチェックを追加実装してはいけない
- Domain Expertsが読んでも分からないし、Developerだって見落とす可能性がある
- Design PatternのStrategyパターンを使って、110%PolicyインスタンスをAggregateして、そのメソッドを呼び出す
- 他のクラスに区別して分けることで、これが重要なビジネスルールであることがDomain ExpertやDeveloperに認識されやすくなる
- Domain Expertがコードを読んで、ビジネスルールが正しく実装されているかどうかフィードバックを返すことができるようになる
Chap.2 Communication and the Use of Language
- ユビキタス言語を使って会話せよ
- 翻訳と誤認識のコストをなくせる
- モデルで明示されるビジネスルールを議論するための用語を含む
- ユビキタス言語の一貫した利用は、モデルの弱点を明らかにする
- ユビキタス言語の変更は、ドメインモデル内の変更として認識される(つまりコードのリファクタリングが必要)
- Domain Expertもビジネス用語を使うのではなく、ユビキタス言語を使うべし
- ドメインモデルをユビキタス言語の支柱として使え
- 全てのコミュニケーションとコードにおいて、ユビキタス言語を使うよう徹底しなければいけない
- Domain Expertは、ドメインの理解を伝えるには不適切または不十分な用語には、常に反対を表明すべき
- Developerは、設計をつまずかせる不明瞭さ、一貫性のなさを観察すべき
Example
- ユーザーと開発者は同じ言語を話しているか
その言語はアプリケーションが行わなければいけない何かの議論を進めるのに充分なほどリッチか
Domain Expertが話す言語を、ドメインモデル(EntityやValueObject, Application Service)の振る舞いで説明する
モデルを改善する最善の方法は、話されている言葉を探究すること
- 曖昧だったり技術的な話がユビキタス言語に出てきてはならない
- くどい説明になっている部分も改善の余地がある
ビジネスのユースケースを、ドメインモデルのの要素と振る舞いを用いて、声に出して説明する
- 実現したいことを声に出して、より簡潔な方法を探し、図で表してコードに落とし込む
- もし洗練されたDomain Expertがそのモデルを理解しないならば、そのモデルに何か誤りがある
- 開発者とDomain Expertはモデルを使って目的のシナリオを議論することで、非公式にモデルをテストすべき
- ドメインモデルはDomain Expertの特有の専門用語に由来するが、それは精製され、より明確に、より狭義のものとなる
ユビキタス言語は、Domain Expertの言語とDeveloperの言語の交差する領域で発展する
- ユビキタス言語に なり得ない もの
- Domain Expert
- ビジネス用語(ただし、Developerは理解しない)
- 普段使うビジネス用語(ただし、ソフトウェア設計には登場しない)
- Developer
- 設計の技術的側面
- 技術用語
- 技術設計パターン
ユビキタス言語
- ドメインモデル用語
- Bounded Contextsの名前 (Chap.14)
- 大きな規模の構造の用語 (Chap.16)
- パターン名
Developerの間での会話、Domain Expertの間での会話、コードの表現そのもの、これら全てが(共有されたドメインモデル由来の)同じユビキタス言語に基づく
基本的にドキュメントは書かなくていい
- 動いているコードに不明確な部分はない
ドキュメントはコードが既にうまくやっていることについては記載しなくて良い
- コードが正確で詳細なプログラムの振る舞いの仕様であり、既に詳細を提供している
コードに付与されたコメントもコードの動き自体には影響を及ぼさないので、いずれ同期されなくなる
外部ドキュメントも同様、いずれ同期されなくなる
ドキュメントは、コードと会話を補足するような、もっと大きな構造に対して記載する
ドキュメントは、ユビキタス言語で書かれており、コードに組み込まれた言語で書かれていること
(まとめ)ドキュメントは最小限に、コードと会話を補足するものと弁えよ
良いDeveloperは、変数名、メソッド名、コードの体系が、正しく"主張"しているコードを書く
- 振る舞いは正しいが、メソッド名が不明瞭、誤解を招く、あるいは更新し忘れて古くなっているコード
- テストのAssertは正確だが、変数名やコード全体から伝わるストーリーはそうではない
UMLだけでなく、フローチャートなどを書くと、別の視点から全体像を把握できるので良い
Chap.3 Binding Model and Implementation
一回限りのドメインモデルは無関係な事柄に対して深みにハマり、一方で重要な事柄を見落とす
設計とその中心部分がドメインモデルに紐づいていなければ、そのモデルに価値はなく、ソフトウェアの正確さは疑わしい
- 同時に、モデルと設計機能の複雑な紐付きは理解しにくく、実質的には、設計の変更に対する保守は不可能
- 分析と設計には絶望的な分断が発生し、それらの活動で得られた洞察は他方に還元されることはない
オブジェクト指向プログラミングは有効であり、それはモデリングの概念に基づき、モデルの構想の実装を提供するためである
C言語のような純粋な手続き型の言語では、ドメインモデルの実装は困難である(関数の集まり以上の意味を持たせられない)
オブジェクト指向プログラミングは、ドメイン駆動設計に用いられる主要なアプローチ
モデリングと実装の二つのタスクを分けることで問題が生じる
- Developerがドメインモデルに対して責任を感じず、そのアプリケーションに対してモデルがどのように機能するかを理解しないのなら、そのモデルはそのソフトウェアと無関係になってしまう
- Developerがコードの変更がモデルの変更であることを認識していなければ、リファクタリングはモデルを強めるどころか弱めてしまう
- モデラー(ドメインモデルを作る人)が実装プロセスから隔絶されている時、彼や彼女は実装の制約を決して掌握せず、あるいは即座に失う
コードの変更は、モデルを変更する
コードを変更する責任を持つものは、そのコードを通じてモデルを表現することを学ばなければならない
全てのDeveloperはモデルに関する幾らかのレベルの議論に参画しなければならず、Domain Expertとも話をしなければいけない
モデリングと実装の明確な分離は機能しないが、大きなプロジェクトでは未だに技術リーダーが必要である
- 高レベルの設計をまとめたり、最も難しい、クリティカルな決定の解決を助ける人
ユビキタス言語は、Developer、Domain Expert、そのソフトウェアの間に流れる全ての情報のチャネルである
II. The Building Blocks of a Model-Driven Design
- 設計は "responsibility-driven design", "design by contract" そして "object-oriented design" に大きく従うが、 Developerはよく知られた基礎がどのようにモデル駆動設計を支えているのかを理解する必要がある
Chap.4 Isolating the Domain
- かなり複雑なタスク扱うプログラムの作成は、関心の分離を求める
Layered Architecture
- User Interface -> Application -> Domain -> Infrastructure
各レイヤーは緩く結合しており、その依存性は上位から下位の一方向のみ
- 上位のレイヤーは下位のレイヤーの要素を利用できるが、その逆は不可
- あるレイヤーの要素は同じレイヤーの他の要素か、その配下のレイヤーの要素にのみ依存する
User Interface (Presentation) Layer
- ユーザーに情報を表示したり、ユーザーのコマンドの解釈に責任を持つ。ユーザーではなく他のシステムのインタフェースとなることもある
Application Layer
- このレイヤーが責任を持つタスクはビジネスにとって意味のあるものであるか、他のシステムの同じレイヤーでの相互作用に必要
- このレイヤーは薄くなければならない
- ビジネスロジックや知識を持たない
- タスクを調整したり、次の下のレイヤーでのドメインオブジェクトの共同作業に仕事を委譲するだけ
- ビジネスシチュエーションを反映した状態は持たない
- ユーザーやプログラムに対するタスクの進行状況を反映する状態を持つ
Domain (Model) Layer
- ビジネスシチュエーションに関する情報、ビジネスルールといった、ビジネスの概念の表現に責任を持つ
- ビジネスシチュエーションを反映する状態はこのレイヤーで制御され使用されるが、それを保存する技術的詳細はInfrastructureに以上される
- ビジネスソフトウェアの心臓とも言えるレイヤー
Infrastructure Layer
- より高位のレイヤーをサポートする一般的な技術的能力を提供する
Layered Architectureによって
- それぞれの層は上位の層にのみ依存するため、保守しやすく
- ドメインモデルはDomain Layerのみに記述され、
- Domain Layerは他のレイヤーのロジックを含むことがないため、ドメインルールだけに集中して実装できる
Layered Architecutureを採用することで、分散システムにおいてそれぞれの層を別々のサーバーにデプロイすることもできる
ドメインの実装の分離が、ドメイン駆動設計には必須である
SMART UIアーキテクチャーは、ドメイン駆動設計のアンチパターンであるので共存できない
Chap.5 A Model Expressed in Software
EntityとValue Objectとの違いは、
- オブジェクトが継続性と一意性をもった、異なる状態を通じて追跡される何かを示すかどうか(Entity)
- 何かの状態を表現する属性なのか(Value Object)
アクションやオペレーションと明確に表現されるそのドメインの様子は、Serviceとして表現される
可能な限りオブジェクトの関係を制限することは重要
- ある方向の紐付きが、他方よりもずっと意味があって重要である
Many-to-Many の横断的な方向の紐付きを制限することは、その実装をずっと簡単な設計である One-to-Many に効果的に減らす
関係を一貫して制限することは、双方向の関係がそのドメインの意味のある特徴であるとき、既存の双方向の紐付きに重要さを与える
Entity
- 幾らかのオブジェクトは主にそれらの属性によって定義されない。それらは、時とはっきりした表現をしばしば跨って動くアイデンティティのスレッドを表す
- 時々そのようなオブジェクトは、属性が異なっていても、もう一つのオブジェクトと合致しなければいけない
- オブジェクトは、同じ属性を持っていたとしても、他のオブジェクトと区別されなければいけない
アイデンティティの誤認識は、データの不整合を招く
そのアイデンティティによって主に定義されるオブジェクトは、Entityと呼ばれる
- そのクラス定義、責任、属性、そして紐付きは、それらが何であるかの周辺を解決すべきであって、それが持つ特定の属性ではない
Entity は、ライフサイクルを通じた継続性、ユーザーにとって重要な属性から独立した特徴を持つ
同じものであることを何が意味するのか、ドメインモデルは定義しなければいけない
アイデンティティはこの世界の物に対して本来備わっているものではない。それは後付けされた意味であり、なぜなら便利だからである
- 実際に、この世界の同じ物が、ドメインモデルにおいてEntityとして表されたりそうでなかったりする
Entityの最も基本的な責務は継続性を確立することであり、その結果、振る舞いが明確かつ予測しやすいものになる
- 属性や振る舞いにさえ集中せず、それを特定し、それを探したり合致したりするために広く使われる、最も特徴的な本来備わったものまで、そのEntityオブジェクトの定義を削ぎ落としましょう
- 概念に本質的な振る舞いや、その振る舞いで要求される属性だけを追加しましょう
- 加えて、振る舞いと属性をコアEntityに紐づく他のオブジェクトに移動させられないか、目を向けてみましょう
オブジェクト指向言語は、二つのオブジェクトが同じメモリの場所を参照しているかどうかで評価する "Identity" を持っているが、これはドメイン駆動設計におけるEntityのIdentifierとしては不適切である
真のユニークKeyがオブジェクトになければ、それぞれのインスタンスにnumberまたはstringの一意なシンボルを付与することが代替策となる
- このシンボルはイミュータブルで、作成されたら変更されてはならない
Value Object
- そのドメインの説明的な側面を概念的なアイデンティティなしで表すオブジェクトが、Value Objectである
- Value Objectは、それらが何であるか(誰またはどの、ではない)にのみ注意を払う設計の要素を表すためにインスタンス化され、その後は状態が変化することはない
- Value Objectはイミュータブルとして扱う
ドメインによっては、他のドメインでEntityであるものがValue Objectになり得る(その逆も然り)
番地、町、郵便番号は、Personオブジェクトの個別の属性ではない
- それらはPersonオブジェクトをシンプルにするような、住所全体の一部であり、よりまとまったValue Objectである
- 住所Value Objectに番地、町、郵便番号が含まれ、Personオブジェクトは住所Value Objectをもつ
Value Objectは、どのインスタンスであるかは気にかけない(同じ状態を持つ別のインスタンスであっても良い)
値が頻繁に変わったり、作成や削除のコストがとても大きい場合に限り、変更可能なValue Objectを定義することもあるが、特例に過ぎない
- もしミュータブルなValue Objectを定義した場合、それは共有されてはならない
Value Object間の双方向の紐付きを完全に排除することに試みよ
Service
- EntityにもValue Objectにも回帰しない、重要なドメインのオペレーションが存在し、これらはモノではなく本来は活動やアクションである
- Serviceはモデルオブジェクトとして、何らかのオペレーションを実行するだけのオブジェクトとして現れる
Serviceはそれ自体に状態を持たず、そのドメインにおいてそのServiceが持つオペレーション以外の意味を持たない
あるドメインの幾らかの概念は、オブジェクトとしてのモデルに対して自然ではない
要求されるドメインの機能をEntityやValue Objectの責務に強制することは、モデルベースオブジェクトの定義を歪めてしまうか、意味のない人工的なオブジェクトを追加してしまう
Serviceはそのモデル内に独立している、インタフェースとして提供されるオペレーションであり、EntityやValue Objectが行う状態のカプセル化をすることはない
オペレーション名はユビキタス言語から由来したもの、あるいはそれに組み込まれるべきである
Serviceの引数や結果は、ドメインオブジェクトであるべき
良いServiceは3つの特徴を持ち
- そのオペレーションは、EntityやValue Objectの自然な部分ではないドメインの概念に関連する
- そのインタフェースは、そのドメインモデルの他の要素に関して定義される
- そのオペレーションはステートレスである
Application ServiceとDomain Serviceを区別するのは難しいことがあるが
- Serviceが基本的なビジネスロジックを含むものはDomain Service
- Application Serviceは、ビジネス的な意味合いを全く持たない
Domain Serviceは複数のEntityに跨る処理を行う
- その場合、Domain Service自体は調整役に徹し、それらのEntityが大抵のことを行う
Module (A.K.A Packages)
- Moduleはモデルの2の観点を提供する
- 全体に圧倒されることなく、Module内の詳細を見ることができる
- 内部的な詳細を見ずに、Module間の関係を見ることができる
- Moduleは、概念の結合したまとまりを表す
- Moduleの名前は、ユビキタス言語の何かの意味を伝える
Module(名)のリファクタリングはあまり起こらず、それはチームのコミュニケーションを混乱させることがある
- 結果として、Moduleの構成と名前は、そのクラスよりも、そのモデルのだいぶ前の形態をしばしば反映する
Modeling Paradigmは、コミュニティが成熟しているObject-Oriented Designを採用すべき
- しかし、これにずっと制限すべきでもなく、数学的な計算やグローバルロジックなどの実装にはObject-Oriented Designは不向きのため、その際は別のParadigmを選択すべきである
Chap.6 The Life Cycle of a Domain Object
Aggregates
- Aggregateは、データ変更の目的でユニットとして扱う、紐づいたオブジェクトのクラスタである
Aggregateはrootとboundaryを持つ
- rootは、そのAggregate内に含まれる、単一の特定のEntity
- boundaryは、そのAggregateに何があるかを定義する
Aggregate boundaryの外にあって、その内側の何かに対して参照を持つものはない(root Entityを除く)
root Entityは内部のEntityに対する参照を他のオブジェクトに渡すことができるが、それらのオブジェクトはそれらを透過的にのみ使うことができ、それらはその参照に固定することはないかもしれない
rootはValue Objectのコピーを他のオブジェクトに手渡すかもしれず、それに何が起きても問題ではない。なぜなら、それは単なる値であり、Aggregateとのいかなる紐付きも、もはやもたないだろうからである
上述の必然的帰結として、Aggregate rootだけが、データベースクエリから直接得られる。全ての他のオブジェクトは、横断的な紐付きによって発見されなければいけない
Aggregate内のオブジェクトは、他のAggregate rootに対する参照を持つことができる
削除オペレーションは、そのAggregate boundary内の全てを一度に削除しなければならない
- ガベージコレクションによって、これは容易である。そのroot以外のあらゆるものに対する外側の参照がないため、そのrootを削除し、その他の全ては回収されるだろう
そのAggregate boundaryないのいかなるオブジェクトに対する変更がコミットされたとき、そのAggregate全体の全ての不変性は満たされなければならない
Factories
- 責務が他のオブジェクトの作成であるようなプログラムの要素は、Factoryと呼ばれる
Factoryは、複雑なオブジェクトまたはAggregateを作るのに必要な知識をカプセル化する
それぞれの作成メソッドはatomic(それ以上分割することができない)、かつ作成された、オブジェクトまたはAggregateの全ての不変性を強制する
Factoryは、一貫した状態においてオブジェクトを生成できるだけであるべき
- Entityにとってこれは、全ての普遍性が満たされた状態で、もしかしたらオプション要素が今もなお追加される、Aggregate全体の作成を意味する
- イミュータブルなValue Objectにとってこれは、全ての属性がそれらの最後の状態に初期化されることを意味する
もしそのインタフェースが、正しく作成され得ないオブジェクトを要求できるならば、その時は例外が投げられるべき、あるいは不適切な戻り値が可能ではないことを保証するような何か他のメカニズムが呼び出されるべきである
Factoryは、作成された具象クラスというよりも、望まれる型に対して抽象化されるべきである
- 洗練されたFactory pattern(Gamma et al.1995)がこれを手助けする
もし既存のAggregate内に要素を追加したいならば、そのAggregateのrootにFactoryメソッドを作成する
- 要素が追加されたものとしてのAggregateの統合性を保証する責務をrootに与えつつ、いかなる外部のclientからそのAggregateの内部の実装をこれは隠す
もう一つは、他のオブジェクトの生成に密接に関与しているオブジェクト上にFactoryメソッドを配置することだが、一度それが作成されたら、それはそのプロダクトを所有しない
- そのデータと場合によっては一つのオブジェクトのそのルールがオブジェクトの作成においてとても支配的であるとき、そのオブジェクトを作成するためのどこか別の場所で使われる情報が、その生成者から抜き出されることをこれは防ぐ
Factoryではなくpublicなconstructorを使うべきなのは、以下の状況である
- クラスが型であること。それはいかなる興味深いヒエラルキーの一部ではなく、インタフェースを実装することによってポリモーフィックに使用されない
- clientは、おそらくStrategyパターンを選択する手段として、その実装を気にかける
- そのオブジェクトの全ての属性はそのclientによって利用可能であるため、そのclientに提示されたconstructorの内部では、どんなオブジェクトの作成もネストされない
- オブジェクトの生成が複雑ではない
- publicなconstructorはFactoryと同様のルールに従わなければならない
- それはその作成されたオブジェクトの全ての不変性を満たすような、atomic(それ以上分割できない)操作でなければならない
他のクラスのconstructorの内部でconstructorを呼ぶことは避けよ
引数に、そのモデル内のproduct(class)に密接に関連しているオブジェクトを渡すのも良い
- ex) Carクラスのconstructorに、Handleクラスのインスタンスを渡す
引数は抽象型を使い、それらの具象クラスの型は使ってはならない
Factoryは不変性のチェックをそのproduct(class)に以上することができ、大抵はこれがベストである
いくつかの状況では、不変性のロジックをFactory内に配置し、そのproduct(class)のごちゃごちゃを低減することに有用性がある
- これは特に、多くのオブジェクトに渡るAggregateのルールだと好ましい
- これは特に、他のドメインオブジェクトに紐づいたFactoryメソッドだと、好ましくない
Entityのアイデンティティ属性の割り当てに適用されるルールが存在していたとすると、Entity作成後はそのアイデンティティはイミュータブルであり、Value Objectは完全にイミュータブルである
オブジェクトは、そのactiveな活動期間内で決して適用されることのないロジックの周辺を保持する必要はない
- このような場合に、そのproduct(class)をシンプルにするため、そのFactoryが不変性を置くためのロジック置き場となる
Entity FactoryとValue Object Factoryは2つの点で異なる
- Value Objectはイミュータブルであり、すなわちそのproduct(instance)はその最終形態において完璧な状態で姿を現す
- よって、そのFactoryオペレーションは、そのproduct(instance)の完全な説明を考慮しなければいけない
- Entity Factoryは、有効なAggregateを作るために求められる必須の属性だけを受け取る傾向がある
- 詳細は、もしある普遍性によってそれらが求められなければ、後で追加され得る
オブジェクトの再構成に使用されるFactoryは、作成に使用されるそれととても似ているが、2つの大きな相違がある
- 再構成に使われるEntity Factoryは、新しい追跡IDをを割り当てない
- よってID属性は、保存されたオブジェクトの再構成を行うFactory内でのinput parameter(引数)の一部でなければならない
- オブジェクトを再構成するFactoryは、不変性の違反を違った形で扱う
- 新しいオブジェクトの作成の間、Factoryは不変性が満たされない時は単純に待つべきだが、より柔軟なレスポンスが再構成に必要かもしれない
- オブジェクトがそのシステムのどこか(そのデータベース内など)に既に存在する場合、その事実は無視できない(分散システムにおける話?)
- そのような矛盾を、それは再構成を新しいオブジェクトの作成よりもさらに挑戦的なものにし得るのだが、補完するための何らかの戦略がなければならない
Factories(Factoryを構成要素として述べた時の名前)は通常、そのモデルのいかなる部分も表現しないが、今のところはDomain設計の一部であり、そのオブジェクトのモデル表現力をはっきりさせるのに役立っている
Repositories
- Repositoryは(通常模倣された)概念のセットとして一定の型の全てのオブジェクトを表す
- それは、より入り組んだクエリ能力を除き、コレクションのように振舞う
- その適切な型のオブジェクトは追加または削除され、Repositoryの背後の機構はそれらをinsertあるいはデータベースからそれらを削除する
上述の定義は、ライフサイクルの早くからその終わりまで、Aggregateのrootに対するアクセスを提供する責務の凝集したセットを集める
Clientは、典型的には一定の属性の値で指定した基準に基づくオブジェクトをselectするクエリメソッドを使って、Repositoryからオブジェクトをリクエストする
直接的なアクセスが実際に必要なAggregate Rootに対してのみ、Repositoryを提供せよ
全てのオブジェクトストレージとアクセスをそのRepositoryに委譲しながら、Clientにはそのモデルに集中させよ
Repositoriesはいくつかの利点がある:
- 一貫したオブジェクトの取得とそれらのライフサイクルの管理のための、シンプルなモデルを持つClientをそれらは示す
- それらは、ApplicationとDomainの設計を、一貫したテクノロジー、複数のデータベース戦略、あるいは複数のデータソースからでさえ分離する
- それらは、オブジェクトアクセスについての設計の決定を伝える
- テスト中の使用(典型的にはインメモリコレクションの使用)に対し、ダミーの実装の簡易な代用を許可する
最も簡単なRepositoryは、特定のパラメーターを持つハードコードされたクエリを持つ
- 例えば、EntityをそのIDで取得する、など
Developerは使用しているカプセル化された振る舞いの意味合いを理解しなければいけない
- Repositoriesが異なる方法で使用される、あるいは異なる方法で機能する時、パフォーマンスの意味合いが極端なものになり得る
- それは、その実装に対する詳細な親しみを意味するわけではない
トランザクションの制御は、そのclientに委ねておく
一般的には、使用するフレームワークと戦うべきではない
- ドメイン駆動開発の基礎を保つ方法を探し、そのフレームワークが敵対する時、その細目を手放せ
- ドメイン駆動設計の概念と、そのフレームワークの概念の間の親和性を探せ
FactoriesとRepoisitoriesはそれぞれ独立した責務を持っている
- Factoryは新しいオブジェクトを作る
- Repositoryは古いオブジェクトを探す
- Repositoryのclientは、そのオブジェクトがメモリ内にあるといった幻想を与えられるべきである
- これらの2つの観点は、Repositoryに、オブジェクトの作成をFactoryに委譲させることで共存し得る
既存オブジェクトを再構成するには、clientがRepositoryにクエリを発行し、Repositoryはデータベースにクエリを投げデータを取得、Factoryにそのデータを渡して再構成し、その戻り値をclientに返す
新しいオブジェクトを保存するには、clientがFactoryに必要な値を渡してオブジェクトを作成し、Repositoryのメソッドにそのオブジェクトを渡し、Repositoryはそのオブジェクトをデータベースに保存する
FactoryとRepositoryを組み合わせる誘惑に人々が駆られるその他のケースは、"find or create" 機能であるが、この機能は避けるべきである(僅かな利便性でしかない)
- 求めるオブジェクトが見つからなければ、新しく作成したオブジェクトが返される機能
- ("update or insert"も同様だと考えられる)
技術的には、リレーショナルテーブルの設計はそのドメインモデルを反映する必要はない
問題なのは、複数の重複したモデルがかなり複雑なことである
- 分析と設計モデルの分離を避ける、モデル駆動開発に対して示された同じ引数の多くが、このミスマッチに当てはまる
- これは必然の結果としてそのオブジェクトモデルのリッチさにおいて幾らかの犠牲を伴い、時々そのデータベース設計において妥協(選択的な非正規化など)が生まれるが、違ったことをするのはモデルと実装の密な結びつきを緩めるリスクがある
- このアプローチは、短絡的な1オブジェクト/1テーブルマッピングを要求しない
マッピングが透過的であること、すなわちコードを見たりマッピングツール内のエントリを読んだりすることで容易に理解できることがとても重要である
データベースがオブジェクトストアとして見られる時、データモデルとオブジェクトモデルを深く分岐させてはならない(マッピングツールのパワーに関係なく)
- そのリレーショナルモデルを親密にしておくために、オブジェクトの関係のリッチさの幾らかを犠牲にせよ
- もしオブジェクトのマッピングの単純化を手助けをするなら、正規化といった、公式のリレーショナル標準の幾らかを妥協せよ
実行速度問題を解決するために、突飛な設計の変更が検討されるかもしれないが、オブジェクト指向ドメインの一貫した形として振舞うリレーショナルデータベースの重要な共通ケースのためには、シンプルな直結性が最善である
- テーブルの1行は、おそらくAggregate内に従属するものと一緒に、オブジェクトを含むべきである
- そのテーブルの外部キーは、もう一つのEntityオブジェクトに対する参照を説明しなければならない
- このシンプルな直結性から時々外れることの必要性は、シンプルなマッピングの原則の全体的放棄に通じるべきではない
ユビキタス言語は、そのオブジェクトとリレーショナル構成物を一つのモデルに結ぶ手助けができる
- そのオブジェクトの名前と要素の紐付きは、リレーショナルテーブルのそれらに細心の注意を払って一致すべきである
Chap.7 Using the Language: An Extended Example
- モデルをこれまで紹介してきた構成要素で組んでいく
- 上記の決定をクロスチェックするため、定期的に複数のビジネスシナリオに踏み込み、アプリケーションの問題を効果的に解決できることを確認する
- (例えば、ビジネスにおいてXXXのケースの場合、その問題は解決できるか、具に検討する)
- Entityの何かの状態を変更するケース
- 前に使ったEntityを再利用するケース
- その場合、次の二つが実装案となる
- そのEntity上にFactoryメソッドを定義する
public XXX copy(String newId)
- 独立したFactoryにメソッドを作成する
public XXX newXXX(XXX prototype, String newId? /* この引数をなくして、メソッド内で割り当てても良い */ )
- 双方向の関連(association)を持つ場合、一方のEntityのconstructorでは参照先となるEntityのインスタンスを渡す
public XXX(String id) { trackingID = id; yyy = new YYY(this); ... }
- 双方向の関連を持つEntityのインスタンス化は複雑になるため、リファクタリングして再設計すると良い
- 双方向の関連を持つEntityの設計は、そのEntityに対してRepositoryを追加することで改善される場合がある(本文ではその例が紹介されている)
- Module名を決定し、それぞれの要素がどのようにユビキタス言語に貢献するかを示すことも含まれる
- Module名は、Developerが見て実装しやすいように決めるべきではない
- もし幾らか他システムとの統合が必要な場合、その責務を満たすServiceが作成され得る
III. Refactoring Toward Deeper Insight
便利なモデルの開発の成功は、次の3点に帰結する
- 洗練されたドメインモデルは、達成可能であり、割に合うものである
- そのドメインについて学ぶことに興味のあるDeveloperとDomain Expertの緊密な関わりを含む、リファクタリングの繰り返しのプロセスを通じることなしに、そのようなモデルが開発されることはほとんどない
- それらは、効果的に実装と利用するための洗練された設計スキルを要求するかもしれない
要求される仕様から名詞と動詞を特定して、それらを最初のオブジェクトやメソッドとしてモデリングしたものは、初学者にこそ分かりやすいものの、通常は鋭さにかける表面的なものである
- Domain Expertと数々の繰り返しプロセスを経て進化したモデルは、初心者にはより分かりにくいものの、Domain Expertにはとてもより関連したものとなる
深いモデルは、そのドメインの表面的な観点から脱却する一方で、Domain Expertの主要な関心の明快な表現と、彼らの最も関連した知識を提供する
そのようなモデルが大抵いつも持っているある機能はシンプルで、それでいて限りなく抽象的で、ビジネスエキスパートが使いたくなるような言語である
モデル駆動設計は2つの足から立っている
- 深いモデルは、表現豊かな設計を可能にする
- 同時に、その設計が、実験と何が起きているのかを示すための明快さをDeveloperに許可するような柔軟性を持つ時、設計は実際にはそのモデル発見プロセスに洞察を与える
- そのフィードバックループの半分は本質的であり、なぜなら我々が探し求めているそのモデルは、単なるアイデアの素晴らしい集まりではなく、それはそのシステムの基礎だからである
Chap.8 Breakthrough
- リファクタリングの恩恵は線形的ではなく、ある時を境にその価値がとても大きくなる場合がある。これをBreakthroughと呼んでいる
コードとモデルのそれぞれの改善は、Developerにより明確な見通しを与え、この見通しが、洞察のBreakthroughに対して可能性を生み出す
より深いモデルが構築できた後では、「我々(Domain Expert)にとって技術的すぎる」としばしば示していたモデルの図表が、彼らにとって説得力のあるものになった
より深いモデルに対するBreakthroughの可能性がより深いモデルを提供する時、それはしばしば怖いことである
- そのような変更は、大抵のリファクタリングよりもより良い機会とより高いリスクがある。また、タイミングも都合が悪いかもしれない
- 本当に深いモデルへの遷移は、我々の思考における大きな変化であり、その設計に対して主要な変更を要求する
Chap.9 Making Implicit Concepts Explicit
ドメインモデルとその付随するコードの多くの変化は、議論のなかで暗示されてきた概念をDeveloperが認識した時に発生したり、あるいはその設計の中に暗黙的に存在しており、そのためDeveloperは一つまたはそれ以上のオブジェクトや関係を伴ってそのモデルの中にそれを明示的に示す
Developerは隠れた暗黙の概念を明らかにするヒントに対して敏感でなければならず、そして時々、彼らはそれらを先を見越して探し出す必要がある
- 大抵のそのような発見は、
- そのチームの言語に耳を傾けたり
- その設計の不器用さやDomain Expertの文章内の見せかけの矛盾を注意深く調査したり
- そのドメインの文学を採掘したり
- 非常にたくさんの実験をすることからもたらされる
Domain Expertが使用する言語に耳を傾けよ。以下がそのモデルのためになるかもしれない、概念のヒントである
- 簡潔に何か複雑なことを述べている用語はあるか
- (多分外交的に)彼らはあなたの言葉の選択を正しているか
- 特定のフレーズを使った際、彼らの表情に浮かぶ困った様相は消えるか
ユーザーあるいはDomain Expertが、その設計のどこにもない語彙を使った時、それは警告のサインである
- DeveloperとDomain Expertの両方がその設計にない用語を使っている時、それは疑いなく強い警告である
(Domain ExpertとDeveloperがホワイトボードにクラスのスケッチを書きながら会話している)
- (スケッチはクラス名と、そのAggregate(何対何なのかを含む)を書いた簡単な図)
深掘りする場所は、設計の最も下手な部分である
- 手続きが、説明し難い複雑なことをしている場所
- 全ての新たな要求が、複雑性を追加するように見える場所
時々、見逃している概念さえあると認識することは困難になり得る
異なるDomain Expertは、彼らの経験と必要に基づいた異なる方法で物を見る。同じ人間でさえ、注意深い分析の後では論理的に一貫性のない情報を提供する。そのような厄介な矛盾は、プログラムの要求を掘り下げる時ならいつでも我々が遭遇するものだが、より深いモデルへの素晴らしい手がかりになる
- 幾らかは用語内のただのバリエーションであり、あるいは誤解に基づくものである
- しかし、Domain Expertによる2つの事実の記述が矛盾するように見える残余がある
全ての矛盾を調和することは実践的ではなく、それは理想的でさえないかもしれない
- しかし、矛盾が放置されている時でさえ、二つの記述の両方がその同じ外部の現実にどうやって適用するのかを熟孝することは、明らかになり得る
どの分野でも、基本的な概念と慣習として認められた知恵を解説する本を見つけることができる(その本を読め)
- そのドメインでの開発経験を持った別のソフトウェアプロフェッショナルによって書かれた本を読むのもまた、もう一つの選択肢である
(Domain Expertが別のことに責務をもち、ソフトウェア開発プロジェクトの協力に興味がなかったため、本屋に行ってそのドメインの入門書を探してざっと読む例が示されている)
設計でつまずきを回避しようとすることはより低い品質の結果に帰結し、なぜならそれがより少ない経験に基づくだろうからである
- そして、それは一連の素早い実験よりも優に時間がかかり得る
制約はモデルの概念の重要なカテゴリを具体的に作る
- それらはしばしば暗黙的に現れ、そしてそれらを明示的に表現することは設計を大いに改善し得る
制約をその独自のメソッドに因数分解することは、我々は設計においてその制約を明示的にする意図の明確な名前をそれに付与できる
以下は、制約がホスト(Root?)オブジェクトの設計を歪めている幾らかの警告サインである
- 制約の評価が、その他の点でそのオブジェクトの定義に合致しないデータを要求する
- 関連したルールが、その他の点では共同体ではないオブジェクトの間で重複や継承を強制している複数のオブジェクトに現れる
- 多くの設計と必要要件の会話がその制約の周りを循環するが、その実装の中では手続きのコードの中に隠されている
明示的にされるはずのプロセスを、隠されるそれと区別するための鍵はシンプルである:
- これはDomain Expertが話すことか
- それとも、それはコンピュータプログラムの機構のほんの一部か
SPECIFICATION
Predicatesは、
- true or falseを評価し、
- より複雑なルールを表現するために、And や Or といった演算子を使って組み合わされ得るような関数である
ビジネスルールはしばしば、明白なEntityやValue Objectのいかなる責務にも合致せず、それらの多様性や組み合わせはそのドメインオブジェクトの基本的な意味をひっくり返し得る
しかしそのルールをDomain Layerの外に移すことはより一層悪いことであり、というのもそのドメインコードはもはやそのモデルを表現しないためである
ロジックプログラミングは、分離され、組み合わせ可能である、Predicatesと呼ばれるルールオブジェクト(メソッドの間違い?)の概念を提供するが、オブジェクトを伴うこの概念の完全な実装は厄介である
それがより特化された設計ほど意図を伝えないことは、またとても一般的である
我々はPredicatesの概念を拝借でき、Booleanに評価する特化されたオブジェクトを作成できる
手に負えないそれらのテストメソッドは、それら独自のオブジェクトにきちんと進出するだろう
- それらは、分離されたValue Objectに因数分解され得る真実性のテストではほとんどない
- この新しいオブジェクトはもう一つのオブジェクトを評価し、その新しいオブジェクトのPredicatesがそのもう一つのオブジェクトに対してtrueであるかを見ることができる
SPECIFICATION(パターン)は、もう一つのオブジェクトの状態における制約を述べるが、それは存在するかもしれないし、しないかもしれない
特化した目的に対する、明示的なPredicateライクなValue Objectを作成せよ
SPECIFICATIONは、オブジェクトが幾らかの基準を満たすかどうかを決定するPredicateである
デザインパターンはクックブックではない
- それは、あなたの解決策を開発するために、あなたが経験の基から出発することを許し、そしてあなたが何をやっているのかについて話すための幾らかの言語をあなたに提供する
我々は、以下の3つの目的の一つかそれ以上のために、オブジェクトのその状態を指定する必要があるかもしれない。これらの3つの利用は、概念レベルの上では同じである
- Validation: それが幾らかの必要性を満たすかどうか、あるいは幾らかの目的に対して準備が整っているかどうかを見るために、オブジェクトをValidateするため
- Selection(or Querying): コレクションからオブジェクトを選択するため
- Building to order(Generating): 幾らかの必要性を満たすための新しいオブジェクトの作成を指定するため
SPECIFICATIONを使うことなしに、必要なオブジェクトを作成する手続きや命令のセットを持ったジェネレータが書かれ得る
- このコードは、暗黙的にそのジェネレータの振る舞いを定義する
代わりに、説明的なSPECIFICATIONに関して明示的に定義されたそのジェネレータのインタフェースは、そのジェネレータの生成物を制限?する
このアプローチはいくつかの利点がある:
- そのジェネレータの実装は、そのインタフェースから分離されている。そのSPECIFICATIONはそのアウトプットに対する要求を宣言するが、その結果がどのように到達されるのかを定義しない
- そのインタフェースはそのルールを明示的に伝え、その結果、その操作の全ての詳細を理解することなしに、そのジェネレータから期待する物をDeveloperは知ることができる。手続き的に定義されたジェネレータのその振る舞いを予測する唯一の方法は、コードを実行するか、あるいはコードの全行を理解することである
- そのインタフェースはより柔軟であり、あるいはより多くの柔軟性に伴って洗練され得るが、それはなぜなら、そのジェネレータがそのSPECIFICATIONの一字一句を満たすよう唯一義務付けられた一方で、その要求の内容がそのクライアントの手中にあるからである
- 大事なことを言い忘れていたが、この種類のインタフェースはテストしやすく、それはなぜなら、そのアウトプットのバリデーションでもあるそのジェネレータにインプットを定義するための明示的方法を、そのモデルが含むからである
(プロトタイプを素早く作ることと、インタフェースと実装を分けることで、複数チームでの開発でも並行して進めることができる)
Chap.10 Supple Design
- もしDeveloperがそれを使うためにコンポーネントの実装を考慮しなければならないのなら、カプセル化の価値は失われる
- もし実装したDeveloper以外の誰かが、その実装に基づくオブジェクトや操作の目的を推測しなければならないとしたら、新しいDeveloperはその操作やクラスが果たす目的をただの偶然によって推測するかもしれない
もしあれがその意図でなければ、そのコードは束の間の間機能するかもしれないが、その設計の概念的な基礎は崩壊していき、その2人のDeveloperは行き違うだろう
クラスまたはメソッドの形で概念を明示的にモデル化することの価値を得るには、我々はこれらのプログラムの要素にそれらの概念を反映する名前を与えなければならない
- クラスとメソッドのその名前は、Developerの間での会話と、そのシステムの抽象化を改善するための素晴らしい機会である
型名、メソッド名、引数名全ては、INTENTION-REVEALING INTERFACEを構成するために結びつく
クラスや操作が約束することをするための手段を参照することなく、それらの効果や目的を説明するために、クラスや操作を命名せよ
- これは、Client(利用する側の)Developerがその内部を理解する必要をなくす
- これらの名前は、そのユビキタス言語に従うべきであり、その結果チームメンバーはそれらの意味を素早く推測できる
- 振る舞いに対して、それを作る前にテストを書け、あなたの思考をClient(利用する側の)Developerモードに矯正するために
(1. あるドメインモデルのコードを書いたら、次にテストコードを書く)
(2. 利用する側のクライアントアプリケーションを書いているつもりで(Client(利用する側の)Developerの観点から)、そのドメインモデルのオブジェクトのインタフェース設計を探求するために、そのテストを書き直す)
(3. 最初は、そのテストはコンパイルさえできない)
(4. そのドメインモデルのクラスを、テストが通るようにリファクタリングする)
SIDE-EFFECT-FREE FUNCTIONS
- 複数のルールあるいは計算の組み立ての相互作用は、予測するのが極めて難しくなる
安全に予測可能な抽象化なしに、Developerはビルド実行可能な振る舞いのリッチさに低い制限を課し、その結合性の爆発を制限しなければいけない
副作用を生むことなしに結果を返す操作は、関数と呼ばれる。以下の理由で、関数はリスクが少ない
- 関数は何回でも呼び出すことができ、毎回同じ値を返す
- 関数は、ネストの深さを心配することなく、他の関数を呼ぶことができる
- 関数は、副作用を持つ操作よりもずっとテストしやすい
できるだけ多くのそのプログラムのロジックを、目に見える副作用のない結果を返す関数や操作に配置せよ
(目に見える状態の修正をもたらすメソッド)コマンドを、ドメインの情報を返さないとてもシンプルな操作に、厳格に分離せよ
その責務に合致する概念がそれ自体を示すとき、複雑なロジックをValue Objetに移すことで、副作用をさらに制御せよ
- (Value Objectはimmutableなので、その状態が変わるメソッドでは、新たなValue Objectを生成して返すことになる)
ASSERTIONS
- 操作、クラスの不変性、Aggregateの(メソッドなどが実行された)その後の状態を述べよ
- もしあなたのプログラミング言語でASSERTIONSを直接コードで書けないなら、それらのための自動ユニットテストを書け
- それがそのプロジェクトの開発プロセスのスタイルに一致するドキュメントや図の中に、それらを書け
- 学習曲線を加速させ、矛盾したコードのリスクを減らす、その意図されたASSERTIONSをDeveloperに推測させるような、わかりやすい概念のセットをもったモデルを得ることに努めよ
CONCEPTUAL CONTOURS
- デザインの要素(操作、インタフェース、クラス、そしてAggregate)を、そのドメイン内の重要な分割のあなたの直感を考慮しながら、凝集したまとまりに分解せよ
- 有用なリファクタリングを通じて変更と安定性の座標軸を観察し、これらのせん断パターンを説明する、基礎となるCONCEPTUAL CONTOURSを探し求めよ
- そのモデルを、それをその最初の場所での知識の実行可能エリアとするドメインの、矛盾のない側面と合わせよ
STANDALONE CLASSES
- Low couplingがオブジェクト設計の基礎である
- もし可能な時は、それを徹底せよ
- 全ての他の概念をその青写真から除外せよ
- その結果、そのクラスは完全に自己完結したものとなり、単独で学習や理解され得る
全てのそのような自己完結したクラスは、Moduleの理解の負荷を著しく緩和する
おそらくより多くの結合したクラスに保持されるValue Objectをモデル化することによって、最も複雑な計算をSTANDALONE CLASSESに分解せよ
CLOSURE OF OPERATIONS
最も関心を引くオブジェクトは、primitives単独で特徴付けられない物事を結局行う
合致するところでは、戻り値の型がその引数の肩と同じ操作を定義せよ
そのような操作は、その型のインスタンスの集合の元に閉じている
閉じた操作は、他の概念上に依存性を導入することなく、高レベルのインタフェースを提供する
このパターンはほとんどの場合、Value Objectのその操作に適用される
Chap.11 Applying Analysis Patterns
- (具体例の記述がメインのため、特に要約することなし)
Chap.12 Relating Design Patterns to the Model
STRATEGY (A.K.A POLICY)
- プロセスの変化する部分を、そのモデル内の分離した"strategy"オブジェクトに分解せよ
- ルールと、それが当時する振る舞いを別々に分解せよ
- STRATEGYデザインパターンに従う、そのルールや代替可能なプロセスを実装せよ
- strategyオブジェクトの多様なバージョンは、そのプロセスが完了され得る異なる方法を表す
- デザインパターンとしてのSTRATEGYの慣習的な見方が、異なるアルゴリズムを代用する能力に焦点を当てるのに対し、ドメインパターンとしてのその利用は、通常プロセスやポリシールールとして概念を表現するその能力に焦点を当てる
COMPOSITE
- そのCOMPOSITEの全てのメンバーを包含する抽象型を定義せよ
- 情報を返すメソッドはコンテナ上に実装され、それらの内容に関する集約された情報を返す
- "枝"点は、それら特有の値に基づいて、それらのメソッドを実装する
クライアントはその抽象型を処理し、コンテナと枝を区別する必要はない
唯一の要求は、そのパターンが概念的なドメインについて何かを示さなければならず、それは技術的な問題に対する技術的な解決策であるべきではない
Chap.13 Refactoring Toward Deeper Insight
- 以下の時にリファクタリングせよ
- その設計が、そのドメインのチームの現在の理解を表現しないとき
- その設計において、重要な概念が暗黙的である(そして、それらを明示的にする方法を見つけた)とき
- その設計の幾らか重要な部分をより柔軟にする機会を見つけたとき
リリースの前日はリファクタリングするな
技術的な名人芸のデモにすぎない"柔軟な設計"を紹介するな
どんなにそれが美しく見えても、Domain Expertに使用するよう納得させられない"より深いモデル"を紹介するな
物事に対して絶対的になるな、しかしリファクタリングを好む方向性の中のコンフォートゾーンを超えろ
IV. Strategic Design
Chap.14 Maintaining Model Integrity
- それぞれの用語が不明瞭ではなく、どのルールも矛盾していないような、モデル内部の一貫性は、unificationと呼ばれる
- 事実、巨大なシステムに対するそのドメインモデルのunificationの合計は、実行可能あるいは費用対効果の良いものではないだろう
BOUNDED CONTEXT
- あらゆる巨大なプロジェクト上で、複数のモデルが影響を及ぼしている
- 異なるモデルに基づくコードが組み合わさっている時でも、いつかソフトウェアはバグを含み、信頼できない、理解し難いものになる
- チームメンバーの間でのコミュニケーションは波乱含みになる
どんな文脈にモデルが適用されるべきで"ない"のかは、しばしば不明瞭である
モデルが適用される範囲内でのその文脈を明示的に定義せよ
チーム組織、そのアプリケーションの特定の部分での使用法、そしてコードベースとデータベーススキーマのような物理的な表明に関して、明示的に境界線を設定せよ
そのモデルをこれらの境界内において、厳密に一貫している状態を保て、しかし外側の問題によって気が散ったり混乱してはいけない
CONTINUOUS INTEGRATION
- 数多くの人が同じBOUNDED CONTEXT内で働いている時、そのモデルが砕ける強い傾向がある
- チームが大きければ大きいほど、その問題も大きく、しかしわずか3、4人だけが深刻な問題に直面しうる
そのシステムをさらに細かいCONTEXTSに分解することは結局、統合と一貫性の価値あるレベルを失う
CONTINUOUS INTEGRATIONは、その文脈内での全ての仕事が統合され、コードをばらばらに裂く人が現れたときに彼らがすぐ捕まえられて正されるほど充分頻繁に、一貫性あるものにされることを意味する
CONTINUOUS INTEGRATIONは、ドメイン駆動設計における他の事柄と同じように、二つの基準で稼働する
- モデル概念の統合
- その実装の統合
効果的なプロセスにおけるもう一つの側面は、滅多に正式に含まれることがないが、"概念"の統合である
そのモデルやアプリケーションの議論において、そのユビキタス言語を定期的に(使うことで)鍛えよ
分裂を素早く知らせる自動化されたテストを伴う、全てのコードと他の実装の成果物を頻繁に統合するプロセスを設定せよ
概念が異なる人々の頭の中で進化するにつれて、そのモデルの共有された見解を形作るために、そのユビキタス言語を容赦無く(使うことで)鍛えよ
CONTEXT MAP
- 他のチームの人々は、その文脈の境界にほぼ気づいていないかもしれず、無意識のうちにその縁を不鮮明にする変更を生んだり、その相互接続を複雑にしてしまうだろう
異なる文脈の間で結合が作られなければならない時、それらは互いに血を流す傾向がある
そのプロジェクト上で稼働するそれぞれのモデルを特定し、そのBOUNDED CONTEXTを定義せよ
- これはオブジェクト指向で設計されていないサブシステムの暗黙的なモデルも含む
それぞれのBOUNDED CONTEXTの名前を、そのユビキタス言語の一部となるように命名せよ
あらゆるコミュニケーションに対する明示的な翻訳の輪郭を描き、いかなる共有も強調しながら、そのモデルの間での接点の要点を描写せよ
"既存"の領域の地図を作成せよ。変形は後ほど着手せよ
CONTEXT MAPは現状を常に表すことに留意せよ
- 目にする関係性をただ描写せよ
- 現実での変更が完了するまで、CONTEXT MAPを変更してはならない
BOUNDED CONTEXTは名前を持つべきであり、その結果、それらについて会話することができる。それらの名前は、そのチームのユビキタス言語に立ち入らなければならない
全ての人は、その境界線がどこにあるのかを知らなければならず、コードのいかなる部分やいかなる状況のその文脈を認識できなければならない
等しく重要なのは、チーム全員が同じ方法で概念的な境界を理解するような方法で、それらを会話することである
もしBOUNDED CONTEXTの名前がユビキタス言語に立ち入るのならば、それらを議論に組み入れることは絶対不可欠である
- 「ジョージのチームのものが変わっているので、我々はそれに話しかける我々のものを変化しなければならない」などと言うな
- 代わりに「Transport Networkモデルが変わっているので、我々はそのBooking contextに対してtranslatorを変化させなければならない」と言え
SHARED KERNEL
(SHARED KERNELとは、CONTEXT MAPにおいて、BOUNDED CONTEXTで区切られた領域が重複している部分)
二つのチームが共有すると合意したドメインモデルの幾らかのサブセットを指定せよ
もちろんこれは、そのモデルのこのサブセットと共に、そのモデルの部分に関連したコードやデータベース設計のサブセットを含む
この明示的に共有されたものは特別なステータスをもち、他のチームに相談することなく変更すべきではない
機能的なシステムを頻繁に統合せよ、しかしなんとかしてそのチーム内のCONTINUOUS INTEGRATIONのペースよりも頻度は低くせよ
これらの統合において、両チームのテストを実行せよ
CUSTOMER/SUPPLIER DEVELOPMENT TEAMS
- 上流チームの自由奔放な開発は窮屈になり得るが、それは下流チームが変更に対する拒否権を持つ場合や、あるいは変更を要求する手続きがとても面倒な場合である
- 下流のそのシステムを破壊することを恐れて、上流チームは抑制さえされているかもしれない
一方で、上流の優先順位に翻弄されて、下流のチームは頼りなくなり得る
上流と下流のチームの間で、明白なcustomer/supplier関係を成立させよ
プランニングセッションで、下流チームは上流チームに対して顧客の役割を演じさせよ
下流チームの要求に対するタスクを交渉し、予算に計上すれば、全員がその傾倒とスケジュールを理解する
期待されるインタフェースを検証するであろう、自動化された受け入れテストを合同で開発せよ
- これらのテストを上流チームのテスト一式に追加し、その継続的インテグレーションの一部として実行されるようにせよ
このテストは、上流チームが下流への副作用を恐れることなく変更することを自由にする
- (また、下流のチームは、上流チームを定期的に監視することなく、彼らの仕事に集中できる)
受け入れテストの自動化は、このcustomer関係における重要な部分である
これらのテストに対するいかなる変更は、他のチームと共に会話することを要求するが、なぜならテストの変更はそのインタフェースの変更を意味するからである
CONFORMIST
- 二つの開発チームが、上流が下流チームの需要に供給するモチベーションを持たないような上流/下流の関係を持つ時、下流チームはなす術がない
- 利他主義は上流のDeveloperに約束するよう動機付けするかもしれないが、それらが満たされることはありそうもない
- それらの善意における信念は、決して手に入らないだろう機能に基づく計画を下流チームに作るよう仕向ける
- 下流チームが与えられたものと共に存続することを究極的に学ばない限り、そのプロジェクトは遅延するだろう
下流チームの要求に対して調整されたインタフェースは、そのカードの中にない
上流チームのそのモデルに奴隷のように忠実であることで、BOUNDED CONTEXTの間の翻訳の複雑性を排除せよ
これは下流のデザイナーの表現方法をけいれんさせ、そのアプリケーションの理想的なモデルをおそらく生むことができないにもかかわらず、CONFORMITYの選択は桁外れに統合をシンプルなものにする
またあなたは、supplierチームと共にユビキタス言語を共有するだろう
そのsupplierは運転席にいるため、彼らにとってコミュニケーションを容易にすることは良いことである
利他主義は、彼らに情報をあなたと共有させるには充分かもしれない
ANTICORRUPTION LAYER
- クライアントに彼らが保有するドメインモデルに関する機能を提供するための、隔離しているレイヤーを作成せよ
- そのレイヤーは、他のシステムに対する修正を少しかあるいは全く要求することなく、その既存のインタフェースを通じて他のシステムと会話する
内部的には、そのレイヤーは、その二つのモデルの間で必要なように、双方向において翻訳する
ANTICORRUPTION LAYERのpublicなインタフェースは通常、SERVICEのセットとして現れ、場合によってはENTITYの形を取ることもある
二つのシステムのセマンティクスの間の翻訳に対して責務を持つ、全く新しいレイヤーを構築することは、我々に次の機会を与える
- 他のシステムの振る舞いを再抽象化する
- 我々のモデルと一貫した状態で、我々のシステムにそのサービスや情報を提供する
ANTICORRUPTION LAYERの設計を組織する一つの方法は、システム間で会話するのに通常要求されるコミュニケーションと転送メカニズムを伴う、FACADES、ADAPTERSとtranslatorの組み合わせとしてである
FACADEは、クライアントのアクセスを単純化するサブシステムにむけた別のインタフェースであり、そのサブシステムを使いやすくする
- 特に必要でなければ不要
ADAPTERは、その振る舞いの実装者によって理解されたものよりも、異なるプロトコルをクライアントが使えるようにするラッパーである
我々が定義するどのSERVICEに対しても、そのSERVICEのインタフェースを支え、その他のシステムと同等なリクエストをどう作るか、あるいはそのFACADEを知るADAPTERが、我々には必要である
translatorは、必要な時にインスタンスかされる、軽量なオブジェクトになり得る
- それは状態を必要とせず、分散される必要がないが、なぜならそれはそれが仕えるADAPTERに属するためである
(自分のシステム -> Service A -> Adapter A -> Facade -> 他のサブシステムとして、ANTICORRUPTION LAYERが定義される)
- (Adapter Aは内部的にtranslatorをインスタンス化して利用する)
ANTICORRUPTION LAYERは、二つのBOUNDED CONTEXTを結びつける手段であることを覚えておくこと
SEPARATE WAYS
- 統合は常に高価である。時にはその利益が小さいこともある
- Developerが小さなスコープの中に、シンプルで特化した解決策を見つけられるような、他に対して全く結合を持たないようにBOUNDED CONTEXTを宣言せよ
OPEN HOST SERVICE
- サブシステムが多くの他者と統合されなければならない時、それぞれに対してtranslatorをカスタマイズすることは、そのチームの動きを取れなくする
ますます維持と、変更が生まれた時についての心配が出てくる
SERVICEのセットとして、あなたのサブシステムにアクセスを提供するプロトコルを定義せよ
そのプロトコルを開け、その結果あなたと統合する必要のある全員がそれを使うことができる
単一のチームが変わった要求を持つ時を除き、新しい統合の要求を扱えるように、そのプロトコルの質を高め、拡張せよ
そして、その特別なケースに対してそのプロトコルを増大するために、一回限りのtranslatorを使用せよ、その結果共有されたそのプロトコルはシンプルかつ理路整然とした状態でいることができる
PUBLISHED LANGUAGE
- 既存のドメインモデルに対する、そしてそれからの直接の翻訳は、良い解決策ではないかもしれない
- それらのモデルは過度に複雑か、あるいは下手に分解されているかもしれない。
- それらはおそらくドキュメント化されていない
一方がデータ入れ替え言語として使われいる場合、それは基本的に機能が停止したものになり、新しい開発の要求に応答することができない
必要なものとして共有化された言語に、かつその言語から翻訳しながら、コミュニケーションの共通媒体としての必要なドメインの情報を表現できる、よくドキュメント化された、共有の言語を使用せよ
(まずSEPARATE WAYSを考慮し、統合が本当に必要不可欠であれば、CONFORMISTあるいはANTICORRUPTION LAYERを選択せよ)
(今まで登場してきた概念をどう選択するかのガイドラインが記載されているので、必要に応じてp385以降を参照せよ)
一般的に、CONTEXTを分解することはとても簡単で、しかしそれらを統合したり、それらの間の関係を変化することは困難だがやりがいがある
Chap.15 Distillation
- ドメインモデルの戦略的な精製は、次のことの全てをする
- そのシステムの全体の設計と、どのようにそれが一緒に調和するのかを、全てのチームメンバーが理解することの手助け
- そのユビキタス言語に取り込むために、管理可能な大きさのコアモデルを特定することによる、コミュニケーションの手助け(ファシリテート)
- リファクタリングのガイド
- 最も価値のあるそのモデルの範囲における仕事への焦点
- アウトソーシング、既製コンポーネントの使用、割り当てに関する決定のガイド
CORE DOMAIN
巨大なシステムの設計において、全てが複雑かつ成功のために絶対的に必要な、本当に多くの貢献する構成要素が存在するので、実際のビジネス資源であるそのドメインモデルの本質は、不明瞭で手入れされていないことがあり得る
厳しい現実は、その設計の全ての部分が等しく精製されるわけではないことである
優先順位が設定されるに違いない
そのドメインモデルを資源とするため、そのモデルの重要な中心部は、アプリケーションの機能性を創造するために、こぎれいで十分利用されなければならない
しかし、まれなとてもスキルのあるDeveloperは、専門のドメイン知識なしで理解され得る、技術的なインフラストラクチャあるいはきちんと設計できるドメインの問題に引き付けられる傾向にある
CORE DOMAINは、あなたのシステムに付与されなければならない最も大きな価値がある場所である
そのモデルを煮詰めよ
CORE DOMAINを探し、それを脇役のモデルやコードの塊から容易に区別するための手段を提供せよ
最も価値があり、特殊化された概念を浮き彫りにせよ
その中心部を小さくせよ
トップの才能をそのCORE DOMAINに適用し、状況に応じて採用せよ
より深いモデルを探し、そのシステムの構想を十分満たすような柔軟な設計を開発するために、その中心部に労力を費やせ
それが精製された中心部をどのくらい支えるのかによって、その他の部分への投資を正当化せよ
GENERIC SUBDOMAINS
- そのモデルのいくつかの部分は、専門の知識を獲得したり会話したりすることなしに、複雑性を追加する
- 外部のいかなるものは、そのCORE DOMAINを見つけたり理解したりし辛くする
- 全員が知っている一般的な原則や、あなたの主要な焦点ではないが、支援する役割を果たす得意分野に属する詳細を伴って、そのモデルは行き詰まる
今のところは、しかしながら一般的に、これらの他の要素は、そのシステムの機能やそのモデルの最大限の表現にとって必要不可欠である
あなたのプロジェクトに対して意欲を起こさせないような、凝集したサブドメインを特定せよ
これらのサブドメインの一般的なモデルを取り除き、個別のMODULEにそれらを配置せよ
それらにあなたの得意分野の痕跡を残すな
一度それらが分離されたら、そのCORE DOMAINよりもそれらの継続的な開発には低い優先度を与え、あなたの主要なDeveloperにそのタスクを割り当てるのは避けよ(なぜなら、彼らはそれらからドメイン知識を少しも得ることがないだろうから)
また、既製の解決策や、これらのGENERIC SUBDOMAINSに対する公開されたモデルを検討せよ
そのドメインモデリングのリスクを過小評価するのは容易い。それは次のような形を取ることがあり得る
- 見えていない複雑性
- ビジネスエキスパートに対する不十分な疎通
- そのDeveloperの鍵となるスキルのギャップ
DOMAIN VISION STATEMENT
- プロジェクトの初期において、そのモデルは通常存在さえしていないが、それにもかかわらず、その開発に焦点を当てるその必要性は既にそこにある
- 開発の後の方の段階で、そのモデルの徹底した学習を要求しないシステムの、その価値の説明の必要がある
また、そのドメインモデルの重要な側面は複数のBOUNDED CONTEXTに跨がるかもしれないが、本質的にこれらの異なったモデルは、それらの共通の焦点を示すために構成され得ない
そのCORE DOMAINとそれがもたらすであろう価値(その価値ある提案)の、(約1ページの)短い説明を書け
このドメインモデルを他と区別しないそれらの側面は無視せよ
そのドメインモデルが多様な関心にどのように仕え、バランスを取るのかを示せ
それを限定されたものに保て
この記述を早期に書き、新しい洞察を得るにつれてそれを修正せよ
(DOMAIN VISION STATEMENTの例がいくつか登場するが、"そうでない"例では技術的な内容が包含されている)
HIGHLIGHTED CORE
- チームメンバーは何がそのCORE DOMAINを構成するのかを広く知っているかもしれないにも関わらず、異なる人々は寸分違わない要素を選び出さず、全く同じ人でさえもある日とその次の日では辻褄が合っていないだろう
- 鍵となる部分を特定するために、そのモデルを定期的にフィルターにかけることの精神的負担は、設計の思考に費やされる集中をより緩和し、そしれそれはそのモデルの包括的な知識を要求する
- そのCORE DOMAINは見やすいように作られなければならない
- そのコードに対する重要な構造上の変化は、そのCORE DOMAINを特定する理想的な方法であるが、短い期間においては常に実用的であるわけではない
事実、そのような主要なコードの変化は、そのチームが大きく欠いている見解なしでは引き受けるのが難しい
そのCORE DOMAINとCORE要素の間の主要な相互作用を描写する、(3から7のまばらなページの)とても簡潔なドキュメントを書け
役割を明らかにするための特別な取り組みなしに、そのモデルの主要なリポジトリの内にそのCORE DOMAINのそれぞれの要素の旗を立てよ
DeveloperがそのCOREに何があって、何がないのかを知ることに努力を要しないようにせよ
もしその精製ドキュメントがそのCORE DOMAINの本質の要点を述べるならば、結果それはモデル変更の重要性の実践的な指針として仕える
モデルあるいはコードの変更がその精製ドキュメントに影響を与える時、それは他のチームメンバーとの相談を要求する
その変更がなされた時、それは全てのチームメンバーへの早急な通知と、そのドキュメントの新しいバージョンの流布を要求する
そのCOREの外側、あるいはその精製ドキュメントに含まれない詳細に対する変更は、相談や通知なしで統合され得るし、仕事の過程で他のメンバーによって引き起こされるだろう
そして、DeveloperはXPが提案するその完全な自律を持つのだ
COHESIVE MECHANISMS
- 計算は時々、その設計を膨張させ始めるような複雑性のレベルに到達する
- 概念的な"何"は、機械的な"how"によって無力化される
その問題を解決するためのアルゴリズムを提供するメソッドの大勢は、その問題を表現するそのメソッドを分かりにくくする
概念的なCOHESIVE MECHANISMを分離した軽量のフレームワークに分割せよ
形式主義、あるいはよくドキュメント化されたアルゴリズムのカテゴリをとりわけ監視せよ
INTENTION-REVEALING INTERFACEを伴うそのフレームワークの能力を顕在化させよ
今やそのドメインの他の要素は、解決策("どうやって")の複雑さをそのフレームワークに委譲しながら、その問題("何")を表現することに焦点を当てることができる
SEGREGATED CORE
- そのモデルの要素は、部分的にはそのCORE DOMAINに仕えて、部分的には支援する役割を演じるかもしれない
- CORE要素は、包括的な要素にきつく結合されているかもしれない
- そのCOREの概念的な結合は、強いあるいは目に見えるものではないかもしれない
- 全てのこの混乱ともつれは、そのCOREを窒息させる
Designerは、弱い設計に至りながら、最も重要な関係性を明確に見ることができない
そのモデルをリファクタリングして、そのCORE概念から支援するもの(はっきりしないものを含む)を分離し、他のコードに対するその結合を減らす一方で、そのCOREの結合を強化せよ
全ての包括的あるいは支援的な要素を他のオブジェクトに分解し、それらを他のパッケージに配置せよ、もしこれが高度に結合された要素を分離する方法でそのモデルをリファクタリングすることを意味するとしても
SEGREGATED COREにリファクタリングするのに必要なステップは、典型的には以下のようなものである
COREサブドメインを特定せよ(場合により、その精製ドキュメントから引っ張る)
関連するその概念に対して命名された、関連したクラスを新しいMODULEに移動させよ
その概念の直接的表現ではないデータや機能を断ち切るために、コードをリファクタリングせよ。削除された側面を、他のパッケージの(可能であれば新しい)クラスに入れよ。それらを概念的に関連したタスクと共に配置することを試みよ、しかし完璧であることに多くの時間を浪費するな。
そのCOREサブドメインを磨き、それからの参照を明示的で自明な他のパッケージに作成することに焦点を当て続けよ
関係性と相互作用をよりシンプルに、より多くを語り、他のMODULEとのその関係性を最小限かつ明らかにするため、その新しいSEGREGATED CORE MODULEをリファクタリングせよ
そのSEGREGATED COREが完成するまで、もう一つのCOREサブドメインと共に繰り返せ
DeveloperとDomain Expertは、その知識の噛み砕きプロセスの一部として、戦略的な精製において協力する
ABSTRACT CORE
分離したMODULE内のサブドメインの間で多くの相互作用が存在する時、多くの参照のどちらか一方は、その仕切りの価値の多くを無にするような、MODULEの間に作成されなければならず、あるいはその相互作用はそのモデルを不明瞭にするような、間接的なものにされなければならないだろう
そのモデルの中で最も基本的な概念を特定し、それらを独立したクラス、抽象クラス、あるいはインタフェースに分解せよ
この抽象モデルを設計せよ、その結果、それは重要な構成要素の間の大抵の相互作用を表現する
特化した、詳細な実装クラスが、サムドメインで定義されたそれらが所有するMODULEの中に取り残されている一方で、この端から端まで抽象的なモデルをそれ自身のMODULEの中に配置せよ
深いモデルに対するブレイクスルーが、発生するどの場所においても価値を提供するにも関わらず、それがプロジェクト全体の起動を変えることができるのは、CORE DOMAINの中である
Chap.16 Large-Scale Structure
- 要素が全体の設計にまたがるパターンにおけるそれらの役割によって解釈されることを許すような、いかなる全体的な減速もない巨大なシステムにおいては、Developerは木を見て森を見ることができない
- その部分の責務の詳細な知識を持たないとしても、その全体のシステムにまたがり、かつその全体においてそれぞれの部分の幾らかの理解を許すような、ルールあるいは役割や関係性のパターンを考案せよ
EVOLVING ORDER
- 自由参加の設計は、誰も全体としての意味を解明できないシステムを作り出し、それらは維持するのがとても困難である
- しかし、設計者はプロジェクトを前もっての設計想定で束縛することができ、そのアプリケーションの特定の部分のDeveloper/Designerから非常に多くの力を奪う
すぐさま、Developerはそのアプリケーションをその構成に合うように書き直すか、あるいは、ギクシャクした開発のその問題に後戻りしながら、それを転覆して、全く構成を持たないようにするだろう
場合によってはここに至るまでの構成の完全に異なった型に変更しながら、この概念的な大きなスケールの構成をそのアプリケーションと一緒に進化させよ
詳細な知識と共に作られなければならない、設計とモデルの詳細な決定を過剰に抑制してはならない
CONTEXT MAPと違い、Large-Scale Structureは必須ではない
Large-Scale Structureは、構成がモデル開発において不自然な制限を課すことなくそのシステムをとてもうまく明確にするとわかった時だけ、適用されるべきである
サイズの合わない構成はないよりも尚悪いため、包括性に向かって頑張らずに、しかし現れてきた問題を解決する最小限のセットを見つけ出す方が最善である
少ないはより効果を挙げる
SYSTEM METAPHOR
- ソフトウェア設計はとても抽象的で、理解するのが困難な傾向にある
Developerとユーザーは同様に、そのシステムを理解し、全体としてそのシステムの姿を共有するための具体的な方法を要求する
チームメンバーの創造性を引き付け、有益な方向に思考を導くように見える具体的な類推がそのシステムに現れた時、それをLarge-Scale Structureとして採用せよ
この隠喩の周りのその設計を組織し、それをユビキタス言語の一部とせよ
SYSTEM METAPHORは、そのシステムについての会話と、それの開発のガイドをの両方を促すべきである
潜在的には異なったBOUNDED CONTEXTに渡ったとしても、これはそのシステムの異なる部分における一貫性を増大させる
しかし、全ての隠喩は不正確であるため、過剰な拡大や不適切に対して、その隠喩を継続的に再調査し、もしそれがその道に差し入ったら、それを捨てる心構えをせよ
RESPONSIBILITY LAYERS
- それぞれの固有のオブジェクトが手作りの責務を持つ時、ガイドライン、統一性、そのドメインの大きな帯状を一緒に扱う能力などない
大きなモデルに統一性を与えるために、それらの責務の割り当て上に幾らかの構成を課すことは便利である
あなたのモデルのその概念的な依存性、その変動率、あなたのドメインの異なる部分の変更の源に着目せよ
もしあなたがそのドメインいおいて自然な階層を特定するならば、それらに広く抽象的な責務を割り当てよ
これらの責務は、その高レベルの目的のストーリーや、あなたのシステムの設計を伝えるべきである
そのモデルをリファクタリングせよ、その結果、それぞれのドメインオブジェクト、AGGREGATEそしてMODULEのその責務は、一つのレイヤーの責務の中にきちんとはまる
KNOWLEDGE LEVEL
KNOWLEDGE LEVELとはオブジェクトのグループであり、オブジェクトのもう一つのグループがどう振る舞うべきかを描写するものである
Entity間のその役割と関係性が異なる状況で違うような中でのアプリケーションにおいて、複雑性は爆発し得る
完全に汎用なモデルもとてもカスタマイズされたモデルのどちらも、そのユーザーのニーズに仕えることはない
オブジェクトは結局、複数ケースの多様性を満たすために他の型に対する参照したり、あるいは異なる状況で異なる方法で使われる属性となる
同じデータと振る舞いを持つクラスは、異なる集合規則を提供するためだけに、増えていくかもしれない
その基礎となるモデルの構造と振る舞いを描写かつ制限するために使われ得る、オブジェクトの独立したセットを作成せよ
これらの関心を2つの"レベル”として分離した状態に保て、一つはとても具体的で、もう一つはユーザーあるいはスーパーユーザーが調整できるルールと知識を反映している
PLUGGABLE COMPONENT FRAMEWORK
- アプリケーションの多様性が相互作用しなければいけない時、独立して設計されたが、全く同じ抽象と複数のBOUNDED CONTEXTの間の翻訳に基づく全ては、統合を制限する
- SHARED KERNELは密接に連携して働かないチームにとっては実行可能ではない
重複と分裂化は開発とインストールのコストを増大させ、相互運用性はとても困難なものになる
インタフェースや相互作用のABSTRACT COREを精製し、インタフェースが自由に置換される多様な実装ができるフレームワークを生み出せ
同じように、どんなアプリケーションでもそれらのコンポーネントを使えるようにさせ、それはそれがそのABSTRACT COREのインタフェースを通じて厳密に動作する限りにおいてである
PLUGGABLE COMPONENTは、適用するのが非常に難しいパターンであり、アプリケーションが限定された選択肢を持つことがマイナス点である
(インタフェースを厳密に決めて、そのフレームワークを業界全体で守ることで大きな利益を上げた工場ロボット用ソフトウェアの例が示されている)
Chap.17 Bringing the Strategy Together
- あなたがプロジェクト上の戦略的設計に取り組む時、その現在の状況の明白な評価から始めなければならない
- CONTEXT MAPを描け。一貫したものが書けるだろうか?それとも曖昧な状況があるだろうか?
- そのプロジェクト上で言葉の使用に関心を向けよ。ユビキタス言語はあるか?それは開発を手助けできるほど十分リッチなものか?
- 何が重要であるかを理解せよ。そのCORE DOMAINは特定されているか?DOMAIN VISION STATEMENTは存在するか?それを書けるか?
- そのプロジェクトのテクノロジーは、MODEL DRIVEN DESIGNの役に立つか?不利に働くか?
- そのチームのDeveloperは必須の技術スキルを持っているか?
- そのDeveloperはそのドメインについて博識であるか?彼らはそのドメインに"興味がある"か?
Six Essentials for Strategic Design Decision Making
- 意思決定は、その全体のチームに到達しなければならない
- その意思決定プロセスはフィードバックを吸収しなければならない
- その計画は進化を考慮しなければならない
- アーキテクチャーチームは、全てのその最善と輝かしさを吸い上げてはならない
- Strategic Designは、ミニマリズムと謙虚さを要求する
- 真実は、ほとんど全てのものが何かの邪魔になるので、それぞれの要素は価値がないと困る
- オブジェクトはスペシャリストであり、Developerはジェネラリストである
超初心者向けのフレームワークは書くな
マスタープラン(基本計画)は失敗する
本書に例として登場するどの開発プロジェクトも、本書の全てのテクニックを動員してはいない
- 例えそうであっても、ドメイン駆動設計に取り組んだいかなるプロジェクトも、限られた種類の方法で認識できるだろう
- その決定的な特徴は、その対象ドメインを理解することとその理解をそのソフトウェアに取り込むことに重点を置いていることである
思考の産物でなければならないものを自動化しようとする努力は考えが甘く、逆効果である
GLOSSARY
AGGREGATE
- データの変更の目的のためのまとまりとして扱われる、紐づけられたオブジェクトのクラスタ
- 外部からの参照は、rootとして指定されるAGGREGATEのメンバーに制限される
- 一貫性のルールのセットは、そのAGGREGATEの境界内で適用される
analysis pattern
- ビジネスモデリングにおける共通の構造を示す概念のグループ
- ただ一つのドメインに関係があるかもしれず、あるいは複数のドメインに跨るかもしれない
ASSERTION
- ある時点でのプログラムのその正しい状態の陳述で、プログラムがどのようにそれを行うかとは無関係である
- 典型的には、ASSERTIONは操作あるいは設計要素の不変性のその結果を指定する
BOUNDED CONTEXT
- 特定のモデルの線で分けた適用可能性
- BOUNDED CONTEXTSはチームメンバーに、何が一貫していなければならず、何が独立して開発できるのかについての、明白で共有された理解を提供する
client
- 設計の元で、その能力を使ってその要素を呼ぶプログラムの要素
cohesion
- 論理的な合意と依存
command
- (A.K.A modifier)
- (例えば変数を設定することといった)そのシステムに幾らかの変化をもたらす操作
- 内部的に副作用を生む操作
CONCEPTUAL CONTOUR
- もしドメインの中に反映されたら、その設計が変更をより自然に提供することを手助けするような、そのドメイン自体の基礎となる一貫性
context
- 言葉や記述がその意味を決定すると分かるようなその設定
- See BOUNDED CONTEXT
CONTEXT MAP
- プロジェクトに関与しているそのBOUNDED CONTEXTSと、それらとそれらのモデルの間のその実際の関係性の表明
CORE DOMAIN
- ユーザーのゴールの中核を成す、そのアプリケーションを差別化し、それを価値のあるものにする、そのモデルの代表となる部分
declarative design
- プロパティの詳細な記述が実際にそのソフトウェアを制御するプログラミングの形式。
- 実行可能な仕様
deep model
- そのDomain Expertsのその主要な関心と、彼らに最も関連した知識の、鋭い表現
- 深いモデルは、そのドメインの表面的な側面と考えの甘い解釈を捨て去る
design pattern
- 特定の文脈において一般的な設計の問題を解決するようカスタマイズされたオブジェクトとクラスを伝える説明
distillation
- より価値があって使いやすいようにする形式の中でその本質を抽出するため、混合物からその要素を分離するプロセス
- ソフトウェア設計においては、モデルの鍵となる側面のその抽象化、あるいはそのCORE DOMAINを前面に押し出すためのより巨大なシステムのその分割
domain
- 知識、影響あるいは活動の領域
domain expert
- ソフトウェア開発というよりそのアプリケーションのそのドメインを畑にもつソフトウェアプロジェクトのメンバー
- 単にそのソフトウェアの全てのユーザーではなく、そのDomain Expertはそのテーマの深い知識をもつ
domain layer
- LAYERED ARCHITECTURE内のドメインロジックに責務を持つその設計と実装の一部分
- そのドメインレイヤーは、そのドメインモデルのソフトウェア的表現が息づく場所
ENTITY
- その属性によってではなく、継続性と独自性の筋によって基本的に定義されるオブジェクト
FACTORY
- 複雑な生成ロジックのカプセル化とclientのために作成されたオブジェクトのその型を抽象化するための機構
function
- 観察できる副作用なしに、結果を計算し返す操作
immutable
- 作成後、観測可能な状態が決して変更されないプロパティ
implicit concept
- モデルや設計のその意味を理解するために必須だが、決して言及されない概念
INTENTION-REVEALING INTERFACE
- クラス、メソッドそして他の要素のその名前が、それらを作った最初のDeveloperの目的とそれらの価値の両方をclient Developerに伝える設計
invariant(上記では不変性と訳している)
- メソッドの実行の途中あるいはコミットされていないデータベーストランザクションの途中のような、特定の遷移的な状況の間を除き、どんな時でも真実でなければならない幾らかの設計要素についてのASSERTION
iteration
- プログラムが細かなステップの中で繰り返し改善されるプロセス
- また、それらのステップの一つ
large-scale structure
- 全体のシステムに対して設計のパターンを確立する、高レベルの概念、ルールあるいはその両方のセット
- そのシステムが大まかなアウトラインにおいて議論され理解され得る言語
LAYERED ARCHITECTURE
- 他のものに囲まれたドメインレイヤーを隔離しながら、ソフトウェアシステムの関心を分離するテクニック
life cycle
- 典型的には、ある状態から次の状態に変化する時、その完全性を保証するための制限を伴って、作成と削除の間でオブジェクトがとり得る状態の連続
- システムと異なるBOUNDED CONTEXTの間で、Entityの移行を含むかもしれない
model
- ドメインの選択された側面を記述し、そのドメインに関連した問題を解決するために使用され得る、抽象化のシステム
MODEL-DRIVEN DESIGN
- ソフトウェアの要素の幾らかのサブセットがモデルの要素に密接に一致する設計
- また、お互いに整列した状態を保つような、モデルと実装の共同開発のプロセス
modeling paradigm
- (例えば、オブジェクト指向プログラミングとロジックプログラミングのような)概念のソフトウェア類似物をつくるためのツールと相まった、ドメイン内の概念を苦労して作り上げる特定のスタイル
REPOSITORY
- オブジェクトの集合を模倣するような保管、修正、検索の振る舞いをカプセル化するための機構
responsibility
- タスクを実行するための、あるいは情報を知るための責務
SERVICE
- カプセル化された状態なしで、そのモデル内に独立している、インタフェースとして提供される操作
side effect
- 内部的かそうでないかに関わらず、たとえ意図的な更新であろうとも、操作に起因する状態の観測可能な全ての変更
SIDE-EFFECT-FREE FUNCTION
- See function
STANDALONE CLASS
- システムの原始的で基本的なライブラリを除いてその他を参照しない、理解されテストされ得るようなクラス
stateless
- その要素の歴史に関係なく、その操作のいずれかをclientが使うことができるような、設計要素のプロパティ
- ステートレスな要素は、グローバルにアクセス可能な情報を使用するかもしれず、また、(副作用である、あるいは副作用を持つかもしれない)グローバルな情報を変更さえするかもしれないが、その振る舞いに影響を与える私的な状態は持たない
strategic design
- そのシステムの多くの部分に適用される、モデリングと設計の意思決定
- そのような決定はその全体のプロジェクに影響を与え、チームレベルで決定されなければならない
supple design
- 期待された結果を確実にもたらすような、明白で柔軟な表現を生むため、深いモデルに本来備わっているパワーをclient Developerの手にもたらすような設計
- 等しく重要だが、新しい洞察を提供するために、その設計自体を実装者が形作りと再形成しやすいようにする、"同じ"深いモデルをそれは利用する
UBIQUITOUS LANGUAGE
- そのドメインモデルの周辺で構成され、そのソフトウェアと共にあるチームの全ての活動を関連づけるため、全てのチームメンバーによって使用される言語
unification
- それぞれのチームが曖昧でなく、どのルールも矛盾していないようなモデルの、内部的な一貫性
VALUE OBJECT
- 幾らかの特徴的あるいは属性を描写するが、独自性の概念を持たないオブジェクト
WHOLE VALUE
- 単一の、完全な概念をモデル化するオブジェクト
- 投稿日:2020-02-05T10:10:49+09:00
Evans本を読んでDDDの要点を翻訳してみた
『Domain-Driven Design Tackling Complexity in the Heart of Software - Eric Evans』
(注意: このような括弧書きの説明は私の意見を意味し、翻訳ではありません)
Index
- FOREWORD
- PREFACE
- I. Putting the Domain Model to Work
- II. The Building Blocks of a Model-Driven Design
- III. Refactoring Toward Deeper Insight
- IV. Strategic Design
- GLOSSARY
- AGGREGATE
- analysis pattern
- ASSERTION
- BOUNDED CONTEXT
- client
- cohesion
- command
- CONCEPTUAL CONTOUR
- context
- CONTEXT MAP
- CORE DOMAIN
- declarative design
- deep model
- design pattern
- distillation
- domain
- domain layer
- ENTITY
- FACTORY
- function
- immutable
- implicit concept
- INTENTION-REVEALING INTERFACE
- invariant(上記では不変性と訳している)
- iteration
- large-scale structure
- LAYERED ARCHITECTURE
- life cycle
- model
- MODEL-DRIVEN DESIGN
- modeling paradigm
- REPOSITORY
- responsibility
- SERVICE
- side effect
- SIDE-EFFECT-FREE FUNCTION
- STANDALONE CLASS
- stateless
- strategic design
- supple design
- UBIQUITOUS LANGUAGE
- unification
- VALUE OBJECT
- WHOLE VALUE
FOREWORD
- 概念と実装を一緒にする主要な理由は、ドメインモデルがDomain ExpertsとDeveloperを結ぶユビキタス言語を提供するため
- どんな優れたドメインモデラーでさえも、最初のリリースの後にベストなアイデアを得る
PREFACE
- 成功に共通する特徴は、設計の繰り返しを通じて進化したリッチなドメインモデルだった
- XPはAgileプロセスで有名であり、この本では設計とプロセスの議論の基礎としてXPを使う ### XP
- リファクタリングによってデザインを改善できることを、XPのプロセスでは仮定する
- 変更と不確実性に対処する能力を重視する
- 初回1回限りの設計はしない
- 方針を素早く変えるために、コミュニケーションとプロジェクトの能力改善に注力する
- "simplest thing that could work" をプロジェクトのどの段階でも用いて以下を行うことで、究極的には真に求める設計に到達する
- 継続的なリファクタリング
- 多くの小さな設計の改善 ### DDDの前提
- デプロイはiterativeであること
- (プロジェクトの最後まで)DeveloperとDomain Expertが近い関係にあること
I. Putting the Domain Model to Work
- ドメインモデルとは、綿密に組織された業務知識の選択的な抽象である
- モデルと実装は密に結びついている
- モデルは全てのチームメンバーが使う言語の支柱である
- モデルは、業務知識を濃縮したもの
Chap.1 Crunching Knowledge
- 最初にやることは、Domainの仕様を聞いて、ドメインモデルのシナリオのラフなスケッチを書くこと
- Domainの仕様の認識間違いを指摘して直してもらいながら、その都度スケッチを書き直す(一回では書けないので、何度も書くこと)
- 本文では 6回 書き直している
- スケッチを書く時は、自分が理解していないことを恐れず聞いて、理解を深めること
- 本文では「これがまだ分からないのだが」と、何度もDomain Expertに繰り返し質問している
- Domain ExpertからもDeveloperが作ったラフなスケッチに対して質問をしている「これは何を示すのか」
- スケッチが固まったら、クラス図(クラス名、属性、操作を最小単位として、集約や継承などの関係性を描いたもの)を書く
- 次に、Domainだけコードを書く、Infrastructureは書かない
- コードを書いたらDomain Expertに確認してもらい、具体的な認識の間違いを正していく
- Domain Expertと密に会話し、モデルに不要な仕様(実装)を削ぎ落とす
- 仕様を全て正確に満たすことができなければ、再度0から新たにモデルを作り、実装する
- 継続的にDomainの知識を得ることが大切
- 人の断片的な知識や、断片的なドキュメントなど、基本的にDomainの知識はまとまっていないため
Ingredients of Effective Modeling
- モデルと実装を結びつけよ
- モデルに基づく言語を育てよ
- モデルは複雑な問題を解くために知識リッチであれ
- モデルを精製せよ(要らないものは削ぎ落とせ、さもないと間違って重要なものが削ぎ落とされてしまう)
- ブレインストーミングと実験を繰り返せ
- モデルの表現を口に出して言えば、それが正しいかどうかすぐテストできる
- 船と貨物を運ぶシステムを開発する。(実際のビジネスでそうなのだが)直前のキャンセルに備えて、実際の積載量の110%を積むことができる
- 明示的かつ分別されていないため、メソッドの最初に110%を超えるかどうかのチェックを追加実装してはいけない
- Domain Expertsが読んでも分からないし、Developerだって見落とす可能性がある
- Design PatternのStrategyパターンを使って、110%PolicyインスタンスをAggregateして、そのメソッドを呼び出す
- 他のクラスに区別して分けることで、これが重要なビジネスルールであることがDomain ExpertやDeveloperに認識されやすくなる
- Domain Expertがコードを読んで、ビジネスルールが正しく実装されているかどうかフィードバックを返すことができるようになる
Chap.2 Communication and the Use of Language
- ユビキタス言語を使って会話せよ
- 翻訳と誤認識のコストをなくせる
- モデルで明示されるビジネスルールを議論するための用語を含む
- ユビキタス言語の一貫した利用は、モデルの弱点を明らかにする
- ユビキタス言語の変更は、ドメインモデル内の変更として認識される(つまりコードのリファクタリングが必要)
- Domain Expertもビジネス用語を使うのではなく、ユビキタス言語を使うべし
- ドメインモデルをユビキタス言語の支柱として使え
- 全てのコミュニケーションとコードにおいて、ユビキタス言語を使うよう徹底しなければいけない
- Domain Expertは、ドメインの理解を伝えるには不適切または不十分な用語には、常に反対を表明すべき
- Developerは、設計をつまずかせる不明瞭さ、一貫性のなさを観察すべき
Example
- ユーザーと開発者は同じ言語を話しているか
その言語はアプリケーションが行わなければいけない何かの議論を進めるのに充分なほどリッチか
Domain Expertが話す言語を、ドメインモデル(EntityやValueObject, Application Service)の振る舞いで説明する
Modeling Out Loud
- 曖昧だったり技術的な話がユビキタス言語に出てきてはならない
- くどい説明になっている部分も改善の余地がある
ビジネスのユースケースを、ドメインモデルのの要素と振る舞いを用いて、声に出して説明する
- 実現したいことを声に出して、より簡潔な方法を探し、図で表してコードに落とし込む
- もし洗練されたDomain Expertがそのモデルを理解しないならば、そのモデルに何か誤りがある
- 開発者とDomain Expertはモデルを使って目的のシナリオを議論することで、非公式にモデルをテストすべき
- ドメインモデルはDomain Expertの特有の専門用語に由来するが、それは精製され、より明確に、より狭義のものとなる
One Team, One Language
- ユビキタス言語に なり得ない もの
- Domain Expert
- ビジネス用語(ただし、Developerは理解しない)
- 普段使うビジネス用語(ただし、ソフトウェア設計には登場しない)
- Developer
- 設計の技術的側面
- 技術用語
- 技術設計パターン
ユビキタス言語
- ドメインモデル用語
- Bounded Contextsの名前 (Chap.14)
- 大きな規模の構造の用語 (Chap.16)
- パターン名
Developerの間での会話、Domain Expertの間での会話、コードの表現そのもの、これら全てが(共有されたドメインモデル由来の)同じユビキタス言語に基づく
基本的にドキュメントは書かなくていい
- 動いているコードに不明確な部分はない
ドキュメントはコードが既にうまくやっていることについては記載しなくて良い
- コードが正確で詳細なプログラムの振る舞いの仕様であり、既に詳細を提供している
コードに付与されたコメントもコードの動き自体には影響を及ぼさないので、いずれ同期されなくなる
外部ドキュメントも同様、いずれ同期されなくなる
ドキュメントは、コードと会話を補足するような、もっと大きな構造に対して記載する
ドキュメントは、ユビキタス言語で書かれており、コードに組み込まれた言語で書かれていること
(まとめ)ドキュメントは最小限に、コードと会話を補足するものと弁えよ
良いDeveloperは、変数名、メソッド名、コードの体系が、正しく"主張"しているコードを書く
- 振る舞いは正しいが、メソッド名が不明瞭、誤解を招く、あるいは更新し忘れて古くなっているコード
- テストのAssertは正確だが、変数名やコード全体から伝わるストーリーはそうではない
UMLだけでなく、フローチャートなどを書くと、別の視点から全体像を把握できるので良い
Chap.3 Binding Model and Implementation
一回限りのドメインモデルは無関係な事柄に対して深みにハマり、一方で重要な事柄を見落とす
設計とその中心部分がドメインモデルに紐づいていなければ、そのモデルに価値はなく、ソフトウェアの正確さは疑わしい
- 同時に、モデルと設計機能の複雑な紐付きは理解しにくく、実質的には、設計の変更に対する保守は不可能
- 分析と設計には絶望的な分断が発生し、それらの活動で得られた洞察は他方に還元されることはない
オブジェクト指向プログラミングは有効であり、それはモデリングの概念に基づき、モデルの構想の実装を提供するためである
C言語のような純粋な手続き型の言語では、ドメインモデルの実装は困難である(関数の集まり以上の意味を持たせられない)
オブジェクト指向プログラミングは、ドメイン駆動設計に用いられる主要なアプローチ
モデリングと実装の二つのタスクを分けることで問題が生じる
- Developerがドメインモデルに対して責任を感じず、そのアプリケーションに対してモデルがどのように機能するかを理解しないのなら、そのモデルはそのソフトウェアと無関係になってしまう
- Developerがコードの変更がモデルの変更であることを認識していなければ、リファクタリングはモデルを強めるどころか弱めてしまう
- モデラー(ドメインモデルを作る人)が実装プロセスから隔絶されている時、彼や彼女は実装の制約を決して掌握せず、あるいは即座に失う
コードの変更は、モデルを変更する
コードを変更する責任を持つものは、そのコードを通じてモデルを表現することを学ばなければならない
全てのDeveloperはモデルに関する幾らかのレベルの議論に参画しなければならず、Domain Expertとも話をしなければいけない
モデリングと実装の明確な分離は機能しないが、大きなプロジェクトでは未だに技術リーダーが必要である
- 高レベルの設計をまとめたり、最も難しい、クリティカルな決定の解決を助ける人
ユビキタス言語は、Developer、Domain Expert、そのソフトウェアの間に流れる全ての情報のチャネルである
II. The Building Blocks of a Model-Driven Design
- 設計は "responsibility-driven design", "design by contract" そして "object-oriented design" に大きく従うが、 Developerはよく知られた基礎がどのようにモデル駆動設計を支えているのかを理解する必要がある
Chap.4 Isolating the Domain
- かなり複雑なタスク扱うプログラムの作成は、関心の分離を求める
Layered Architecture
- User Interface -> Application -> Domain -> Infrastructure
各レイヤーは緩く結合しており、その依存性は上位から下位の一方向のみ
- 上位のレイヤーは下位のレイヤーの要素を利用できるが、その逆は不可
- あるレイヤーの要素は同じレイヤーの他の要素か、その配下のレイヤーの要素にのみ依存する
User Interface (Presentation) Layer
- ユーザーに情報を表示したり、ユーザーのコマンドの解釈に責任を持つ。ユーザーではなく他のシステムのインタフェースとなることもある
Application Layer
- このレイヤーが責任を持つタスクはビジネスにとって意味のあるものであるか、他のシステムの同じレイヤーでの相互作用に必要
- このレイヤーは薄くなければならない
- ビジネスロジックや知識を持たない
- タスクを調整したり、次の下のレイヤーでのドメインオブジェクトの共同作業に仕事を委譲するだけ
- ビジネスシチュエーションを反映した状態は持たない
- ユーザーやプログラムに対するタスクの進行状況を反映する状態を持つ
Domain (Model) Layer
- ビジネスシチュエーションに関する情報、ビジネスルールといった、ビジネスの概念の表現に責任を持つ
- ビジネスシチュエーションを反映する状態はこのレイヤーで制御され使用されるが、それを保存する技術的詳細はInfrastructureに以上される
- ビジネスソフトウェアの心臓とも言えるレイヤー
Infrastructure Layer
- より高位のレイヤーをサポートする一般的な技術的能力を提供する
Layered Architectureによって
- それぞれの層は上位の層にのみ依存するため、保守しやすく
- ドメインモデルはDomain Layerのみに記述され、
- Domain Layerは他のレイヤーのロジックを含むことがないため、ドメインルールだけに集中して実装できる
Layered Architecutureを採用することで、分散システムにおいてそれぞれの層を別々のサーバーにデプロイすることもできる
ドメインの実装の分離が、ドメイン駆動設計には必須である
SMART UIアーキテクチャーは、ドメイン駆動設計のアンチパターンであるので共存できない
Chap.5 A Model Expressed in Software
EntityとValue Objectとの違いは、
- オブジェクトが継続性と一意性をもった、異なる状態を通じて追跡される何かを示すかどうか(Entity)
- 何かの状態を表現する属性なのか(Value Object)
アクションやオペレーションと明確に表現されるそのドメインの様子は、Serviceとして表現される
可能な限りオブジェクトの関係を制限することは重要
- ある方向の紐付きが、他方よりもずっと意味があって重要である
Many-to-Many の横断的な方向の紐付きを制限することは、その実装をずっと簡単な設計である One-to-Many に効果的に減らす
関係を一貫して制限することは、双方向の関係がそのドメインの意味のある特徴であるとき、既存の双方向の紐付きに重要さを与える
Entity
- 幾らかのオブジェクトは主にそれらの属性によって定義されない。それらは、時とはっきりした表現をしばしば跨って動くアイデンティティのスレッドを表す
- 時々そのようなオブジェクトは、属性が異なっていても、もう一つのオブジェクトと合致しなければいけない
- オブジェクトは、同じ属性を持っていたとしても、他のオブジェクトと区別されなければいけない
アイデンティティの誤認識は、データの不整合を招く
そのアイデンティティによって主に定義されるオブジェクトは、Entityと呼ばれる
- そのクラス定義、責任、属性、そして紐付きは、それらが何であるかの周辺を解決すべきであって、それが持つ特定の属性ではない
Entity は、ライフサイクルを通じた継続性、ユーザーにとって重要な属性から独立した特徴を持つ
同じものであることを何が意味するのか、ドメインモデルは定義しなければいけない
アイデンティティはこの世界の物に対して本来備わっているものではない。それは後付けされた意味であり、なぜなら便利だからである
- 実際に、この世界の同じ物が、ドメインモデルにおいてEntityとして表されたりそうでなかったりする
Entityの最も基本的な責務は継続性を確立することであり、その結果、振る舞いが明確かつ予測しやすいものになる
- 属性や振る舞いにさえ集中せず、それを特定し、それを探したり合致したりするために広く使われる、最も特徴的な本来備わったものまで、そのEntityオブジェクトの定義を削ぎ落としましょう
- 概念に本質的な振る舞いや、その振る舞いで要求される属性だけを追加しましょう
- 加えて、振る舞いと属性をコアEntityに紐づく他のオブジェクトに移動させられないか、目を向けてみましょう
オブジェクト指向言語は、二つのオブジェクトが同じメモリの場所を参照しているかどうかで評価する "Identity" を持っているが、これはドメイン駆動設計におけるEntityのIdentifierとしては不適切である
真のユニークKeyがオブジェクトになければ、それぞれのインスタンスにnumberまたはstringの一意なシンボルを付与することが代替策となる
- このシンボルはイミュータブルで、作成されたら変更されてはならない
Value Object
- そのドメインの説明的な側面を概念的なアイデンティティなしで表すオブジェクトが、Value Objectである
- Value Objectは、それらが何であるか(誰またはどの、ではない)にのみ注意を払う設計の要素を表すためにインスタンス化され、その後は状態が変化することはない
- Value Objectはイミュータブルとして扱う
ドメインによっては、他のドメインでEntityであるものがValue Objectになり得る(その逆も然り)
番地、町、郵便番号は、Personオブジェクトの個別の属性ではない
- それらはPersonオブジェクトをシンプルにするような、住所全体の一部であり、よりまとまったValue Objectである
- 住所Value Objectに番地、町、郵便番号が含まれ、Personオブジェクトは住所Value Objectをもつ
Value Objectは、どのインスタンスであるかは気にかけない(同じ状態を持つ別のインスタンスであっても良い)
値が頻繁に変わったり、作成や削除のコストがとても大きい場合に限り、変更可能なValue Objectを定義することもあるが、特例に過ぎない
- もしミュータブルなValue Objectを定義した場合、それは共有されてはならない
Value Object間の双方向の紐付きを完全に排除することに試みよ
Service
- EntityにもValue Objectにも回帰しない、重要なドメインのオペレーションが存在し、これらはモノではなく本来は活動やアクションである
- Serviceはモデルオブジェクトとして、何らかのオペレーションを実行するだけのオブジェクトとして現れる
Serviceはそれ自体に状態を持たず、そのドメインにおいてそのServiceが持つオペレーション以外の意味を持たない
あるドメインの幾らかの概念は、オブジェクトとしてのモデルに対して自然ではない
要求されるドメインの機能をEntityやValue Objectの責務に強制することは、モデルベースオブジェクトの定義を歪めてしまうか、意味のない人工的なオブジェクトを追加してしまう
Serviceはそのモデル内に独立している、インタフェースとして提供されるオペレーションであり、EntityやValue Objectが行う状態のカプセル化をすることはない
オペレーション名はユビキタス言語から由来したもの、あるいはそれに組み込まれるべきである
Serviceの引数や結果は、ドメインオブジェクトであるべき
良いServiceは3つの特徴を持ち
- そのオペレーションは、EntityやValue Objectの自然な部分ではないドメインの概念に関連する
- そのインタフェースは、そのドメインモデルの他の要素に関して定義される
- そのオペレーションはステートレスである
Application ServiceとDomain Serviceを区別するのは難しいことがあるが
- Serviceが基本的なビジネスロジックを含むものはDomain Service
- Application Serviceは、ビジネス的な意味合いを全く持たない
Domain Serviceは複数のEntityに跨る処理を行う
- その場合、Domain Service自体は調整役に徹し、それらのEntityが大抵のことを行う
Module (A.K.A Packages)
- Moduleはモデルの2の観点を提供する
- 全体に圧倒されることなく、Module内の詳細を見ることができる
- 内部的な詳細を見ずに、Module間の関係を見ることができる
- Moduleは、概念の結合したまとまりを表す
- Moduleの名前は、ユビキタス言語の何かの意味を伝える
Module(名)のリファクタリングはあまり起こらず、それはチームのコミュニケーションを混乱させることがある
- 結果として、Moduleの構成と名前は、そのクラスよりも、そのモデルのだいぶ前の形態をしばしば反映する
Modeling Paradigmは、コミュニティが成熟しているObject-Oriented Designを採用すべき
- しかし、これにずっと制限すべきでもなく、数学的な計算やグローバルロジックなどの実装にはObject-Oriented Designは不向きのため、その際は別のParadigmを選択すべきである
Chap.6 The Life Cycle of a Domain Object
Aggregates
- Aggregateは、データ変更の目的でユニットとして扱う、紐づいたオブジェクトのクラスタである
Aggregateはrootとboundaryを持つ
- rootは、そのAggregate内に含まれる、単一の特定のEntity
- boundaryは、そのAggregateに何があるかを定義する
Aggregate boundaryの外にあって、その内側の何かに対して参照を持つものはない(root Entityを除く)
root Entityは内部のEntityに対する参照を他のオブジェクトに渡すことができるが、それらのオブジェクトはそれらを透過的にのみ使うことができ、それらはその参照に固定することはないかもしれない
rootはValue Objectのコピーを他のオブジェクトに手渡すかもしれず、それに何が起きても問題ではない。なぜなら、それは単なる値であり、Aggregateとのいかなる紐付きも、もはやもたないだろうからである
上述の必然的帰結として、Aggregate rootだけが、データベースクエリから直接得られる。全ての他のオブジェクトは、横断的な紐付きによって発見されなければいけない
Aggregate内のオブジェクトは、他のAggregate rootに対する参照を持つことができる
削除オペレーションは、そのAggregate boundary内の全てを一度に削除しなければならない
- ガベージコレクションによって、これは容易である。そのroot以外のあらゆるものに対する外側の参照がないため、そのrootを削除し、その他の全ては回収されるだろう
そのAggregate boundaryないのいかなるオブジェクトに対する変更がコミットされたとき、そのAggregate全体の全ての不変性は満たされなければならない
Factories
- 責務が他のオブジェクトの作成であるようなプログラムの要素は、Factoryと呼ばれる
Factoryは、複雑なオブジェクトまたはAggregateを作るのに必要な知識をカプセル化する
それぞれの作成メソッドはatomic(それ以上分割することができない)、かつ作成された、オブジェクトまたはAggregateの全ての不変性を強制する
Factoryは、一貫した状態においてオブジェクトを生成できるだけであるべき
- Entityにとってこれは、全ての普遍性が満たされた状態で、もしかしたらオプション要素が今もなお追加される、Aggregate全体の作成を意味する
- イミュータブルなValue Objectにとってこれは、全ての属性がそれらの最後の状態に初期化されることを意味する
もしそのインタフェースが、正しく作成され得ないオブジェクトを要求できるならば、その時は例外が投げられるべき、あるいは不適切な戻り値が可能ではないことを保証するような何か他のメカニズムが呼び出されるべきである
Factoryは、作成された具象クラスというよりも、望まれる型に対して抽象化されるべきである
- 洗練されたFactory pattern(Gamma et al.1995)がこれを手助けする
もし既存のAggregate内に要素を追加したいならば、そのAggregateのrootにFactoryメソッドを作成する
- 要素が追加されたものとしてのAggregateの統合性を保証する責務をrootに与えつつ、いかなる外部のclientからそのAggregateの内部の実装をこれは隠す
もう一つは、他のオブジェクトの生成に密接に関与しているオブジェクト上にFactoryメソッドを配置することだが、一度それが作成されたら、それはそのプロダクトを所有しない
- そのデータと場合によっては一つのオブジェクトのそのルールがオブジェクトの作成においてとても支配的であるとき、そのオブジェクトを作成するためのどこか別の場所で使われる情報が、その生成者から抜き出されることをこれは防ぐ
Factoryではなくpublicなconstructorを使うべきなのは、以下の状況である
- クラスが型であること。それはいかなる興味深いヒエラルキーの一部ではなく、インタフェースを実装することによってポリモーフィックに使用されない
- clientは、おそらくStrategyパターンを選択する手段として、その実装を気にかける
- そのオブジェクトの全ての属性はそのclientによって利用可能であるため、そのclientに提示されたconstructorの内部では、どんなオブジェクトの作成もネストされない
- オブジェクトの生成が複雑ではない
- publicなconstructorはFactoryと同様のルールに従わなければならない
- それはその作成されたオブジェクトの全ての不変性を満たすような、atomic(それ以上分割できない)操作でなければならない
他のクラスのconstructorの内部でconstructorを呼ぶことは避けよ
引数に、そのモデル内のproduct(class)に密接に関連しているオブジェクトを渡すのも良い
- ex) Carクラスのconstructorに、Handleクラスのインスタンスを渡す
引数は抽象型を使い、それらの具象クラスの型は使ってはならない
Factoryは不変性のチェックをそのproduct(class)に以上することができ、大抵はこれがベストである
いくつかの状況では、不変性のロジックをFactory内に配置し、そのproduct(class)のごちゃごちゃを低減することに有用性がある
- これは特に、多くのオブジェクトに渡るAggregateのルールだと好ましい
- これは特に、他のドメインオブジェクトに紐づいたFactoryメソッドだと、好ましくない
Entityのアイデンティティ属性の割り当てに適用されるルールが存在していたとすると、Entity作成後はそのアイデンティティはイミュータブルであり、Value Objectは完全にイミュータブルである
オブジェクトは、そのactiveな活動期間内で決して適用されることのないロジックの周辺を保持する必要はない
- このような場合に、そのproduct(class)をシンプルにするため、そのFactoryが不変性を置くためのロジック置き場となる
Entity FactoryとValue Object Factoryは2つの点で異なる
- Value Objectはイミュータブルであり、すなわちそのproduct(instance)はその最終形態において完璧な状態で姿を現す
- よって、そのFactoryオペレーションは、そのproduct(instance)の完全な説明を考慮しなければいけない
- Entity Factoryは、有効なAggregateを作るために求められる必須の属性だけを受け取る傾向がある
- 詳細は、もしある普遍性によってそれらが求められなければ、後で追加され得る
オブジェクトの再構成に使用されるFactoryは、作成に使用されるそれととても似ているが、2つの大きな相違がある
- 再構成に使われるEntity Factoryは、新しい追跡IDをを割り当てない
- よってID属性は、保存されたオブジェクトの再構成を行うFactory内でのinput parameter(引数)の一部でなければならない
- オブジェクトを再構成するFactoryは、不変性の違反を違った形で扱う
- 新しいオブジェクトの作成の間、Factoryは不変性が満たされない時は単純に待つべきだが、より柔軟なレスポンスが再構成に必要かもしれない
- オブジェクトがそのシステムのどこか(そのデータベース内など)に既に存在する場合、その事実は無視できない(分散システムにおける話?)
- そのような矛盾を、それは再構成を新しいオブジェクトの作成よりもさらに挑戦的なものにし得るのだが、補完するための何らかの戦略がなければならない
Factories(Factoryを構成要素として述べた時の名前)は通常、そのモデルのいかなる部分も表現しないが、今のところはDomain設計の一部であり、そのオブジェクトのモデル表現力をはっきりさせるのに役立っている
Repositories
- Repositoryは(通常模倣された)概念のセットとして一定の型の全てのオブジェクトを表す
- それは、より入り組んだクエリ能力を除き、コレクションのように振舞う
- その適切な型のオブジェクトは追加または削除され、Repositoryの背後の機構はそれらをinsertあるいはデータベースからそれらを削除する
上述の定義は、ライフサイクルの早くからその終わりまで、Aggregateのrootに対するアクセスを提供する責務の凝集したセットを集める
Clientは、典型的には一定の属性の値で指定した基準に基づくオブジェクトをselectするクエリメソッドを使って、Repositoryからオブジェクトをリクエストする
直接的なアクセスが実際に必要なAggregate Rootに対してのみ、Repositoryを提供せよ
全てのオブジェクトストレージとアクセスをそのRepositoryに委譲しながら、Clientにはそのモデルに集中させよ
Repositoriesはいくつかの利点がある:
- 一貫したオブジェクトの取得とそれらのライフサイクルの管理のための、シンプルなモデルを持つClientをそれらは示す
- それらは、ApplicationとDomainの設計を、一貫したテクノロジー、複数のデータベース戦略、あるいは複数のデータソースからでさえ分離する
- それらは、オブジェクトアクセスについての設計の決定を伝える
- テスト中の使用(典型的にはインメモリコレクションの使用)に対し、ダミーの実装の簡易な代用を許可する
最も簡単なRepositoryは、特定のパラメーターを持つハードコードされたクエリを持つ
- 例えば、EntityをそのIDで取得する、など
Developerは使用しているカプセル化された振る舞いの意味合いを理解しなければいけない
- Repositoriesが異なる方法で使用される、あるいは異なる方法で機能する時、パフォーマンスの意味合いが極端なものになり得る
- それは、その実装に対する詳細な親しみを意味するわけではない
トランザクションの制御は、そのclientに委ねておく
一般的には、使用するフレームワークと戦うべきではない
- ドメイン駆動開発の基礎を保つ方法を探し、そのフレームワークが敵対する時、その細目を手放せ
- ドメイン駆動設計の概念と、そのフレームワークの概念の間の親和性を探せ
FactoriesとRepoisitoriesはそれぞれ独立した責務を持っている
- Factoryは新しいオブジェクトを作る
- Repositoryは古いオブジェクトを探す
- Repositoryのclientは、そのオブジェクトがメモリ内にあるといった幻想を与えられるべきである
- これらの2つの観点は、Repositoryに、オブジェクトの作成をFactoryに委譲させることで共存し得る
既存オブジェクトを再構成するには、clientがRepositoryにクエリを発行し、Repositoryはデータベースにクエリを投げデータを取得、Factoryにそのデータを渡して再構成し、その戻り値をclientに返す
新しいオブジェクトを保存するには、clientがFactoryに必要な値を渡してオブジェクトを作成し、Repositoryのメソッドにそのオブジェクトを渡し、Repositoryはそのオブジェクトをデータベースに保存する
FactoryとRepositoryを組み合わせる誘惑に人々が駆られるその他のケースは、"find or create" 機能であるが、この機能は避けるべきである(僅かな利便性でしかない)
- 求めるオブジェクトが見つからなければ、新しく作成したオブジェクトが返される機能
- ("update or insert"も同様だと考えられる)
技術的には、リレーショナルテーブルの設計はそのドメインモデルを反映する必要はない
問題なのは、複数の重複したモデルがかなり複雑なことである
- 分析と設計モデルの分離を避ける、モデル駆動開発に対して示された同じ引数の多くが、このミスマッチに当てはまる
- これは必然の結果としてそのオブジェクトモデルのリッチさにおいて幾らかの犠牲を伴い、時々そのデータベース設計において妥協(選択的な非正規化など)が生まれるが、違ったことをするのはモデルと実装の密な結びつきを緩めるリスクがある
- このアプローチは、短絡的な1オブジェクト/1テーブルマッピングを要求しない
マッピングが透過的であること、すなわちコードを見たりマッピングツール内のエントリを読んだりすることで容易に理解できることがとても重要である
データベースがオブジェクトストアとして見られる時、データモデルとオブジェクトモデルを深く分岐させてはならない(マッピングツールのパワーに関係なく)
- そのリレーショナルモデルを親密にしておくために、オブジェクトの関係のリッチさの幾らかを犠牲にせよ
- もしオブジェクトのマッピングの単純化を手助けをするなら、正規化といった、公式のリレーショナル標準の幾らかを妥協せよ
実行速度問題を解決するために、突飛な設計の変更が検討されるかもしれないが、オブジェクト指向ドメインの一貫した形として振舞うリレーショナルデータベースの重要な共通ケースのためには、シンプルな直結性が最善である
- テーブルの1行は、おそらくAggregate内に従属するものと一緒に、オブジェクトを含むべきである
- そのテーブルの外部キーは、もう一つのEntityオブジェクトに対する参照を説明しなければならない
- このシンプルな直結性から時々外れることの必要性は、シンプルなマッピングの原則の全体的放棄に通じるべきではない
ユビキタス言語は、そのオブジェクトとリレーショナル構成物を一つのモデルに結ぶ手助けができる
- そのオブジェクトの名前と要素の紐付きは、リレーショナルテーブルのそれらに細心の注意を払って一致すべきである
Chap.7 Using the Language: An Extended Example
- モデルをこれまで紹介してきた構成要素で組んでいく
- 上記の決定をクロスチェックするため、定期的に複数のビジネスシナリオに踏み込み、アプリケーションの問題を効果的に解決できることを確認する
- (例えば、ビジネスにおいてXXXのケースの場合、その問題は解決できるか、具に検討する)
- Entityの何かの状態を変更するケース
- 前に使ったEntityを再利用するケース
- その場合、次の二つが実装案となる
- そのEntity上にFactoryメソッドを定義する
public XXX copy(String newId)
- 独立したFactoryにメソッドを作成する
public XXX newXXX(XXX prototype, String newId? /* この引数をなくして、メソッド内で割り当てても良い */ )
- 双方向の関連(association)を持つ場合、一方のEntityのconstructorでは参照先となるEntityのインスタンスを渡す
public XXX(String id) { trackingID = id; yyy = new YYY(this); ... }
- 双方向の関連を持つEntityのインスタンス化は複雑になるため、リファクタリングして再設計すると良い
- 双方向の関連を持つEntityの設計は、そのEntityに対してRepositoryを追加することで改善される場合がある(本文ではその例が紹介されている)
- Module名を決定し、それぞれの要素がどのようにユビキタス言語に貢献するかを示すことも含まれる
- Module名は、Developerが見て実装しやすいように決めるべきではない
- もし幾らか他システムとの統合が必要な場合、その責務を満たすServiceが作成され得る
III. Refactoring Toward Deeper Insight
便利なモデルの開発の成功は、次の3点に帰結する
- 洗練されたドメインモデルは、達成可能であり、割に合うものである
- そのドメインについて学ぶことに興味のあるDeveloperとDomain Expertの緊密な関わりを含む、リファクタリングの繰り返しのプロセスを通じることなしに、そのようなモデルが開発されることはほとんどない
- それらは、効果的に実装と利用するための洗練された設計スキルを要求するかもしれない
要求される仕様から名詞と動詞を特定して、それらを最初のオブジェクトやメソッドとしてモデリングしたものは、初学者にこそ分かりやすいものの、通常は鋭さにかける表面的なものである
- Domain Expertと数々の繰り返しプロセスを経て進化したモデルは、初心者にはより分かりにくいものの、Domain Expertにはとてもより関連したものとなる
深いモデルは、そのドメインの表面的な観点から脱却する一方で、Domain Expertの主要な関心の明快な表現と、彼らの最も関連した知識を提供する
そのようなモデルが大抵いつも持っているある機能はシンプルで、それでいて限りなく抽象的で、ビジネスエキスパートが使いたくなるような言語である
モデル駆動設計は2つの足から立っている
- 深いモデルは、表現豊かな設計を可能にする
- 同時に、その設計が、実験と何が起きているのかを示すための明快さをDeveloperに許可するような柔軟性を持つ時、設計は実際にはそのモデル発見プロセスに洞察を与える
- そのフィードバックループの半分は本質的であり、なぜなら我々が探し求めているそのモデルは、単なるアイデアの素晴らしい集まりではなく、それはそのシステムの基礎だからである
Chap.8 Breakthrough
- リファクタリングの恩恵は線形的ではなく、ある時を境にその価値がとても大きくなる場合がある。これをBreakthroughと呼んでいる
コードとモデルのそれぞれの改善は、Developerにより明確な見通しを与え、この見通しが、洞察のBreakthroughに対して可能性を生み出す
より深いモデルが構築できた後では、「我々(Domain Expert)にとって技術的すぎる」としばしば示していたモデルの図表が、彼らにとって説得力のあるものになった
より深いモデルに対するBreakthroughの可能性がより深いモデルを提供する時、それはしばしば怖いことである
- そのような変更は、大抵のリファクタリングよりもより良い機会とより高いリスクがある。また、タイミングも都合が悪いかもしれない
- 本当に深いモデルへの遷移は、我々の思考における大きな変化であり、その設計に対して主要な変更を要求する
Chap.9 Making Implicit Concepts Explicit
ドメインモデルとその付随するコードの多くの変化は、議論のなかで暗示されてきた概念をDeveloperが認識した時に発生したり、あるいはその設計の中に暗黙的に存在しており、そのためDeveloperは一つまたはそれ以上のオブジェクトや関係を伴ってそのモデルの中にそれを明示的に示す
Developerは隠れた暗黙の概念を明らかにするヒントに対して敏感でなければならず、そして時々、彼らはそれらを先を見越して探し出す必要がある
- 大抵のそのような発見は、
- そのチームの言語に耳を傾けたり
- その設計の不器用さやDomain Expertの文章内の見せかけの矛盾を注意深く調査したり
- そのドメインの文学を採掘したり
- 非常にたくさんの実験をすることからもたらされる
Domain Expertが使用する言語に耳を傾けよ。以下がそのモデルのためになるかもしれない、概念のヒントである
- 簡潔に何か複雑なことを述べている用語はあるか
- (多分外交的に)彼らはあなたの言葉の選択を正しているか
- 特定のフレーズを使った際、彼らの表情に浮かぶ困った様相は消えるか
ユーザーあるいはDomain Expertが、その設計のどこにもない語彙を使った時、それは警告のサインである
- DeveloperとDomain Expertの両方がその設計にない用語を使っている時、それは疑いなく強い警告である
(Domain ExpertとDeveloperがホワイトボードにクラスのスケッチを書きながら会話している)
- (スケッチはクラス名と、そのAggregate(何対何なのかを含む)を書いた簡単な図)
深掘りする場所は、設計の最も下手な部分である
- 手続きが、説明し難い複雑なことをしている場所
- 全ての新たな要求が、複雑性を追加するように見える場所
時々、見逃している概念さえあると認識することは困難になり得る
異なるDomain Expertは、彼らの経験と必要に基づいた異なる方法で物を見る。同じ人間でさえ、注意深い分析の後では論理的に一貫性のない情報を提供する。そのような厄介な矛盾は、プログラムの要求を掘り下げる時ならいつでも我々が遭遇するものだが、より深いモデルへの素晴らしい手がかりになる
- 幾らかは用語内のただのバリエーションであり、あるいは誤解に基づくものである
- しかし、Domain Expertによる2つの事実の記述が矛盾するように見える残余がある
全ての矛盾を調和することは実践的ではなく、それは理想的でさえないかもしれない
- しかし、矛盾が放置されている時でさえ、二つの記述の両方がその同じ外部の現実にどうやって適用するのかを熟孝することは、明らかになり得る
どの分野でも、基本的な概念と慣習として認められた知恵を解説する本を見つけることができる(その本を読め)
- そのドメインでの開発経験を持った別のソフトウェアプロフェッショナルによって書かれた本を読むのもまた、もう一つの選択肢である
(Domain Expertが別のことに責務をもち、ソフトウェア開発プロジェクトの協力に興味がなかったため、本屋に行ってそのドメインの入門書を探してざっと読む例が示されている)
設計でつまずきを回避しようとすることはより低い品質の結果に帰結し、なぜならそれがより少ない経験に基づくだろうからである
- そして、それは一連の素早い実験よりも優に時間がかかり得る
制約はモデルの概念の重要なカテゴリを具体的に作る
- それらはしばしば暗黙的に現れ、そしてそれらを明示的に表現することは設計を大いに改善し得る
制約をその独自のメソッドに因数分解することは、我々は設計においてその制約を明示的にする意図の明確な名前をそれに付与できる
以下は、制約がホスト(Root?)オブジェクトの設計を歪めている幾らかの警告サインである
- 制約の評価が、その他の点でそのオブジェクトの定義に合致しないデータを要求する
- 関連したルールが、その他の点では共同体ではないオブジェクトの間で重複や継承を強制している複数のオブジェクトに現れる
- 多くの設計と必要要件の会話がその制約の周りを循環するが、その実装の中では手続きのコードの中に隠されている
明示的にされるはずのプロセスを、隠されるそれと区別するための鍵はシンプルである:
- これはDomain Expertが話すことか
- それとも、それはコンピュータプログラムの機構のほんの一部か
SPECIFICATION
Predicatesは、
- true or falseを評価し、
- より複雑なルールを表現するために、And や Or といった演算子を使って組み合わされ得るような関数である
ビジネスルールはしばしば、明白なEntityやValue Objectのいかなる責務にも合致せず、それらの多様性や組み合わせはそのドメインオブジェクトの基本的な意味をひっくり返し得る
しかしそのルールをDomain Layerの外に移すことはより一層悪いことであり、というのもそのドメインコードはもはやそのモデルを表現しないためである
ロジックプログラミングは、分離され、組み合わせ可能である、Predicatesと呼ばれるルールオブジェクト(メソッドの間違い?)の概念を提供するが、オブジェクトを伴うこの概念の完全な実装は厄介である
それがより特化された設計ほど意図を伝えないことは、またとても一般的である
我々はPredicatesの概念を拝借でき、Booleanに評価する特化されたオブジェクトを作成できる
手に負えないそれらのテストメソッドは、それら独自のオブジェクトにきちんと進出するだろう
- それらは、分離されたValue Objectに因数分解され得る真実性のテストではほとんどない
- この新しいオブジェクトはもう一つのオブジェクトを評価し、その新しいオブジェクトのPredicatesがそのもう一つのオブジェクトに対してtrueであるかを見ることができる
SPECIFICATION(パターン)は、もう一つのオブジェクトの状態における制約を述べるが、それは存在するかもしれないし、しないかもしれない
特化した目的に対する、明示的なPredicateライクなValue Objectを作成せよ
SPECIFICATIONは、オブジェクトが幾らかの基準を満たすかどうかを決定するPredicateである
デザインパターンはクックブックではない
- それは、あなたの解決策を開発するために、あなたが経験の基から出発することを許し、そしてあなたが何をやっているのかについて話すための幾らかの言語をあなたに提供する
我々は、以下の3つの目的の一つかそれ以上のために、オブジェクトのその状態を指定する必要があるかもしれない。これらの3つの利用は、概念レベルの上では同じである
- Validation: それが幾らかの必要性を満たすかどうか、あるいは幾らかの目的に対して準備が整っているかどうかを見るために、オブジェクトをValidateするため
- Selection(or Querying): コレクションからオブジェクトを選択するため
- Building to order(Generating): 幾らかの必要性を満たすための新しいオブジェクトの作成を指定するため
SPECIFICATIONを使うことなしに、必要なオブジェクトを作成する手続きや命令のセットを持ったジェネレータが書かれ得る
- このコードは、暗黙的にそのジェネレータの振る舞いを定義する
代わりに、説明的なSPECIFICATIONに関して明示的に定義されたそのジェネレータのインタフェースは、そのジェネレータの生成物を制限?する
このアプローチはいくつかの利点がある:
- そのジェネレータの実装は、そのインタフェースから分離されている。そのSPECIFICATIONはそのアウトプットに対する要求を宣言するが、その結果がどのように到達されるのかを定義しない
- そのインタフェースはそのルールを明示的に伝え、その結果、その操作の全ての詳細を理解することなしに、そのジェネレータから期待する物をDeveloperは知ることができる。手続き的に定義されたジェネレータのその振る舞いを予測する唯一の方法は、コードを実行するか、あるいはコードの全行を理解することである
- そのインタフェースはより柔軟であり、あるいはより多くの柔軟性に伴って洗練され得るが、それはなぜなら、そのジェネレータがそのSPECIFICATIONの一字一句を満たすよう唯一義務付けられた一方で、その要求の内容がそのクライアントの手中にあるからである
- 大事なことを言い忘れていたが、この種類のインタフェースはテストしやすく、それはなぜなら、そのアウトプットのバリデーションでもあるそのジェネレータにインプットを定義するための明示的方法を、そのモデルが含むからである
(プロトタイプを素早く作ることと、インタフェースと実装を分けることで、複数チームでの開発でも並行して進めることができる)
Chap.10 Supple Design
- もしDeveloperがそれを使うためにコンポーネントの実装を考慮しなければならないのなら、カプセル化の価値は失われる
- もし実装したDeveloper以外の誰かが、その実装に基づくオブジェクトや操作の目的を推測しなければならないとしたら、新しいDeveloperはその操作やクラスが果たす目的をただの偶然によって推測するかもしれない
もしあれがその意図でなければ、そのコードは束の間の間機能するかもしれないが、その設計の概念的な基礎は崩壊していき、その2人のDeveloperは行き違うだろう
クラスまたはメソッドの形で概念を明示的にモデル化することの価値を得るには、我々はこれらのプログラムの要素にそれらの概念を反映する名前を与えなければならない
- クラスとメソッドのその名前は、Developerの間での会話と、そのシステムの抽象化を改善するための素晴らしい機会である
型名、メソッド名、引数名全ては、INTENTION-REVEALING INTERFACEを構成するために結びつく
クラスや操作が約束することをするための手段を参照することなく、それらの効果や目的を説明するために、クラスや操作を命名せよ
- これは、Client(利用する側の)Developerがその内部を理解する必要をなくす
- これらの名前は、そのユビキタス言語に従うべきであり、その結果チームメンバーはそれらの意味を素早く推測できる
- 振る舞いに対して、それを作る前にテストを書け、あなたの思考をClient(利用する側の)Developerモードに矯正するために
(1. あるドメインモデルのコードを書いたら、次にテストコードを書く)
(2. 利用する側のクライアントアプリケーションを書いているつもりで(Client(利用する側の)Developerの観点から)、そのドメインモデルのオブジェクトのインタフェース設計を探求するために、そのテストを書き直す)
(3. 最初は、そのテストはコンパイルさえできない)
(4. そのドメインモデルのクラスを、テストが通るようにリファクタリングする)
SIDE-EFFECT-FREE FUNCTIONS
- 複数のルールあるいは計算の組み立ての相互作用は、予測するのが極めて難しくなる
安全に予測可能な抽象化なしに、Developerはビルド実行可能な振る舞いのリッチさに低い制限を課し、その結合性の爆発を制限しなければいけない
副作用を生むことなしに結果を返す操作は、関数と呼ばれる。以下の理由で、関数はリスクが少ない
- 関数は何回でも呼び出すことができ、毎回同じ値を返す
- 関数は、ネストの深さを心配することなく、他の関数を呼ぶことができる
- 関数は、副作用を持つ操作よりもずっとテストしやすい
できるだけ多くのそのプログラムのロジックを、目に見える副作用のない結果を返す関数や操作に配置せよ
(目に見える状態の修正をもたらすメソッド)コマンドを、ドメインの情報を返さないとてもシンプルな操作に、厳格に分離せよ
その責務に合致する概念がそれ自体を示すとき、複雑なロジックをValue Objetに移すことで、副作用をさらに制御せよ
- (Value Objectはimmutableなので、その状態が変わるメソッドでは、新たなValue Objectを生成して返すことになる)
ASSERTIONS
- 操作、クラスの不変性、Aggregateの(メソッドなどが実行された)その後の状態を述べよ
- もしあなたのプログラミング言語でASSERTIONSを直接コードで書けないなら、それらのための自動ユニットテストを書け
- それがそのプロジェクトの開発プロセスのスタイルに一致するドキュメントや図の中に、それらを書け
- 学習曲線を加速させ、矛盾したコードのリスクを減らす、その意図されたASSERTIONSをDeveloperに推測させるような、わかりやすい概念のセットをもったモデルを得ることに努めよ
CONCEPTUAL CONTOURS
- デザインの要素(操作、インタフェース、クラス、そしてAggregate)を、そのドメイン内の重要な分割のあなたの直感を考慮しながら、凝集したまとまりに分解せよ
- 有用なリファクタリングを通じて変更と安定性の座標軸を観察し、これらのせん断パターンを説明する、基礎となるCONCEPTUAL CONTOURSを探し求めよ
- そのモデルを、それをその最初の場所での知識の実行可能エリアとするドメインの、矛盾のない側面と合わせよ
STANDALONE CLASSES
- Low couplingがオブジェクト設計の基礎である
- もし可能な時は、それを徹底せよ
- 全ての他の概念をその青写真から除外せよ
- その結果、そのクラスは完全に自己完結したものとなり、単独で学習や理解され得る
全てのそのような自己完結したクラスは、Moduleの理解の負荷を著しく緩和する
おそらくより多くの結合したクラスに保持されるValue Objectをモデル化することによって、最も複雑な計算をSTANDALONE CLASSESに分解せよ
CLOSURE OF OPERATIONS
最も関心を引くオブジェクトは、primitives単独で特徴付けられない物事を結局行う
合致するところでは、戻り値の型がその引数の肩と同じ操作を定義せよ
そのような操作は、その型のインスタンスの集合の元に閉じている
閉じた操作は、他の概念上に依存性を導入することなく、高レベルのインタフェースを提供する
このパターンはほとんどの場合、Value Objectのその操作に適用される
Chap.11 Applying Analysis Patterns
- (具体例の記述がメインのため、特に要約することなし)
Chap.12 Relating Design Patterns to the Model
STRATEGY (A.K.A POLICY)
- プロセスの変化する部分を、そのモデル内の分離した"strategy"オブジェクトに分解せよ
- ルールと、それが当時する振る舞いを別々に分解せよ
- STRATEGYデザインパターンに従う、そのルールや代替可能なプロセスを実装せよ
- strategyオブジェクトの多様なバージョンは、そのプロセスが完了され得る異なる方法を表す
- デザインパターンとしてのSTRATEGYの慣習的な見方が、異なるアルゴリズムを代用する能力に焦点を当てるのに対し、ドメインパターンとしてのその利用は、通常プロセスやポリシールールとして概念を表現するその能力に焦点を当てる
COMPOSITE
- そのCOMPOSITEの全てのメンバーを包含する抽象型を定義せよ
- 情報を返すメソッドはコンテナ上に実装され、それらの内容に関する集約された情報を返す
- "枝"点は、それら特有の値に基づいて、それらのメソッドを実装する
クライアントはその抽象型を処理し、コンテナと枝を区別する必要はない
唯一の要求は、そのパターンが概念的なドメインについて何かを示さなければならず、それは技術的な問題に対する技術的な解決策であるべきではない
Chap.13 Refactoring Toward Deeper Insight
- 以下の時にリファクタリングせよ
- その設計が、そのドメインのチームの現在の理解を表現しないとき
- その設計において、重要な概念が暗黙的である(そして、それらを明示的にする方法を見つけた)とき
- その設計の幾らか重要な部分をより柔軟にする機会を見つけたとき
リリースの前日はリファクタリングするな
技術的な名人芸のデモにすぎない"柔軟な設計"を紹介するな
どんなにそれが美しく見えても、Domain Expertに使用するよう納得させられない"より深いモデル"を紹介するな
物事に対して絶対的になるな、しかしリファクタリングを好む方向性の中のコンフォートゾーンを超えろ
IV. Strategic Design
Chap.14 Maintaining Model Integrity
- それぞれの用語が不明瞭ではなく、どのルールも矛盾していないような、モデル内部の一貫性は、unificationと呼ばれる
- 事実、巨大なシステムに対するそのドメインモデルのunificationの合計は、実行可能あるいは費用対効果の良いものではないだろう
BOUNDED CONTEXT
- あらゆる巨大なプロジェクト上で、複数のモデルが影響を及ぼしている
- 異なるモデルに基づくコードが組み合わさっている時でも、いつかソフトウェアはバグを含み、信頼できない、理解し難いものになる
- チームメンバーの間でのコミュニケーションは波乱含みになる
どんな文脈にモデルが適用されるべきで"ない"のかは、しばしば不明瞭である
モデルが適用される範囲内でのその文脈を明示的に定義せよ
チーム組織、そのアプリケーションの特定の部分での使用法、そしてコードベースとデータベーススキーマのような物理的な表明に関して、明示的に境界線を設定せよ
そのモデルをこれらの境界内において、厳密に一貫している状態を保て、しかし外側の問題によって気が散ったり混乱してはいけない
CONTINUOUS INTEGRATION
- 数多くの人が同じBOUNDED CONTEXT内で働いている時、そのモデルが砕ける強い傾向がある
- チームが大きければ大きいほど、その問題も大きく、しかしわずか3、4人だけが深刻な問題に直面しうる
そのシステムをさらに細かいCONTEXTSに分解することは結局、統合と一貫性の価値あるレベルを失う
CONTINUOUS INTEGRATIONは、その文脈内での全ての仕事が統合され、コードをばらばらに裂く人が現れたときに彼らがすぐ捕まえられて正されるほど充分頻繁に、一貫性あるものにされることを意味する
CONTINUOUS INTEGRATIONは、ドメイン駆動設計における他の事柄と同じように、二つの基準で稼働する
- モデル概念の統合
- その実装の統合
効果的なプロセスにおけるもう一つの側面は、滅多に正式に含まれることがないが、"概念"の統合である
そのモデルやアプリケーションの議論において、そのユビキタス言語を定期的に(使うことで)鍛えよ
分裂を素早く知らせる自動化されたテストを伴う、全てのコードと他の実装の成果物を頻繁に統合するプロセスを設定せよ
概念が異なる人々の頭の中で進化するにつれて、そのモデルの共有された見解を形作るために、そのユビキタス言語を容赦無く(使うことで)鍛えよ
CONTEXT MAP
- 他のチームの人々は、その文脈の境界にほぼ気づいていないかもしれず、無意識のうちにその縁を不鮮明にする変更を生んだり、その相互接続を複雑にしてしまうだろう
異なる文脈の間で結合が作られなければならない時、それらは互いに血を流す傾向がある
そのプロジェクト上で稼働するそれぞれのモデルを特定し、そのBOUNDED CONTEXTを定義せよ
- これはオブジェクト指向で設計されていないサブシステムの暗黙的なモデルも含む
それぞれのBOUNDED CONTEXTの名前を、そのユビキタス言語の一部となるように命名せよ
あらゆるコミュニケーションに対する明示的な翻訳の輪郭を描き、いかなる共有も強調しながら、そのモデルの間での接点の要点を描写せよ
"既存"の領域の地図を作成せよ。変形は後ほど着手せよ
CONTEXT MAPは現状を常に表すことに留意せよ
- 目にする関係性をただ描写せよ
- 現実での変更が完了するまで、CONTEXT MAPを変更してはならない
BOUNDED CONTEXTは名前を持つべきであり、その結果、それらについて会話することができる。それらの名前は、そのチームのユビキタス言語に立ち入らなければならない
全ての人は、その境界線がどこにあるのかを知らなければならず、コードのいかなる部分やいかなる状況のその文脈を認識できなければならない
等しく重要なのは、チーム全員が同じ方法で概念的な境界を理解するような方法で、それらを会話することである
もしBOUNDED CONTEXTの名前がユビキタス言語に立ち入るのならば、それらを議論に組み入れることは絶対不可欠である
- 「ジョージのチームのものが変わっているので、我々はそれに話しかける我々のものを変化しなければならない」などと言うな
- 代わりに「Transport Networkモデルが変わっているので、我々はそのBooking contextに対してtranslatorを変化させなければならない」と言え
SHARED KERNEL
(SHARED KERNELとは、CONTEXT MAPにおいて、BOUNDED CONTEXTで区切られた領域が重複している部分)
二つのチームが共有すると合意したドメインモデルの幾らかのサブセットを指定せよ
もちろんこれは、そのモデルのこのサブセットと共に、そのモデルの部分に関連したコードやデータベース設計のサブセットを含む
この明示的に共有されたものは特別なステータスをもち、他のチームに相談することなく変更すべきではない
機能的なシステムを頻繁に統合せよ、しかしなんとかしてそのチーム内のCONTINUOUS INTEGRATIONのペースよりも頻度は低くせよ
これらの統合において、両チームのテストを実行せよ
CUSTOMER/SUPPLIER DEVELOPMENT TEAMS
- 上流チームの自由奔放な開発は窮屈になり得るが、それは下流チームが変更に対する拒否権を持つ場合や、あるいは変更を要求する手続きがとても面倒な場合である
- 下流のそのシステムを破壊することを恐れて、上流チームは抑制さえされているかもしれない
一方で、上流の優先順位に翻弄されて、下流のチームは頼りなくなり得る
上流と下流のチームの間で、明白なcustomer/supplier関係を成立させよ
プランニングセッションで、下流チームは上流チームに対して顧客の役割を演じさせよ
下流チームの要求に対するタスクを交渉し、予算に計上すれば、全員がその傾倒とスケジュールを理解する
期待されるインタフェースを検証するであろう、自動化された受け入れテストを合同で開発せよ
- これらのテストを上流チームのテスト一式に追加し、その継続的インテグレーションの一部として実行されるようにせよ
このテストは、上流チームが下流への副作用を恐れることなく変更することを自由にする
- (また、下流のチームは、上流チームを定期的に監視することなく、彼らの仕事に集中できる)
受け入れテストの自動化は、このcustomer関係における重要な部分である
これらのテストに対するいかなる変更は、他のチームと共に会話することを要求するが、なぜならテストの変更はそのインタフェースの変更を意味するからである
CONFORMIST
- 二つの開発チームが、上流が下流チームの需要に供給するモチベーションを持たないような上流/下流の関係を持つ時、下流チームはなす術がない
- 利他主義は上流のDeveloperに約束するよう動機付けするかもしれないが、それらが満たされることはありそうもない
- それらの善意における信念は、決して手に入らないだろう機能に基づく計画を下流チームに作るよう仕向ける
- 下流チームが与えられたものと共に存続することを究極的に学ばない限り、そのプロジェクトは遅延するだろう
下流チームの要求に対して調整されたインタフェースは、そのカードの中にない
上流チームのそのモデルに奴隷のように忠実であることで、BOUNDED CONTEXTの間の翻訳の複雑性を排除せよ
これは下流のデザイナーの表現方法をけいれんさせ、そのアプリケーションの理想的なモデルをおそらく生むことができないにもかかわらず、CONFORMITYの選択は桁外れに統合をシンプルなものにする
またあなたは、supplierチームと共にユビキタス言語を共有するだろう
そのsupplierは運転席にいるため、彼らにとってコミュニケーションを容易にすることは良いことである
利他主義は、彼らに情報をあなたと共有させるには充分かもしれない
ANTICORRUPTION LAYER
- クライアントに彼らが保有するドメインモデルに関する機能を提供するための、隔離しているレイヤーを作成せよ
- そのレイヤーは、他のシステムに対する修正を少しかあるいは全く要求することなく、その既存のインタフェースを通じて他のシステムと会話する
内部的には、そのレイヤーは、その二つのモデルの間で必要なように、双方向において翻訳する
ANTICORRUPTION LAYERのpublicなインタフェースは通常、SERVICEのセットとして現れ、場合によってはENTITYの形を取ることもある
二つのシステムのセマンティクスの間の翻訳に対して責務を持つ、全く新しいレイヤーを構築することは、我々に次の機会を与える
- 他のシステムの振る舞いを再抽象化する
- 我々のモデルと一貫した状態で、我々のシステムにそのサービスや情報を提供する
ANTICORRUPTION LAYERの設計を組織する一つの方法は、システム間で会話するのに通常要求されるコミュニケーションと転送メカニズムを伴う、FACADES、ADAPTERSとtranslatorの組み合わせとしてである
FACADEは、クライアントのアクセスを単純化するサブシステムにむけた別のインタフェースであり、そのサブシステムを使いやすくする
- 特に必要でなければ不要
ADAPTERは、その振る舞いの実装者によって理解されたものよりも、異なるプロトコルをクライアントが使えるようにするラッパーである
我々が定義するどのSERVICEに対しても、そのSERVICEのインタフェースを支え、その他のシステムと同等なリクエストをどう作るか、あるいはそのFACADEを知るADAPTERが、我々には必要である
translatorは、必要な時にインスタンスかされる、軽量なオブジェクトになり得る
- それは状態を必要とせず、分散される必要がないが、なぜならそれはそれが仕えるADAPTERに属するためである
(自分のシステム -> Service A -> Adapter A -> Facade -> 他のサブシステムとして、ANTICORRUPTION LAYERが定義される)
- (Adapter Aは内部的にtranslatorをインスタンス化して利用する)
ANTICORRUPTION LAYERは、二つのBOUNDED CONTEXTを結びつける手段であることを覚えておくこと
SEPARATE WAYS
- 統合は常に高価である。時にはその利益が小さいこともある
- Developerが小さなスコープの中に、シンプルで特化した解決策を見つけられるような、他に対して全く結合を持たないようにBOUNDED CONTEXTを宣言せよ
OPEN HOST SERVICE
- サブシステムが多くの他者と統合されなければならない時、それぞれに対してtranslatorをカスタマイズすることは、そのチームの動きを取れなくする
ますます維持と、変更が生まれた時についての心配が出てくる
SERVICEのセットとして、あなたのサブシステムにアクセスを提供するプロトコルを定義せよ
そのプロトコルを開け、その結果あなたと統合する必要のある全員がそれを使うことができる
単一のチームが変わった要求を持つ時を除き、新しい統合の要求を扱えるように、そのプロトコルの質を高め、拡張せよ
そして、その特別なケースに対してそのプロトコルを増大するために、一回限りのtranslatorを使用せよ、その結果共有されたそのプロトコルはシンプルかつ理路整然とした状態でいることができる
PUBLISHED LANGUAGE
- 既存のドメインモデルに対する、そしてそれからの直接の翻訳は、良い解決策ではないかもしれない
- それらのモデルは過度に複雑か、あるいは下手に分解されているかもしれない。
- それらはおそらくドキュメント化されていない
一方がデータ入れ替え言語として使われいる場合、それは基本的に機能が停止したものになり、新しい開発の要求に応答することができない
必要なものとして共有化された言語に、かつその言語から翻訳しながら、コミュニケーションの共通媒体としての必要なドメインの情報を表現できる、よくドキュメント化された、共有の言語を使用せよ
(まずSEPARATE WAYSを考慮し、統合が本当に必要不可欠であれば、CONFORMISTあるいはANTICORRUPTION LAYERを選択せよ)
(今まで登場してきた概念をどう選択するかのガイドラインが記載されているので、必要に応じてp385以降を参照せよ)
一般的に、CONTEXTを分解することはとても簡単で、しかしそれらを統合したり、それらの間の関係を変化することは困難だがやりがいがある
Chap.15 Distillation
- ドメインモデルの戦略的な精製は、次のことの全てをする
- そのシステムの全体の設計と、どのようにそれが一緒に調和するのかを、全てのチームメンバーが理解することの手助け
- そのユビキタス言語に取り込むために、管理可能な大きさのコアモデルを特定することによる、コミュニケーションの手助け(ファシリテート)
- リファクタリングのガイド
- 最も価値のあるそのモデルの範囲における仕事への焦点
- アウトソーシング、既製コンポーネントの使用、割り当てに関する決定のガイド
CORE DOMAIN
巨大なシステムの設計において、全てが複雑かつ成功のために絶対的に必要な、本当に多くの貢献する構成要素が存在するので、実際のビジネス資源であるそのドメインモデルの本質は、不明瞭で手入れされていないことがあり得る
厳しい現実は、その設計の全ての部分が等しく精製されるわけではないことである
優先順位が設定されるに違いない
そのドメインモデルを資源とするため、そのモデルの重要な中心部は、アプリケーションの機能性を創造するために、こぎれいで十分利用されなければならない
しかし、まれなとてもスキルのあるDeveloperは、専門のドメイン知識なしで理解され得る、技術的なインフラストラクチャあるいはきちんと設計できるドメインの問題に引き付けられる傾向にある
CORE DOMAINは、あなたのシステムに付与されなければならない最も大きな価値がある場所である
そのモデルを煮詰めよ
CORE DOMAINを探し、それを脇役のモデルやコードの塊から容易に区別するための手段を提供せよ
最も価値があり、特殊化された概念を浮き彫りにせよ
その中心部を小さくせよ
トップの才能をそのCORE DOMAINに適用し、状況に応じて採用せよ
より深いモデルを探し、そのシステムの構想を十分満たすような柔軟な設計を開発するために、その中心部に労力を費やせ
それが精製された中心部をどのくらい支えるのかによって、その他の部分への投資を正当化せよ
GENERIC SUBDOMAINS
- そのモデルのいくつかの部分は、専門の知識を獲得したり会話したりすることなしに、複雑性を追加する
- 外部のいかなるものは、そのCORE DOMAINを見つけたり理解したりし辛くする
- 全員が知っている一般的な原則や、あなたの主要な焦点ではないが、支援する役割を果たす得意分野に属する詳細を伴って、そのモデルは行き詰まる
今のところは、しかしながら一般的に、これらの他の要素は、そのシステムの機能やそのモデルの最大限の表現にとって必要不可欠である
あなたのプロジェクトに対して意欲を起こさせないような、凝集したサブドメインを特定せよ
これらのサブドメインの一般的なモデルを取り除き、個別のMODULEにそれらを配置せよ
それらにあなたの得意分野の痕跡を残すな
一度それらが分離されたら、そのCORE DOMAINよりもそれらの継続的な開発には低い優先度を与え、あなたの主要なDeveloperにそのタスクを割り当てるのは避けよ(なぜなら、彼らはそれらからドメイン知識を少しも得ることがないだろうから)
また、既製の解決策や、これらのGENERIC SUBDOMAINSに対する公開されたモデルを検討せよ
そのドメインモデリングのリスクを過小評価するのは容易い。それは次のような形を取ることがあり得る
- 見えていない複雑性
- ビジネスエキスパートに対する不十分な疎通
- そのDeveloperの鍵となるスキルのギャップ
DOMAIN VISION STATEMENT
- プロジェクトの初期において、そのモデルは通常存在さえしていないが、それにもかかわらず、その開発に焦点を当てるその必要性は既にそこにある
- 開発の後の方の段階で、そのモデルの徹底した学習を要求しないシステムの、その価値の説明の必要がある
また、そのドメインモデルの重要な側面は複数のBOUNDED CONTEXTに跨がるかもしれないが、本質的にこれらの異なったモデルは、それらの共通の焦点を示すために構成され得ない
そのCORE DOMAINとそれがもたらすであろう価値(その価値ある提案)の、(約1ページの)短い説明を書け
このドメインモデルを他と区別しないそれらの側面は無視せよ
そのドメインモデルが多様な関心にどのように仕え、バランスを取るのかを示せ
それを限定されたものに保て
この記述を早期に書き、新しい洞察を得るにつれてそれを修正せよ
(DOMAIN VISION STATEMENTの例がいくつか登場するが、"そうでない"例では技術的な内容が包含されている)
HIGHLIGHTED CORE
- チームメンバーは何がそのCORE DOMAINを構成するのかを広く知っているかもしれないにも関わらず、異なる人々は寸分違わない要素を選び出さず、全く同じ人でさえもある日とその次の日では辻褄が合っていないだろう
- 鍵となる部分を特定するために、そのモデルを定期的にフィルターにかけることの精神的負担は、設計の思考に費やされる集中をより緩和し、そしれそれはそのモデルの包括的な知識を要求する
- そのCORE DOMAINは見やすいように作られなければならない
- そのコードに対する重要な構造上の変化は、そのCORE DOMAINを特定する理想的な方法であるが、短い期間においては常に実用的であるわけではない
事実、そのような主要なコードの変化は、そのチームが大きく欠いている見解なしでは引き受けるのが難しい
そのCORE DOMAINとCORE要素の間の主要な相互作用を描写する、(3から7のまばらなページの)とても簡潔なドキュメントを書け
役割を明らかにするための特別な取り組みなしに、そのモデルの主要なリポジトリの内にそのCORE DOMAINのそれぞれの要素の旗を立てよ
DeveloperがそのCOREに何があって、何がないのかを知ることに努力を要しないようにせよ
もしその精製ドキュメントがそのCORE DOMAINの本質の要点を述べるならば、結果それはモデル変更の重要性の実践的な指針として仕える
モデルあるいはコードの変更がその精製ドキュメントに影響を与える時、それは他のチームメンバーとの相談を要求する
その変更がなされた時、それは全てのチームメンバーへの早急な通知と、そのドキュメントの新しいバージョンの流布を要求する
そのCOREの外側、あるいはその精製ドキュメントに含まれない詳細に対する変更は、相談や通知なしで統合され得るし、仕事の過程で他のメンバーによって引き起こされるだろう
そして、DeveloperはXPが提案するその完全な自律を持つのだ
COHESIVE MECHANISMS
- 計算は時々、その設計を膨張させ始めるような複雑性のレベルに到達する
- 概念的な"何"は、機械的な"how"によって無力化される
その問題を解決するためのアルゴリズムを提供するメソッドの大勢は、その問題を表現するそのメソッドを分かりにくくする
概念的なCOHESIVE MECHANISMを分離した軽量のフレームワークに分割せよ
形式主義、あるいはよくドキュメント化されたアルゴリズムのカテゴリをとりわけ監視せよ
INTENTION-REVEALING INTERFACEを伴うそのフレームワークの能力を顕在化させよ
今やそのドメインの他の要素は、解決策("どうやって")の複雑さをそのフレームワークに委譲しながら、その問題("何")を表現することに焦点を当てることができる
SEGREGATED CORE
- そのモデルの要素は、部分的にはそのCORE DOMAINに仕えて、部分的には支援する役割を演じるかもしれない
- CORE要素は、包括的な要素にきつく結合されているかもしれない
- そのCOREの概念的な結合は、強いあるいは目に見えるものではないかもしれない
- 全てのこの混乱ともつれは、そのCOREを窒息させる
Designerは、弱い設計に至りながら、最も重要な関係性を明確に見ることができない
そのモデルをリファクタリングして、そのCORE概念から支援するもの(はっきりしないものを含む)を分離し、他のコードに対するその結合を減らす一方で、そのCOREの結合を強化せよ
全ての包括的あるいは支援的な要素を他のオブジェクトに分解し、それらを他のパッケージに配置せよ、もしこれが高度に結合された要素を分離する方法でそのモデルをリファクタリングすることを意味するとしても
SEGREGATED COREにリファクタリングするのに必要なステップは、典型的には以下のようなものである
COREサブドメインを特定せよ(場合により、その精製ドキュメントから引っ張る)
関連するその概念に対して命名された、関連したクラスを新しいMODULEに移動させよ
その概念の直接的表現ではないデータや機能を断ち切るために、コードをリファクタリングせよ。削除された側面を、他のパッケージの(可能であれば新しい)クラスに入れよ。それらを概念的に関連したタスクと共に配置することを試みよ、しかし完璧であることに多くの時間を浪費するな。
そのCOREサブドメインを磨き、それからの参照を明示的で自明な他のパッケージに作成することに焦点を当て続けよ
関係性と相互作用をよりシンプルに、より多くを語り、他のMODULEとのその関係性を最小限かつ明らかにするため、その新しいSEGREGATED CORE MODULEをリファクタリングせよ
そのSEGREGATED COREが完成するまで、もう一つのCOREサブドメインと共に繰り返せ
DeveloperとDomain Expertは、その知識の噛み砕きプロセスの一部として、戦略的な精製において協力する
ABSTRACT CORE
分離したMODULE内のサブドメインの間で多くの相互作用が存在する時、多くの参照のどちらか一方は、その仕切りの価値の多くを無にするような、MODULEの間に作成されなければならず、あるいはその相互作用はそのモデルを不明瞭にするような、間接的なものにされなければならないだろう
そのモデルの中で最も基本的な概念を特定し、それらを独立したクラス、抽象クラス、あるいはインタフェースに分解せよ
この抽象モデルを設計せよ、その結果、それは重要な構成要素の間の大抵の相互作用を表現する
特化した、詳細な実装クラスが、サムドメインで定義されたそれらが所有するMODULEの中に取り残されている一方で、この端から端まで抽象的なモデルをそれ自身のMODULEの中に配置せよ
深いモデルに対するブレイクスルーが、発生するどの場所においても価値を提供するにも関わらず、それがプロジェクト全体の起動を変えることができるのは、CORE DOMAINの中である
Chap.16 Large-Scale Structure
- 要素が全体の設計にまたがるパターンにおけるそれらの役割によって解釈されることを許すような、いかなる全体的な減速もない巨大なシステムにおいては、Developerは木を見て森を見ることができない
- その部分の責務の詳細な知識を持たないとしても、その全体のシステムにまたがり、かつその全体においてそれぞれの部分の幾らかの理解を許すような、ルールあるいは役割や関係性のパターンを考案せよ
EVOLVING ORDER
- 自由参加の設計は、誰も全体としての意味を解明できないシステムを作り出し、それらは維持するのがとても困難である
- しかし、設計者はプロジェクトを前もっての設計想定で束縛することができ、そのアプリケーションの特定の部分のDeveloper/Designerから非常に多くの力を奪う
すぐさま、Developerはそのアプリケーションをその構成に合うように書き直すか、あるいは、ギクシャクした開発のその問題に後戻りしながら、それを転覆して、全く構成を持たないようにするだろう
場合によってはここに至るまでの構成の完全に異なった型に変更しながら、この概念的な大きなスケールの構成をそのアプリケーションと一緒に進化させよ
詳細な知識と共に作られなければならない、設計とモデルの詳細な決定を過剰に抑制してはならない
CONTEXT MAPと違い、Large-Scale Structureは必須ではない
Large-Scale Structureは、構成がモデル開発において不自然な制限を課すことなくそのシステムをとてもうまく明確にするとわかった時だけ、適用されるべきである
サイズの合わない構成はないよりも尚悪いため、包括性に向かって頑張らずに、しかし現れてきた問題を解決する最小限のセットを見つけ出す方が最善である
少ないはより効果を挙げる
SYSTEM METAPHOR
- ソフトウェア設計はとても抽象的で、理解するのが困難な傾向にある
Developerとユーザーは同様に、そのシステムを理解し、全体としてそのシステムの姿を共有するための具体的な方法を要求する
チームメンバーの創造性を引き付け、有益な方向に思考を導くように見える具体的な類推がそのシステムに現れた時、それをLarge-Scale Structureとして採用せよ
この隠喩の周りのその設計を組織し、それをユビキタス言語の一部とせよ
SYSTEM METAPHORは、そのシステムについての会話と、それの開発のガイドをの両方を促すべきである
潜在的には異なったBOUNDED CONTEXTに渡ったとしても、これはそのシステムの異なる部分における一貫性を増大させる
しかし、全ての隠喩は不正確であるため、過剰な拡大や不適切に対して、その隠喩を継続的に再調査し、もしそれがその道に差し入ったら、それを捨てる心構えをせよ
RESPONSIBILITY LAYERS
- それぞれの固有のオブジェクトが手作りの責務を持つ時、ガイドライン、統一性、そのドメインの大きな帯状を一緒に扱う能力などない
大きなモデルに統一性を与えるために、それらの責務の割り当て上に幾らかの構成を課すことは便利である
あなたのモデルのその概念的な依存性、その変動率、あなたのドメインの異なる部分の変更の源に着目せよ
もしあなたがそのドメインいおいて自然な階層を特定するならば、それらに広く抽象的な責務を割り当てよ
これらの責務は、その高レベルの目的のストーリーや、あなたのシステムの設計を伝えるべきである
そのモデルをリファクタリングせよ、その結果、それぞれのドメインオブジェクト、AGGREGATEそしてMODULEのその責務は、一つのレイヤーの責務の中にきちんとはまる
KNOWLEDGE LEVEL
KNOWLEDGE LEVELとはオブジェクトのグループであり、オブジェクトのもう一つのグループがどう振る舞うべきかを描写するものである
Entity間のその役割と関係性が異なる状況で違うような中でのアプリケーションにおいて、複雑性は爆発し得る
完全に汎用なモデルもとてもカスタマイズされたモデルのどちらも、そのユーザーのニーズに仕えることはない
オブジェクトは結局、複数ケースの多様性を満たすために他の型に対する参照したり、あるいは異なる状況で異なる方法で使われる属性となる
同じデータと振る舞いを持つクラスは、異なる集合規則を提供するためだけに、増えていくかもしれない
その基礎となるモデルの構造と振る舞いを描写かつ制限するために使われ得る、オブジェクトの独立したセットを作成せよ
これらの関心を2つの"レベル”として分離した状態に保て、一つはとても具体的で、もう一つはユーザーあるいはスーパーユーザーが調整できるルールと知識を反映している
PLUGGABLE COMPONENT FRAMEWORK
- アプリケーションの多様性が相互作用しなければいけない時、独立して設計されたが、全く同じ抽象と複数のBOUNDED CONTEXTの間の翻訳に基づく全ては、統合を制限する
- SHARED KERNELは密接に連携して働かないチームにとっては実行可能ではない
重複と分裂化は開発とインストールのコストを増大させ、相互運用性はとても困難なものになる
インタフェースや相互作用のABSTRACT COREを精製し、インタフェースが自由に置換される多様な実装ができるフレームワークを生み出せ
同じように、どんなアプリケーションでもそれらのコンポーネントを使えるようにさせ、それはそれがそのABSTRACT COREのインタフェースを通じて厳密に動作する限りにおいてである
PLUGGABLE COMPONENTは、適用するのが非常に難しいパターンであり、アプリケーションが限定された選択肢を持つことがマイナス点である
(インタフェースを厳密に決めて、そのフレームワークを業界全体で守ることで大きな利益を上げた工場ロボット用ソフトウェアの例が示されている)
Chap.17 Bringing the Strategy Together
- あなたがプロジェクト上の戦略的設計に取り組む時、その現在の状況の明白な評価から始めなければならない
- CONTEXT MAPを描け。一貫したものが書けるだろうか?それとも曖昧な状況があるだろうか?
- そのプロジェクト上で言葉の使用に関心を向けよ。ユビキタス言語はあるか?それは開発を手助けできるほど十分リッチなものか?
- 何が重要であるかを理解せよ。そのCORE DOMAINは特定されているか?DOMAIN VISION STATEMENTは存在するか?それを書けるか?
- そのプロジェクトのテクノロジーは、MODEL DRIVEN DESIGNの役に立つか?不利に働くか?
- そのチームのDeveloperは必須の技術スキルを持っているか?
- そのDeveloperはそのドメインについて博識であるか?彼らはそのドメインに"興味がある"か?
Six Essentials for Strategic Design Decision Making
- 意思決定は、その全体のチームに到達しなければならない
- その意思決定プロセスはフィードバックを吸収しなければならない
- その計画は進化を考慮しなければならない
- アーキテクチャーチームは、全てのその最善と輝かしさを吸い上げてはならない
- Strategic Designは、ミニマリズムと謙虚さを要求する
- 真実は、ほとんど全てのものが何かの邪魔になるので、それぞれの要素は価値がないと困る
- オブジェクトはスペシャリストであり、Developerはジェネラリストである
超初心者向けのフレームワークは書くな
マスタープラン(基本計画)は失敗する
本書に例として登場するどの開発プロジェクトも、本書の全てのテクニックを動員してはいない
- 例えそうであっても、ドメイン駆動設計に取り組んだいかなるプロジェクトも、限られた種類の方法で認識できるだろう
- その決定的な特徴は、その対象ドメインを理解することとその理解をそのソフトウェアに取り込むことに重点を置いていることである
思考の産物でなければならないものを自動化しようとする努力は考えが甘く、逆効果である
GLOSSARY
AGGREGATE
- データの変更の目的のためのまとまりとして扱われる、紐づけられたオブジェクトのクラスタ
- 外部からの参照は、rootとして指定されるAGGREGATEのメンバーに制限される
- 一貫性のルールのセットは、そのAGGREGATEの境界内で適用される
analysis pattern
- ビジネスモデリングにおける共通の構造を示す概念のグループ
- ただ一つのドメインに関係があるかもしれず、あるいは複数のドメインに跨るかもしれない
ASSERTION
- ある時点でのプログラムのその正しい状態の陳述で、プログラムがどのようにそれを行うかとは無関係である
- 典型的には、ASSERTIONは操作あるいは設計要素の不変性のその結果を指定する
BOUNDED CONTEXT
- 特定のモデルの線で分けた適用可能性
- BOUNDED CONTEXTSはチームメンバーに、何が一貫していなければならず、何が独立して開発できるのかについての、明白で共有された理解を提供する
client
- 設計の元で、その能力を使ってその要素を呼ぶプログラムの要素
cohesion
- 論理的な合意と依存
command
- (A.K.A modifier)
- (例えば変数を設定することといった)そのシステムに幾らかの変化をもたらす操作
- 内部的に副作用を生む操作
CONCEPTUAL CONTOUR
- もしドメインの中に反映されたら、その設計が変更をより自然に提供することを手助けするような、そのドメイン自体の基礎となる一貫性
context
- 言葉や記述がその意味を決定すると分かるようなその設定
- See BOUNDED CONTEXT
CONTEXT MAP
- プロジェクトに関与しているそのBOUNDED CONTEXTSと、それらとそれらのモデルの間のその実際の関係性の表明
CORE DOMAIN
- ユーザーのゴールの中核を成す、そのアプリケーションを差別化し、それを価値のあるものにする、そのモデルの代表となる部分
declarative design
- プロパティの詳細な記述が実際にそのソフトウェアを制御するプログラミングの形式。
- 実行可能な仕様
deep model
- そのDomain Expertsのその主要な関心と、彼らに最も関連した知識の、鋭い表現
- 深いモデルは、そのドメインの表面的な側面と考えの甘い解釈を捨て去る
design pattern
- 特定の文脈において一般的な設計の問題を解決するようカスタマイズされたオブジェクトとクラスを伝える説明
distillation
- より価値があって使いやすいようにする形式の中でその本質を抽出するため、混合物からその要素を分離するプロセス
- ソフトウェア設計においては、モデルの鍵となる側面のその抽象化、あるいはそのCORE DOMAINを前面に押し出すためのより巨大なシステムのその分割
domain
- 知識、影響あるいは活動の領域
domain expert
- ソフトウェア開発というよりそのアプリケーションのそのドメインを畑にもつソフトウェアプロジェクトのメンバー
- 単にそのソフトウェアの全てのユーザーではなく、そのDomain Expertはそのテーマの深い知識をもつ
domain layer
- LAYERED ARCHITECTURE内のドメインロジックに責務を持つその設計と実装の一部分
- そのドメインレイヤーは、そのドメインモデルのソフトウェア的表現が息づく場所
ENTITY
- その属性によってではなく、継続性と独自性の筋によって基本的に定義されるオブジェクト
FACTORY
- 複雑な生成ロジックのカプセル化とclientのために作成されたオブジェクトのその型を抽象化するための機構
function
- 観察できる副作用なしに、結果を計算し返す操作
immutable
- 作成後、観測可能な状態が決して変更されないプロパティ
implicit concept
- モデルや設計のその意味を理解するために必須だが、決して言及されない概念
INTENTION-REVEALING INTERFACE
- クラス、メソッドそして他の要素のその名前が、それらを作った最初のDeveloperの目的とそれらの価値の両方をclient Developerに伝える設計
invariant(上記では不変性と訳している)
- メソッドの実行の途中あるいはコミットされていないデータベーストランザクションの途中のような、特定の遷移的な状況の間を除き、どんな時でも真実でなければならない幾らかの設計要素についてのASSERTION
iteration
- プログラムが細かなステップの中で繰り返し改善されるプロセス
- また、それらのステップの一つ
large-scale structure
- 全体のシステムに対して設計のパターンを確立する、高レベルの概念、ルールあるいはその両方のセット
- そのシステムが大まかなアウトラインにおいて議論され理解され得る言語
LAYERED ARCHITECTURE
- 他のものに囲まれたドメインレイヤーを隔離しながら、ソフトウェアシステムの関心を分離するテクニック
life cycle
- 典型的には、ある状態から次の状態に変化する時、その完全性を保証するための制限を伴って、作成と削除の間でオブジェクトがとり得る状態の連続
- システムと異なるBOUNDED CONTEXTの間で、Entityの移行を含むかもしれない
model
- ドメインの選択された側面を記述し、そのドメインに関連した問題を解決するために使用され得る、抽象化のシステム
MODEL-DRIVEN DESIGN
- ソフトウェアの要素の幾らかのサブセットがモデルの要素に密接に一致する設計
- また、お互いに整列した状態を保つような、モデルと実装の共同開発のプロセス
modeling paradigm
- (例えば、オブジェクト指向プログラミングとロジックプログラミングのような)概念のソフトウェア類似物をつくるためのツールと相まった、ドメイン内の概念を苦労して作り上げる特定のスタイル
REPOSITORY
- オブジェクトの集合を模倣するような保管、修正、検索の振る舞いをカプセル化するための機構
responsibility
- タスクを実行するための、あるいは情報を知るための責務
SERVICE
- カプセル化された状態なしで、そのモデル内に独立している、インタフェースとして提供される操作
side effect
- 内部的かそうでないかに関わらず、たとえ意図的な更新であろうとも、操作に起因する状態の観測可能な全ての変更
SIDE-EFFECT-FREE FUNCTION
- See function
STANDALONE CLASS
- システムの原始的で基本的なライブラリを除いてその他を参照しない、理解されテストされ得るようなクラス
stateless
- その要素の歴史に関係なく、その操作のいずれかをclientが使うことができるような、設計要素のプロパティ
- ステートレスな要素は、グローバルにアクセス可能な情報を使用するかもしれず、また、(副作用である、あるいは副作用を持つかもしれない)グローバルな情報を変更さえするかもしれないが、その振る舞いに影響を与える私的な状態は持たない
strategic design
- そのシステムの多くの部分に適用される、モデリングと設計の意思決定
- そのような決定はその全体のプロジェクに影響を与え、チームレベルで決定されなければならない
supple design
- 期待された結果を確実にもたらすような、明白で柔軟な表現を生むため、深いモデルに本来備わっているパワーをclient Developerの手にもたらすような設計
- 等しく重要だが、新しい洞察を提供するために、その設計自体を実装者が形作りと再形成しやすいようにする、"同じ"深いモデルをそれは利用する
UBIQUITOUS LANGUAGE
- そのドメインモデルの周辺で構成され、そのソフトウェアと共にあるチームの全ての活動を関連づけるため、全てのチームメンバーによって使用される言語
unification
- それぞれのチームが曖昧でなく、どのルールも矛盾していないようなモデルの、内部的な一貫性
VALUE OBJECT
- 幾らかの特徴的あるいは属性を描写するが、独自性の概念を持たないオブジェクト
WHOLE VALUE
- 単一の、完全な概念をモデル化するオブジェクト
- 投稿日:2020-02-05T00:50:10+09:00
なぜ僕たちはサーバレスでJavaを諦めTypescriptを採用したか
この記事は個人ブログのうち技術に関する箇所のみを抜粋した転載です。
なぜ僕たちはサーバレスでJavaを諦めTypescriptを採用したか -Junks, GC cannot sweep-またブログには書いたのですが、諸事情により先に英語版が存在します。
こちらも書いたのは僕なので、剽窃などではないことはご了承ください。[元記事]: Why we replaced Java with Typescript for Serverless in dev.to
はじめに
サーバレス(serverless)は昨今もっとも注目を集める設計手法の一つで、おそらく多くの開発者が大なり小なり自分のプロダクトに応用し始めているのではないでしょうか?
僕自身、完全にサーバレスに魅せられてしまい、昔ながらの自分でサーバやミドルウェアを管理しながら運用するみたいな世界には戻れる気がしません。
そもそもスケーリングや分散可能性をきちんと考えて設計されたアプリケーションであれば、旧来のサーバーアプリケーションの機能から受けられる恩恵も比較的少なくなりますし、サーバレスに切り替えるデメリットはそこまでありません。
最近は設計に関して相談された時は、必ずサーバレスの話題を出してみることにしています。さて、サーバレスは既存の開発手法とは大きく異なるため、今持っている知識を刷新し、既存の手法や技術スタックを見直しながら開発していく必要があります。
見直しというからには、開発基盤として何の言語を使うかも、当然ながら見直さなくてはいけない技術スタックの対象になります。タイトルにある通り、最終的に僕たちはTypeScriptを採用し、およそ一年半開発・メンテナンスを行ってきました。
そして一年半経った今、あくまで個人的な感想ではありますが、TypeScriptは僕たちが期待した以上に成果を出してくれました。そこでこの記事では、以前使用していた言語にどんな問題があったのか、そしてなぜTypeScriptに切り替えたことでどんな恩恵があったのかをこの記事では解説していきたいと思います。
なぜJavaを諦めなくてはならなかったのか
さて、なぜTypeScriptを採用したかについて話す前に、まずなぜ以前使用していた非常に強力な言語であるJavaを諦めなくてはいけなかったかについてお話ししたいと思います。
先に述べておきますが、僕は結構なJava好きです。なんなら初めて触った言語もJavaでした。
JVMに関してもそれなりに勉強して、その神がかったランタイムの仕組みにかなり感銘を受けています。(てか多分作ったやつは神)
なので、どこかの大学生のようにJavaがクソだとかレガシーだとか使い物にならんとか、この記事でそういうことを言うつもりは一切ありません。
また、そういったコメントもあまり嬉しくないです。あくまでサーバレスという仕組みにJavaがあまり合わなかっただけなので。
その点だけはご了承いただければ幸いです。
なぜJavaを諦めなくてはならなかったのか
さて、なぜTypeScriptを採用したかについて話す前に、まずなぜ以前使用していた非常に強力な言語であるJavaを諦めなくてはいけなかったかについてお話ししたいと思います。
僕たちのサービスでは、サーバサイドはサービス設立当時から基本的にJavaだけで書かれていました。
当然ながらすでにJavaには多くの利点があり、特に
- プラットフォームフリー
- よくできたJITコンパイル
- やばいGC
- よく構成された文法
- 静的型付け
- 関数型サポート(最近は特に)
- 多様なライブラリ
- 信頼できるコミュニティ(Oracleではなく、開発者の方)
などなど挙げればきりがありません。
しかし、AWS Lambda上でコードを試していて気づいたのですが、Javaはあまりサーバレスに向かないことがわかりました。
理由としては以下のことが挙げられます。
- JVMの起動オーバーヘッドが大きい
- Springフレームワークを使用してるとさらにエグくなる
- 最終的なパッケージアーカイブがでかすぎる(でかいのは100MB以上)
- 関数が増えてくるとWebフレームワークなしでリクエストを捌くのがきつくなる
- コンテナは30分程度しか走らないため、G1GCやJITなどのJavaの利点が生かせない
- Lambdaは基本的にEC2上に建てられたAmazon Linuxのコンテナで動くため、プラットフォームフリーは関係ない。 (欠点ではないけど)
上述の点は全てなかなかに厄介ですが、今回は特に厄介だった問題についてもう少し書いてみたいと思います。
Cold Startがまじで厄介
一番厄介だったのは、圧倒的にCold Startのオーバーヘッドです。おそらく多くの開発者の方々もこいつに悩まされているのではないかと思います。。。
僕たちはコンピューティング基盤としてAWS Lambdaを使っていたのですが、AWS Lambdaはユーザからのリクエストが来るたびに新しいコンテナを立ち上げます。
一度立ち上がってしまえば、しばらくは同じコンテナインスタンスを再利用してくれるのですが、初回起動時にはJavaのランタイムに加え、フレームワークで利用されるDIコンテナやWebコンテナなども全て初期化する必要があります。
さらに言えば、一つのコンテナで処理できるのはあくまで一つのリクエストのみで、複数のリクエストを処理することはできません。(内部で数百のリクエストスレッドをプーリングしてたとしても同じです。)
つまりどういうことかというと、もし複数のユーザがリクエストを同時に送ってきた場合、Lambdaは起動中のコンテナの他に、別のコンテナを起動しなくてはいけなくなるということです。
通常、僕たちはどの時間に具体的に何軒のリクエストが同時に来るかを事前に予測することはできません。
つまり、何らかの仕組みを作ったとしても、事前に全てのLambdaをhot standbyさせることはできないのです。これは必然的にユーザに数秒から10秒以上の待機時間を強制し、ユーザビリティを著しく下げることにつながります。
こんな感じでCold Startがえげつない事を理解した僕らは、これまでの数年かけて書かれた技術スタックを捨てて、他の言語を選択することを決めました。
なぜTypeScriptを選んだのか
めちゃくちゃ恥ずかしい話なのですが、正直Lambdaでサポートされている全ての言語をきちんと精査・比較して決めたわけではないのです。
ただ、正直な話、状況的にTypeScript以外の選択肢はなかったのです。まず第一に、動的型付け言語は外しました。長期に渡ってスキルのバラバラな開発者によって保守・メンテ。拡張されるコードなので、動的型付けはあまり使いたくありません。
したがって、PythonとRubyに関してはかなり序盤で選択肢から外れました。
C#とGoに関しても、現在ほとんどのチームがJavaをメインに開発しているサービスなので、既存言語とあまりかけ離れた言語を使うと新規開発者のジョインが難しくなると判断し、今回は見送られました。
もちろん、昨今この二大言語は非常に注目度が高く、特にGolangに関しては徐々にシェアを伸ばしつつあるのは知っています。
しかし、急いでサーバレスに開発を移す必要があったため、僕たち自身のキャッチアップの時間も考慮し、見送らざるを得なかった感じでした。TypeScriptの利点
という事で、僕たちはTypeScriptを使い始めたわけです。
TypeScriptの利点を挙げるとしたらこんな感じでしょうか?
- 静的型付け
- 小さいパッケージアーカイブ
- ほぼ0秒の起動オーバーヘッド
- JavaとJavaScriptの知識が再利用できる
- NodeJSのライブラリやコミュニティが使える
- JavaScriptと比べても関数型プログラミングがしやすい
- ClassとInterfaceにより構造化されたコードが描きやすい
長期に渡って運用・開発が行われるプロジェクトにおいて静的型付け言語がどれだけ大きな恩恵を与えるかは今更語るまでもありませんので、ここには書きません。
ここでは主に、TypeScriptのどういった点がサーバレス開発によく馴染んだかについて書いていきたいと思います。
静的型付け以外にもTypeScriptを使う利点は非常に大きく、小さいパッケージと小さい起動オーバーヘッド
おそらくサーバレスでTypeScriptを使う利点という観点からいうとこれが一番大事だった気がします。(なにせ他のメリットはほぼTypeScript自体のメリットなので・・・)
先ほど触れた通り、JavaはJVM本体やフレームワークが利用するDI/Webコンテナなどの起動にかかるオーバヘッドが非常に大きいです。
加えて、Javaの性質上、AWS Lambdaで流すには以下の弱点があります。マルチスレッドとそれを取り巻くエコシステム
マルチスレッドは非常に強力な機能であり、事実として僕たちはこの機能のおかげで多くのパフォーマンス問題を解決してきました。
JVM自体もガーベージコレクションやJITコンパイルにおいて、デフォルトでにマルチスレッドを活用してあの素晴らしいランタイムを実現してます。
(詳しくはG1GCやJIT Compileを参照)しかし、起動時間単体で見ると、アプリケーションに使用する全てのスレッドを立て終わるまでに、100ミリ秒から数秒かかっていることがわかります。
この機能自体は旧来のいわゆるクラサバモデルでEC2上で動くアプリケーションならほぼ無視できるオーバーヘッドですが、LambdaなどのFaaS上で動くサーバレスアプリケーションでは決して無視できません。TypeScriptはNodeJSベースであり、基本的にシングルスレッドです。非同期は別スレッドや別プロセスではなくあくまでジョブキュー、イベントループなどで管理されます。
したがって、ほとんどのライブラリやフレームワークは起動時にスレッド展開をする必要はありませんし、ランタイムを起動するためのオーバーヘッドもほとんどかかりません。
巨大なパッケージアーカイブ
サーバレスにおいてソースコードのパッケージアーカイブは、基本的に小さいに越したことはありません。
Lambdaのコンテナは起動時、AWSにより管理されたソースコード用のS3バケットからソースをダウンロードし、コンテナに展開します。
S3からのダウンロード時間は通常非常に短時間ですが、100MBや200MBとなると無視はできません。
NodeJSのパッケージは基本的にJavaに比べて小さくなります。
正直なんでそうなるかに関しては不勉強でわかっていないのですが、以下の理由が関係してるんじゃないかなと思ったりします。(もしこれやでっていうのをご存知の方はコメントで教えてください)
- Javaのフレームワークやライブラリは包括的なものも多く、本来使いたい機能に必要ない依存性を引き込んで来るが、JavaScriptは目的特化のライブラリが多く、必要最低限に依存を抑えられることが多い。
- JavaScript(NodeJS)は1ファイルに複数のmoduleを書くことができ、それでいてメンテもしやすいが、Javaにおけるメンテナンス性の重要なポイントはファイル分割とパッケージ管理のためソースが肥大化しやすい。
実際Javaで書いていた時は最大で200MB以上のパッケージができることもあったのですが、NodeJSに変えてからは35MB程度で済んでいます。
この巨大なパッケージアーカイブは、僕たちがSpringで書かれた旧来のコードを再利用しようとしたのが大きな原因なのですが、実際これらのいらないフレームワークを除いて最適化したコードでも、どうしても50MBは必要になってしまいました。
JavaScriptの知識やエコシステムを利用できる
僕たちもWeb開発者のため、基本的にフロントエンドも書きます。したがって、ある程度のJavaScriptやNodeJSに関する知識は蓄えていました。
jQuery全盛時代からReact/Vueのようなモダンフレームワークでの開発までを通じて、言語的な特徴はある程度抑えていましたし、どうやって書けばいいコードになるかもある程度理解してるつもりです。
TypeScriptはJavaScriptの拡張言語であり、最終的にはJavaScriptにトランスパイルされます。
多くの文法やイディオムはJavaScriptから受け継がれているので、実際それほど準備期間を要さずにサービス開発を始められました。
加えて、ほとんどのメジャなNodeJSのライブラリはTypeScriptに必要な型定義を提供しているので、NodeJSのエコシステムのメリットをそのまま享受できたのも非常に嬉しいポイントでした。
関数型の実装が非常にしやすい
昨今の技術トレンドを語る上で、関数型の台頭はなくして語ることはできません。
関数型の実装はその性質上、シンプルでテスト可能で危険性の低い安定したコードを書くのに大きく寄与します。特にAWS Lambdaの場合、常に状態を外部化するコードが求められるため、状態や副作用を隔離する関数型の実装は非常に相性が良く、メンテもしやすくなります。
そもそも、jQueryの生みの親であるJohn ResigがJavaScriptニンジャの極意で語ったように、JavaScriptはそもそも関数型のプログラミングをある程度サポートしています。
JavaScriptにおいて関数は第1級オブジェクトであり、jQueryも実は関数型で書かれることを期待して作られています。しかし一方で、動的型付け言語で関数型のコードを書こうとすると、時折非常にめんどくさい事になることがあります。
例えば、プリミティブ型だけで表現できる関数は非常に限られますし、返り値や引数にObjectを取るのは普通に結構危険です。しかしTypeScriptでは引数や返り値に型を指定することができます。
加えて、以下のTypeScriptの機能は、僕達の書く関数の表現の幅を広げ、より安全でシンプルなコードを書くのに寄与してくれます。
- Type: 共通に使用される型をコンテクストに合わせて型付けできる。(stringとUserIdやPromiseとResponseなど)
- Interface/Class: Objectで表現されるの引数や返り値をコンテクストにあった型で表現できる。
- Enum: よもや語る必要もあるまい
- Readonly: 自分で作成した型をImmutableに出来る
- Generics: 関数のインターフェイスの表現の幅が広がる
TypeScriptは他にも関数型で書こうとした時に非常に便利な機能をいろいろ備えていますが、全てをここであげることはしません。(っていうか、結構JavaScript由来のものが多い)
関数型とTypeScriptに関する記事は今後どこかで書いていきたいなと思っています。
Javaで学んだBest Practiceを再利用できる
TypeScriptの文法を学ぶと、かなりJavaやScalaに似通った記述ができることに驚きます。
僕たちはそもそも、それなりの期間をJavaで開発してくる中で、Javaにおけるいいコードのお作法をある程度蓄積してきました。
ClassやInterfaceをどう設計すべきか、enumはどう使うと効率的か、Stream APIはどう書くと保守性が上がるかなど、蓄積してきたノウハウはそれなりに捨てがたいものがありました。TypeScriptはインターフェイスやクラスに加えて、アクセスモディファイアやreadonly(Javaでいうfinalのプロパティ)をサポートしており、僕たちは割とさらっとJavaで育んだノウハウをそのまま導入することができました。
これにはオブジェクト指向的なベストプラクティスやデザインパターンなども含まれます。
(関数指向とオブジェクト指向は二律背反ではないので、プロジェクト内で同時に使用されても問題ないと考えています。個人的には。)もし僕たちがやや文法が独特なPythonやRubyを採用していたとしたら、より品質の高いコードを書くためのプラクティスをどうこの言語に応用すべきかに多くの時間を費やすこになったことかと思います。(それも楽しいんですよ、知ってます、ただ時間がね。。。。)
当然ながら全ての設計やロジックをコピペしたわけではないですし、むしろ大半のコードを書き直ししました。
ただ、おおよその部分をスクラッチで書き直した割に、それなりの品質でそれなりの短期間で書き直しが終わったんだよということは特筆しておくべきかと思います。結論
僕たちもまだまだTypeScriptに関しては初心者といっていいレベルでまだまだ勉強が必要ですが、すでにそのメリットは全力で享受しています。
今聞かれれば、Golangもいいなあとか、MicronautとGraalVMとかも面白そうだなあとか、もっと他の選択肢もあったかもなあとか考えたりもするのですが、現状TypeScriptには非常に満足しており、最善の選択肢の一つではないかと信じています。
もちろん、処理遅いけどバッチ処理どうすんねんとか、並行処理とか分散処理どうすんねんとか、ワークフローどう設計すんねんとか、API Gatewayのタイムアウトどうハンドルするねんとか、データの一貫性どう担保すんねんとか、サーバレスやTypeScriptに起因する問題にはたくさんぶち当たりました。
ただ、それはそれでギークとして非常に楽しく取り組んできて、すでにいくつかのこれが今の所best practiceじゃね?っていう方法もいくつか見つけました。(これはのちのち記事にしていきたい。)
もし今Javaでサーバレスに取り組んでいて、サーバレスくそやん、きついやん、やっぱ普通にサーバ欲しいわってなっている方がいたら、ぜひTypeScriptも試してみてください。想像する以上に生産性出るんじゃないかなぁって期待してます。
長文おつきあいいただきありがとうございました。何かコメントや訂正があればぜひお願いします。