インドカレーファンクラブ

パソコン、カメラ

【.NET5】僕が有給を犠牲に解釈した何とも言えないDDD+CQRS実装サンプル

読まなくていいあらすじ

DDDだ!CQRSだ!という主張は多く見るけど、自分のやりたいことに即した実装サンプルがなかなか見つからなかった

僕はDDDというか、Repositoryパターンを組み込んだDDDにCQRSを組み込みたかった
コマンド・クエリ間でデータソースは共通のものとし、イベントソーシングは不要

実装例があったとしても、Repositoryパターンがないとか、イベントソーシングの話がメインだったりとか、DTO(ドメインオブジェクトをその外に露出させないようにするための射影したようなオブジェクト)の使い方が好きじゃなかったりとか

はじめに

読者想定

DDDだ!CQRSだ!という主張は多く見るけど、自分のやりたいことに即した実装サンプルがなかなか見つからなかった

という感じで、DDDとCQRSの概念をざっくり理解している人

言い訳

色々間違ってそうな気がするので何かあったら教えてください
実案件で作ったようなものではないのであくまで参考程度に...

全体的に参考にしたもの

https://nrslib.com/bottomup-ddd/

https://www.amazon.co.jp/gp/product/479815072X

https://little-hands.booth.pm/items/1835632

https://github.com/little-hands/ddd-q-and-a/

https://qiita.com/wasimaru/items/5ad064aac7fc6f929cd1

https://omdwn.hatenablog.com/entry/2021/07/09/221616

逆に言うと、以下はまだ読んでいない(恥ずかしい限り。いつか読みます)

https://www.amazon.co.jp/dp/B00GRKD6XU/

https://www.amazon.co.jp/dp/B00UX9VJGW/

つくるもの(つくるドメイン)

図書館の蔵書管理的なものをつくる

ドメイン

色々端折った図の通り

f:id:omdwn:20210720235058j:plain

  • Books: 本(の情報)。実際はauthor_idとかを持たしていくことになると思う
  • BookStocks: 本の在庫。BooksとBookStocksは1:n
  • Users: ユーザ。UsersとBookStocksは1:n

ユーザは本(Books)を借りるというか、在庫(BookStocks)を借りるというようなイメージ

テーブル

テーブルに値を入れるとこういう感じ

Users

id first_name family_name
001 John Petrucci
002 Jordan Rudess
003 Mike Portnoy
004 Tony Levin

Books

id name
978-0008322069 1984 Nineteen Eighty-Four
978-0141033006 The Day of the Triffids

BookStocks

id book_id rental_user_id
1 978-0008322069 001
2 978-0141033006 NULL
3 978-0141033006 NULL
4 978-0141033006 NULL

貸し出し情報のみのトランザクションテーブル的なものが必要かもしれない...

つくったもの

https://github.com/0mmadawn/DDD_CQRS_Sample

アーキテクチャ

ソリューション

ソリューション(sln)的にはこんな感じ
層ごとにプロジェクト(csproj)を分けている

なんとか層っていうのはレイヤードアーキテクチャでいうところの名前

  • TryDDD.sln
    • LibraryDomain.csproj(ドメイン層)
    • LibraryApplication.csproj(アプリケーション層)
    • LibraryInfrastructure.csproj(インフラストラクチャ層)
    • TryDDD.csproj(プログラム全体のエントリポイント)

フォルダ

フォルダはこんな感じ

├─LibraryDomain
│  ├─Models
│  │  ├─Books
│  │  ├─BookStocks
│  │  └─Users
│  └─Services
├─LibraryApplication
│  ├─Commands
│  │  ├─Handlers
│  │  └─Requests
│  └─Queries
│      ├─Handlers
│      ├─RepositoryIf
│      ├─Requests
│      └─Results
├─LibraryInfrastructure
│  ├─Books
│  ├─BookStocks
│  ├─DataModel
│  ├─Queries
│  ├─Shared
│  └─Users
└─TryDDD

説明を添える

├─LibraryDomain:ドメイン層
│  ├─Models:
│  │  ├─Books:ドメインオブジェクトとか仕様オブジェクト + レポジトリやファクトリのIF
│  │  └─...
│  └─Services

├─LibraryApplication:アプリケーション層
│  ├─Commands:CQRSのC
│  │  ├─Handlers:コマンドを呼ぶためのエントリポイントみたいな
│  │  └─Requests:コマンド用のパラメータクラス(コマンドオブジェクト)
│  └─Queries:CQRSのQ
│      ├─Handlers:クエリを呼ぶためのエントリポイントみたいな
│      ├─RepositoryIf:クエリ用レポジトリのIF
│      ├─Requests:クエリ用のパラメータクラス(コマンドオブジェクト)
│      └─Results:クエリの実行結果用クラス(DTOみたいなもの)

├─LibraryInfrastructure:インフラストラクチャ層
│  ├─Books:レポジトリやファクトリの実装
│  ├─DataModel:データモデル。ドメイン層のドメインオブジェクトをデータソースに依存させないための射影的なもの
│  └─Queries:クエリ用レポジトリの実装

└─TryDDD:今回のプログラム群をテストで叩く全体のエントリポイント

ここから各層について細かく説明する
けど、概ね言葉が分かる人はもうgithubのレポジトリ見たほうが早いかも

ドメイン

├─LibraryDomain:ドメイン層
│  ├─Models:
│  │  ├─Books:ドメインオブジェクトとか仕様オブジェクト + レポジトリやファクトリのIF
│  │  └─...
│  └─Services
├─LibraryDomain
│  │  LibraryDomain.csproj
│  │
│  ├─Models
│  │  ├─Books
│  │  │      Book.cs
│  │  │      BookId.cs
│  │  │      BookName.cs
│  │  │      IBookRepository.cs
│  │  │
│  │  ├─BookStocks
│  │  │      BookStock.cs
│  │  │      BookStockId.cs
│  │  │      IBookStockFactory.cs
│  │  │      IBookStockRepository.cs
│  │  │      RentalLimitCountSpecification.cs
│  │  │
│  │  └─Users
│  │          IUserRepository.cs
│  │          User.cs
│  │          UserId.cs
│  │          UserName.cs
│  │
│  └─Services
│          BookService.cs
│          BookStockService.cs
│          UserService.cs

ここはDDD的に割と素直な作りで特に言うことがない
ここにはCQRSの話は出てこないしね

ご存知の通りだと思うけどレポジトリやファクトリのIFがいるのは依存性逆転の原則のやつ

ドメインオブジェクトの中にはすごくかっこいい実装があるが、この形容は自画自賛ではない
この人の実装を参考にした

https://takap-tech.com/entry/2021/02/03/232823

内心で集約の切り方がいまいち怪しい気がしている
あと他の集約にIdを持たせたりしたほうがいいのか?とか...

また、申し訳程度に仕様オブジェクト( RentalLimitCountSpecification )を作ったけど微妙な気がしている

あとほんの少しだけテストを書いたけど、今回大変なので凍結した

├─LibraryDomainTest
│  │  LibraryDomainTest.csproj
│  │
│  ├─Models
│  │  └─Books

アプリケーション層

├─LibraryApplication:アプリケーション層
│  ├─Commands:CQRSのC
│  │  ├─Handlers:コマンドを呼ぶためのエントリポイントみたいな
│  │  └─Requests:コマンド用のパラメータクラス(コマンドオブジェクト)
│  └─Queries:CQRSのQ
│      ├─Handlers:クエリを呼ぶためのエントリポイントみたいな
│      ├─RepositoryIf:クエリ用レポジトリのIF
│      ├─Requests:クエリ用のパラメータクラス(コマンドオブジェクト)
│      └─Results:クエリの実行結果用クラス(DTOみたいなもの)

第1階層をコマンドとクエリをフォルダ単位で分けた
第1階層については、コマンドで特に言えることだけど最初に集約単位(例えばBooks)でフォルダを切るか迷った
けど結局コマンドもクエリも集約を跨ぐ事が多々ある気がしてやめた

├─LibraryApplication
│  │  LibraryApplication.csproj
│  │
│  ├─Commands
│  │  ├─Handlers
│  │  │      DeleteUserHandler.cs (これ使い忘れた)
│  │  │      LendBookHandler.cs
│  │  │      RegisterBookHandler.cs
│  │  │      RegisterUserHandler.cs
│  │  │
│  │  └─Requests
│  │          LendBookCommand.cs
│  │          RegisterBookCommand.cs
│  │          RegisterUserCommand.cs
│  │
│  └─Queries
│      ├─Handlers
│      │      GetAllBooksHandler.cs
│      │      GetAllUserHandler.cs
│      │      GetUserHandler.cs
│      │
│      ├─RepositoryIf
│      │      IQueryRepository.cs (本当はクエリ毎にレポジトリ切ったほうがいいかも)
│      │
│      ├─Requests
│      │      GetUserQuery.cs
│      │
│      └─Results
│              GetAllBooksResult.cs
│              GetAllUsersResult.cs
│              GetUserResult.cs
│

アプリケーションサービス(≠ドメインサービス)と呼ばれるものをコマンド・クエリのハンドラに分けたような形
厳密には「HandlerにRequestsフォルダのファイル(クエリ用パラメータクラス)を渡す」という作りで、これは上述したMediatRの実装を参考にした
※でも今回は非同期なAPIでもないのでMediatRは使っていない

https://qiita.com/wasimaru/items/5ad064aac7fc6f929cd1

ちょっと分かりにくいかもしれないので、具体的なコードで書くと、こんな感じに呼び出す

// 本当はDIとかする

// コマンド
var commandHandler = new HogeHandler(hogeRepository);
var commandRequest = new HogeCommand(name: "テスト")
commandHandler.Handle(commandRequest);
// クエリ
var queryHandler = new FugaHandler(hogeRepository);
var queryRequest = new FugaQuery(id: 1);
var result = queryHandler.Handle(queryRequest);

クエリ側にはRepositoryIF(リポジトリのIF)、Resultsがいるが、これがコマンドクエリの役割の違いの最たるもの
コマンド側は基本的に集約単位に存在するリポジトリを使ってCRUDのRead以外を行う
その際のWriteModelはDTO(インフラ層在中)を使う
クエリ側はユースケースに応じて独自のReadModelをResultsフォルダに用意し、独自のリポジトリを使うためここにRepositoryIFを設けている

今回に関して、それぞれの命名については少し難があって、 RegisterBookHandler はともかく、 GetAllBooksResult なんかは、そのユースケースが見えてこないのでよくない
例えば GetAllBooksForIndexView みたいな具体的な名前の方がいい
(と、Greg Youngは言っている)

インフラストラクチャ層

├─LibraryInfrastructure:インフラストラクチャ層
│  ├─Books:レポジトリやファクトリの実装
│  ├─DataModel:データモデル。ドメイン層のドメインオブジェクトをデータソースに依存させないための射影的なもの
│  └─Queries:クエリ用レポジトリの実装
├─LibraryInfrastructure
│  │  InitSqlite3.cs (今回SQLiteを使うので最初にそのテーブルを作成したり...などを行う)
│  │  LibraryInfrastructure.csproj
│  │
│  ├─Books
│  │      BookRepository.cs
│  │
│  ├─BookStocks
│  │      BookStockFactory.cs
│  │      BookStockRepository.cs
│  │
│  ├─DataModel
│  │      BookDataModel.cs
│  │      BookStockDataModel.cs
│  │      UserDataModel.cs
│  │
│  ├─Queries
│  │      QueryRepository.cs (本当はクエリ毎にレポジトリ切ったほうがいいかも)
│  │
│  ├─Shared
│  │      RepositoryBase.cs (各レポジトリの基底クラス。コネクションを管理してDisposeするための実装。実装微妙かも)
│  │
│  └─Users
│          UserRepository.cs

DataModelは

ドメイン層のドメインオブジェクトをデータソースに依存させないための射影的なもの

と書いたけど、今回分かりやすくいうとDapperでテーブルと1:1で結びつけるためのオブジェクト
※非DDD文脈ではEntityと呼ばれることが多い気がする

リポジトリやファクトリは集約単位(Books, BookStocks, Users)のフォルダの中にいて、複雑なRead、つまりクエリの処理がQueriesのQueryRepository.csにまとめられている

ユーザインタフェース(プレゼンテーション層)

これらのドメインを利用する層
.NET MVCでいうとControllerとかその辺

今回はコンソールアプリケーションなので薄い

└─TryDDD
    │  Program.cs
    │  TryDDD.csproj

特に言うことはないけど、一応DIをしている

上述したリポジトリのコネクションのDispose周り(IDisposable)はDIに任せておけばOKとのことなのでそれに従った
果たして本当にDisposeされるのか確認していないので何かあったらごめんなさい

利用イメージ

ユーザインタフェース(プレゼンテーション層)の実装を見るのが一番早い

「つくるもの(つくるドメイン)」の「テーブル」で書いた状態を作るような作業をしている

https://github.com/0mmadawn/DDD_CQRS_Sample/blob/main/TryDDD/Program.cs#L74

おわりに

という感じに作ってみたけど正しいのか正しくないのかわからない

個人的にはこの辺はかなり怪しい
一番入り口のとこじゃん!という気もするけど...

内心で集約の切り方がいまいち怪しい気がしている
あと他の集約にIdを持たせたりしたほうがいいのか?とか...

また、申し訳程度に仕様オブジェクト( RentalLimitCountSpecification )を作ったけど微妙な気がしている

あとテスト書けてないのでその時に破綻している箇所が出てくるかも
新規にIF作ったりすることになりそう

アクセサももっと工夫できるんじゃないかとか

とはいえ一番実装例がなくて困っていたCQRS周りの分割の実装はそこそこどうにかなったかも

会社に行っていない期間にせっせと書いたけど、この手の話は人と会話しないと作れないように思った

転職して次の会社でこの手の話が出来たらもうちょっとどうにかしたい

【DIY】2x4とダボレールでモニタを壁掛けしよう(静岡県へ愛を込めて)

こんな感じ

思想

ダボレールを利用することで、「今は座椅子+ローテーブルに合わせたモニタ位置だけど、いつか普通の机に変え、モニタの高さを変えたくなるかも?本棚欲しくなるかも?」という将来の想像に対しても拡張性を持たせることができる
※ダボレール無しで普通に棚受けを固定すると、当然高さが可変でなくなるが死ぬほど楽に作れる

モニタは上下左右に傾斜をつけられ、回転もする

2x4を使うことに意義がある。木材は素晴らしいものなので不要になっても何かの使い回せる
引越する時に急遽で使っていた2x4を全部分解して持って来たため、今でも合計7m分くらいの材木が部屋の隅に立てかけられている

そしてオタクっぽいメタルラックを避けることができる(オタクなのにね)

だいたいの図

※ 図や以降の説明に記載される「渡し木」という表現は僕特有のものであり、正確な言葉がわからないがなんとなくで意味を汲んでほしい

必要なもの

2x4材(支柱)

どこで買ってもいい
気が向いたら塗装してもいい

ホームセンターなどで部屋の高さに合わせてカットしてもらうといい
ディアウォールと込みでの高さになるので注意!(後述)

流石に支柱1本でモニタを支えるのは不安だし最低でも2本あるといいと思う

ディアウォール( or ラブリコ)

これで2x4を天井と床とで突っ張る

ディアウォール-商品紹介|DIAWALL公式サイト

読めば分かる通り、天井の高さ=2x4の長さ-45mm(ディアウォール分の長さ) ということになっている

参考:初めてのDIYにおすすめ!ディアウォールで始める棚作り│ブリコジ

因みにラブリコはディアウォールの類似商品

ラブリコ(LABRICO)公式ブランドサイト| LABRICO DIY Brand Official Website

僕はディアウォールしか使ったことがない
なんかこっちのほうが見た目安定している気がするし、何より名前がかっこいい

ダボレール・ダボレール用ビス

ダボレールについて僕はロイヤルのモノを使った

Amazon | ロイヤル ASF-1 チャンネルサポート 900mm シングル | 棚受

詳細な規格はコチラ

品番一覧 | ROYAL WEB CATALOGUE PLUS

そのダボレールを2x4に固定するビスについてはこのレビューが大変参考になる!

手軽にDIY

今回は失敗したくなかったのでAT-Pシリーズのビスを買った

僕の構成では2x4の幅広の薄い面(39mm)にダボレールを取り付け、更にダボレールの厚さ(11mm)を考慮する必要がある
ということで50mm厚は確保できるのだが何mmのビスを取り付けたのか覚えていない...
多分30mm...

Amazon.co.jp: ロイヤル Aタッピング AT-P 30mm 50本入 クローム: 産業・研究開発用品

ダボレール用棚受け+ビス

棚受けはダボレールとメーカーは揃えたほうが無難だろう
メーカーというか規格が揃っていれば何使っても問題はない

僕はこれを使った(amazonになさそう?)

品番一覧 | ROYAL WEB CATALOGUE PLUS

この棚受けに渡し木を載せることになる
僕のやり方で渡し木の2x4を載せる場合、フックに近い側(壁側)の穴のビス止めだけで2x4を支える

ビスは適当でいいだろう
僕は工事現場みたいな部屋に住んでいるのでそこらに転がっていたものを使った

壁面モニタアーム

他にも選択肢はあると思うがこれを使った

シリーズのCR-LA301は角度固定、CR-LA302は上下角度調節可能、CR-LA303は上下左右角度調節可能となるのでお好みで

https://www.amazon.co.jp/dp/B000S73VEC

CR-LA303【モニターアーム】壁面へ取付けるタイプの液晶モニターアーム。液晶テレビの取付けにも対応。|サンワサプライ株式会社

正直、結構奥行きがあって邪魔になることが多いかも
でもこれが一番安くて...

これはCR-LA302の奥行き

このモニタアーム取付パーツを付属のビス(タッピングボルト)で渡し木に止める時に下穴を空けておく必要があり、その際にドリルが要る
ま、皆さんの家庭には当然あると思うけど!
上記のサンワサプライのページに組立説明書があるので規格含め見ておくといい

2x4材(渡し木)

長さは部屋・モニタに合わせていい感じに
この長さについては後述するが注意点がある

正直2x4材じゃなくていい。けど安かったので僕は2x4にした
2x4と壁面モニタアームを使い、その幅広の面にモニタアーム取付パーツを付けたんだけど、なんと奥行き(39mm)がなさすぎてそのビスが飛び出る

見なかったことにした

もし他の角材を使うなら支柱に負担をかからない軽い材質で下穴が開けやすいものがいいと思う

モニタ

適当に
ま、通常の家庭であれば2, 3枚はあると思うけど

可能な限り左右で同じものを使った方がいい!

あるといいもの

レーザー距離計

https://www.amazon.co.jp/dp/B06Y63L5KV/

便利

防音材・遮音材

詳細は割愛するけど、もし僕のように渡し木の上にスピーカーを置いたりして、尚且隣人が住んでいるなら、防音材とか遮音材を壁と支柱の間に仕込むことを検討するといい

僕は申し訳程度に吸音材だけ仕込んだ
効果があるのかわからないけど、真心は込めたからか隣人からは今のところ怒られていない
真心には遮音効果がある

https://www.amazon.co.jp/dp/B00B58KXYO/

因みにこの固定方法だけど、上手いことやると壁と支柱の挟み込みだけで無理やり固定出来る
そうでない場合には適当に長いピンを刺して固定する
賃貸の壁に小さい穴が開くことになるが、パテでも詰めれば誤魔化せるだろう
(労働者階級は資本家階級に対して多少なりとも図太く向かっていく心持ちが重要だと思う!)

気をつけること

支柱とディアウォールと天井の高さ

これは上述した通り

全体の幅

高さはそうそう間違えないと思うんだけど、幅については計算ミスって足りなかった!という事態に陥りがちなので余裕をもった方がいい!
設計の時に各パーツの規格(サイズ)を確認しておくのは重要

僕のように幅計算を間違えて作り直しになったり、渡し木からモニタアーム用のビスが飛び出たりしないように...

ダボレールの支柱への取り付け

これが一番大変かも!!

結構丁寧に採寸してやらないと歪んだり支柱の辺に対して平行でない状態で固定することになる
平行に取り付けられない場合は斜めになる訳だけど、僕のように1500mmと長いものを利用した場合、更にグニョンと(極端に言えば尺取り虫のように)曲がった状態で固定する羽目になる
そうなるとどうなるかというと棚受けの取り付け・取り外しがしにくくなる

棚受けがダボレールにハマらないなら支柱そのものや棚受けの固定位置をずらせばどうにかなる訳だけど、あんまり良くないので気をつけたい

モニタの幅は左右傾斜時のことも考慮する

傾斜をつけるというか以下のようなイメージ

■ 傾斜なし
── ── ← モニタ
  ○   ← モニタに向かう自分

■ 傾斜あり(極端)
/ \
  ○

あんまり幅をカツカツに設計しすぎると、デュアルディスプレイでハの字型の傾斜をつけた際にモニタ同士が干渉して満足に傾斜をつけられなくなる
僕はなった

壁面モニタアームの性質上、付け直すのがそこそこ手間なので気をつけたほうがいい
三角関数が真っ当にできる人間ならこんなミスを犯すことはないだろう

あんまり上の方に重いものを取り付けない

重心の問題で支柱が崩壊しても責任がとれない...

拡張性を求めない人はダボレールを使う必要はない

大変だし...

設計思想の参考になった先達

mazgi.com

mazgi.com

この人の方法にしようか迷ったけど、モニタの固定方法があんまり好みでなかった
でもこっちのほうが左右に動かせるしかなり迷った

yanoshi.hatenablog.jp

99diy.tokyo

ダボレールのアイディアはこちらから

あとがき:DIYについて

静岡県には大手ローカルホームセンターのジャンボエンチョーというものがある
また地方ローカルではあるが彼らの手掛ける「エンジョイDIY」という番組が放送されていた
※僕に惜しまれながらも2017年放送終了

その放送時間枠は日曜朝、おジャ魔女どれみデジモン・ワンピースあたりの前枠であり、つまるところ県民は全員DIYを嗜む程度に経験していると言うと流石に過言だが他県民より見識は深めているだろう

小学生の僕はジャンボエンチョーを大層気に入っており、ディズニーランドに行くくらいならその分のお金を持たせてジャンボエンチョーに一日いさせてほしいと親に頼んだりしていた

そして祖母には近所を徘徊して各所から要らない材木を貰ってくる怪しい趣味があり、僕はそれが管理されていないことをいいことに工場に籠もって釘を打ったりノコギリで切ったり好き放題やっていた

今のプログラマーとしての生活に至るまでの物作りへの異様な欲求はこれらの豊かな土壌に育まれ形成されたことは自明であり、故に静岡県に感謝するところは大である
その旨に対する感謝をここで表明することとする。ありがとう

CQRS提唱者のドキュメントを読む(イベント部分抜きで途中まで)

読まないのもよくないかな?と思って読んだ
でまとめた

こちらを参考にしながら
https://sipadan2003.blogspot.com/2013/12/cqrs.html

この記事(僕の書いた記事)で引用している訳文はDeepLと僕の共作

(僕の)まえがき

DDDの基本的な話を理解していないと分からないと思う

読んだら実装例とかレイヤ分割の話とか載っているかな?と思ったけどそんなことなくてショックだった
しかも分かりにくいし!

先にこちらを読んでおくと理解が速い
寧ろこっちにはある程度の実装例書いてくれているし大変ありがたい(実践の名に即している!)

little-hands.hatenablog.com

一応原典を読むことで理解は深まりはした

A Stereotypical Architecture

この節ではDDD(ステレオタイプアーキテクチャ)の問題点の指摘を行っている

ステレオタイプアーキテクチャってこんな感じ

f:id:omdwn:20210709221155j:plain
ステレオタイプアーキテクチャ

図:僕作

それらのアーキテクチャの有する動詞はCRUDの4つのみ
※ DDDというかそのDomainObjectを対象にRepositoryで行う操作はCRUDに集約してしまっているということ
しかし現実問題のユースケースCRUDで十分ということは滅多に無い
例えば後述するような「販売を完了する」、「発注書を承認する」、「ローン申請を提出する」など
上の図では早速全てを無視してCreateじゃなくてSaveとか書いてるし他のメソッドを端折ってるけど気にしないで

更に言うと、DBが原因でボトルネックを抱えがち(かつ、スケールさせにくい)

ということで本書(CQRS Documents by Greg Young)ではこれらの課題に対してアプローチをかけていく
その柱となるのがCQRS

Task Based User Interface

Instead of simply sending the same DTO back up when the user is completed with their action the client needs to send a message to the Application Server telling it to do something. It could be to “Complete a Sale”, “Approve a Purchase Order”, “Submit a Loan Application”. Said simply the client needs to send a message to the Application Server to have it complete the task that the user would like to complete. By telling the Application Server what the user would like to do, it is possible to know the intention of the user.
ユーザーのアクションが完了したら、同じDTOを単純に送り返すのではなく、クライアントはアプリケーション・サーバに何かをするようにメッセージを送る必要があります。例えば、「販売を完了する」、「発注書を承認する」、「ローン申請を提出する」などです。簡単に言えば、クライアントはアプリケーション・サーバーにメッセージを送り、ユーザーが完了させたいタスクを完了させる必要があるのです。ユーザーが何をしたいのかを伝えることで、アプリケーション・サーバはユーザーの意図を知ることができます。

課題の残るDDDのアーキテクチャ的にはクライアント(Presentation)がアプリケーション(ApplicationService)にメッセージ(引数)として渡すのはシンプルなDTOで、それそのものではなにしてるのか分かりにくい

でも、ドメインオブジェクトをコピーしただけのDTOではなく、具体的な処理に合わせたオブジェクトがメッセージとして送られたら何を意図しているのか分かりやすいよね?

Commands

そこで上述したメッセージ(引数)に以下のようなCommandObjectを利用しようという話

例えばこれは商品の非活性化処理用のコマンド(引用)

public class DeactivateInventoryItemCommand {
    public readonly Guid InventoryItemId;
    public readononly string Comment;

    public DeactivateInventoryItemCommand(Guid id, string comment) {
        InventoryItemId = id;
        Comment = comment;
    }

これはボトムアップDDDでも出てきたものなので特に細かく書く必要もないかなぁ
分かりにくかったら次の話まで読み進めたほうが分かりやすいかも

User Interface

こっちはボトムアップDDDでは見なかった話

In order to build up Commands the User Interface will generally work a bit differently than in a DTO up/down system. Because the UI must build Command objects it needs to be designed in such a way that the user intent can be derived from the actions of the user.
コマンドを構築するために、ユーザーインターフェースDTOアップ/ダウンシステム(訳注:DDDのApplicationServiceのI/OをDTOにすることっぽい)とは少し違った動作をします。UIはコマンドオブジェクトを構築する必要があるため、ユーザーのアクションからユーザーの意図を汲み取ることができるように設計する必要があります。
The way to solve this is to lean more towards a “Task Based User Interface” also known as an “Inductive User Interface” in the Microsoft world.
この問題を解決する方法は、「タスクベースのユーザーインターフェイス」(Microsoftでは「インダクティブ・ユーザーインターフェイス」と呼ばれています)に近づけることです。

つまり、タスクベースUIという考え方に準じた設計をすることでユーザにとって使いやすいシステム、及び前述のCommandパターンが実現できるようになる

例をあげて説明すると...

(課題の残るDDDのアーキテクチャでは) 例えば商品を表示/非表示するという処理を実現する際に、その引数にDTO、つまりDomainObjectたるItemをコピーしたInventoryItemDTOを利用する
その場合のUIは以下のようなイメージになる

--------------------------------------
商品ID: xxx
    名称    :     [   ]
    提供者  :     [   ]
    仕入れ値:     [   ]
    数量    :     [   ]
    ステータス:   [   ] (表示 or 非表示)
    非表示理由:   [   ] (ステータスで非表示を選んだ時だけ表示)

[ 送信 ]
--------------------------------------

POSTされるであろうこれらのInputに対応するため、InventoryItemDTOにはフィールドが多くなるのは必然だ

一方、タスクベースUIでは、アイテムのリストがまず存在していて、その個々のアイテムに対して非活性化するためのリンクやボタンを用意する
そのリンクを押下した際に以下のようなUIがポップアップなどで表示される

--------------------------------------
商品ID | 商品名 | ステータス |
000001 | ミカン | 表示中     | [非表示]
000002 | トマト | 表示中     | [非表示]
000003 | サンマ | 非表示     |
--------------------------------------

↓ ミカンの非表示リンクを押下

--------------------------------------
商品ID: 000001
商品名: ミカン

商品を非表示とする場合はその理由を入力する必要があります
[                            ] [ 送信 ]
--------------------------------------

タスクベースUIにおいてCommandパターンを採用する場合、これらのInputを受けるのは、CommnadObjectとすることが出来る
例えば今回の場合、そのオブジェクト名は上述したような DeactivateInventoryItemCommand あたりになるだろう(非表示と非活性と命名が微妙だけど)
そしてそのオブジェクトが持つべきフィールドは商品ID、コメント(非表示の理由)のみである
上述の通りのItemDTOを利用する場合よりシンプルかつDTOの役割が明確化される

Command and Query Responsibility Segregation

CQRS コマンドクエリ債務分割

Origins

本ドキュメントではCQSの定義についてはWikipediaのそれを参照している

It states that every method should either be a command that performs an action, or a query that returns data to the caller, but not both. In other words, asking a question should not change the answer. (Wikipedia)
つまり、すべてのメソッドは、「アクションを実行するコマンド」「呼び出し元にデータを返すクエリ」のいずれかでなければならず、両方であってはならない。言い換えれば、質問をしても答えを変えてはいけないということです。

それに対しMartin Fowlerからの問題提起がある

Meyer likes to use command-query separation absolutely, but there are exceptions. Popping a stack is a good example of a query that modifies state.
Meyerはコマンドとクエリの分離を絶対に使いたがりますが、例外もあります。スタックのポップは、状態を変更するクエリの良い例です。
Meyer correctly says that you can avoid having this method, but it is a useful idiom. So I prefer to follow this principle when I can, but I'm prepared to break it to get my pop.
Meyerは正しく、このメソッドを利用しないようにすることができると言っていますが、ポップは便利なイディオムです。ですから、私はできる限りこの原則に従いたいと思っていますが、ポップを利用するためにはそれ(訳注: コマンド/クエリの分離)を破ることも覚悟しています。
https://martinfowler.com/bliki/CommandQuerySeparation.html

スタックのポップは言うなればスタックの一番上の値を取得し、スタックからそれを削除するようなもの
それは、Javaイテレータの反復処理Nextメソッド(カーソルを次に進め、その要素を返す)も似たようなもので、コマンドとクエリが同時に行われてしまっていることに他ならない

CQRSは当初こうしたCQSの拡張だと思われていたけど、実際は別のものですよ。ということらしい

参考:https://qiita.com/hirodragon/items/6281df80661401f48731

Command and Query Responsibility Segregation uses the same definition of Commands and Queries that Meyer used and maintains the viewpoint that they should be pure. The fundamental difference is that in CQRS objects are split into two objects, one containing the Commands one containing the Queries.
コマンドとクエリの責任の分離では、Meyerが使用していたコマンドとクエリの定義をそのまま使用し、それらが純粋であるべきだという視点を維持しています。基本的な違いは、CQRSではオブジェクトが2つのオブジェクトに分割されることです。1つは複数のコマンドを含むオブジェクト、もう1つは複数のクエリを含むオブジェクトです。

CQRSを適用していないサービス
サービスがコマンドとクエリの両方を担っている
ドメインオブジェクトはその両方で利用される

CustomerService
void MakeCustomerPreferred(CustomerId)
Customer GetCustomer(CustomerId)
CustomerSet GetCustomersWithName(Name)
CustomerSet GetPreferredCustomers()
void ChangeCustomerLocale(CustomerId, NewLocale)
void CreateCustomer(Customer)
void EditCustomerDetails(CustomerDetails)

CQRSを適用したサービス
Writeがコマンド側、Readがクエリ側となる

CustomerWriteService
void MakeCustomerPreferred(CustomerId)
void ChangeCustomerLocale(CustomerId, NewLocale)
void CreateCustomer(Customer)
void EditCustomerDetails(CustomerDetails)

CustomerReadService
Customer GetCustomer(CustomerId)
CustomerSet GetCustomersWithName(Name)
CustomerSet GetPreferredCustomers()

そして、コマンド・クエリのそれぞれについて、アーキテクチャの特性は大きく異なる傾向にあるとのことで、いくつかの側面から見た例が挙げられている

それらを引用すると↓

Consistency

Command: It is far easier to process transactions with consistent data than to handle all of the edge cases that eventual consistency can bring into play.
Query: Most systems can be eventually consistent on the Query side.
コマンド: 最終的な一貫性がもたらすエッジケースをすべて処理するよりも、一貫性のあるデータでトランザクションを処理する方がはるかに簡単です。
クエリ: ほとんどのシステムはクエリ側で最終的に一貫性を保つことができます。

コマンド側の「よりも」というのはCQRSを適用していない場合のことを言っているのだろう
分かりにくいけど何となく理解できる

Data Storage

Command: The Command side being a transaction processor in a relational structure would want to store data in a normalized way, probably near 3rd Normal Form (3NF)
Query: The Query side would want data in a denormalized way to minimize the number of joins needed to get a given set of data. In a relational structure likely in 1st Normal Form (1NF) コマンド: コマンド側はリレーショナル構造のトランザクションプロセッサであるため、データを正規化して保存したいと考えています(おそらく第3正規形(3NF)に近い形で)
クエリ: クエリ側では、あるデータセットを得るために必要な結合数を最小限にするために、データを非正規化して保存します。リレーショナル構造の場合、おそらく第1正規形(1NF)になります。

コマンド側では正規化されたオブジェクトを利用しDB更新処理を行う
つまり、テーブルと1:1で関連付けるような、厳しい縛りのORMで使うオブジェクトを使うイメージ(RailsのModelみたいなもの)

クエリ側ではそのクエリのSELECTする内容ごとに適した結果がほしいので、非正規化されたオブジェクト、つまりクエリのユースケースごとに適したフィールドを有するオブジェクトをDBから読み込んだデータにマッピングする

Scalability

Command: In most systems, especially web systems, the Command side generally processes a very small number of transactions as a percentage of the whole. Scalability therefore is not always important.
Query: In most systems, especially web systems, the Query side generally processes a very large number of transactions as a percentage of the whole (often times 2 or more orders of magnitude). Scalabilityis most often needed for the query side.
コマンド: ほとんどのシステム、特にウェブシステムでは、コマンド側で処理するトランザクションの数は、全体に占める割合としては非常に少ないのが一般的です。そのため、スケーラビリティは必ずしも重要ではありません。
クエリ: ほとんどのシステム、特にウェブシステムでは、クエリ側は一般的に全体の中で非常に多くのトランザクションを処理します(しばしば2桁以上)。スケーラビリティが最も必要とされるのは、クエリ側です。

これは分かりやすい。書いてあるとおり

The Query Side

クエリ側はデータを取得するメソッドのみを持つ

クライアントに返すDTOsにはドメインオブジェクトを利用せず、クライアントの要求を満足するDTOをそれぞれ生成し利用する

これによってCQRS非導入時のステレオタイプアーキテクチャに起こりがちな以下の問題を回避できる
この辺はCQRSについて説明した文章でよく見る内容だと思う

  • リポジトリの大量の読み取りメソッドには、ページングやソートの情報も含まれることがある
    • ページングやソートの情報はドメインの情報とは無関係
  • DTOを構築するために利用されるドメインオブジェクトのgetterが予期せぬ利用を生む危険がある
  • クエリで絞り込まない場合、データをメモリに展開してからの絞りこみになりメモリ使用量が増える
  • DTO を構築するために複数の集約ルートを無駄に読み込むことになる
    • SELECT句で利用しないカラムまでメモリに読み込むことになる
    • これによって集約の境界が混乱することがある
  • 何よりクエリの最適化が出来ない (インピーダンスミスマッチ)

(図は原典のを見てください)

クエリ側ではドメインを利用することなく、Thin Read Layerによってデータベースから直接読み込みDTOsにマッピングを行う

The Command Side

(図は原典のを見てください)

コマンド側がクエリ側と違い比較的ステレオタイプアーキテクチャに似た形のまま

クエリ側で記述した「CQRS非導入時のステレオタイプアーキテクチャに起こりがちな問題」はコマンド側にも恩恵をもたらす
ドメインオブジェクトはフィールドのアクセサを広くとる必要がなくなり、Repositoryでは GetById を除くとクエリ用メソッドは僅かしか残らない。 また、集約の境界について今まで以上にふるまいに焦点を絞ることができる

こうしてコマンドとクエリを分離すると、それぞれで読むデータソースを別にすることで、タスクに合わせてデータモデル最適化できる
例えば、上述したようにクエリ側は非正規化状態(第1正規形)のデータ、トランザクショナルモデル(コマンド側?)は正規化状態のデータ(第3正規形)というような形

個人的には、クエリ側はバッチで生成したサマリテーブルでも読むようにすると楽になると思う

続き

Events as a Storage Mechanism から先は読んでないけど気が向いたら...

(僕の)感想

これ結局実践CQRS入門読んだ方がいいわ

あとMartin Fowlerのも読まなきゃ

martinfowler.com

三ツ星ビストロNE-BS807使用レポ(やや酷評)

買ったもの:
スチームオーブンレンジ ビストロ NE-BS807

panasonic.jp

4月に買って2ヶ月利用してのレビュー

あらすじ(蛇足)

一人暮らし開始時に買ったしょぼい電子レンジだが、7年くらい経過したあたりから蓋を閉じてもターンテーブルが周り続けるようになってしまった
非利用時にはコンセントを抜くパワフルな運用で乗り切っていたが遂に買い換えることを決意する...

良いところ

温度指定で自動あたためができる

食べ物の温度というものを意識したのはこれが初めてだった

以前の電子レンジでは食材に対してある程度の加熱の勘所を鍛える必要があった

あたため終了時の音が騒々しくない

品性に満ちている

以前の電子レンジのけたたましさといったら廊下の外まで聞こえている程だったので大満足

ターンテーブルがない

うれしい。掃除もしやすい

スチームあたためが便利

レトルトを温められるのがまず便利
貴族たる僕の場合は実家から鰻が届いたりするのでよく使う

それ以外でもスチーム温めによって明らかに美味しくなる食べ物も多い

低温調理ユーザとしては低温スチームもかなり魅力

60~95℃の間で5℃刻みに設定できます。漬け物も塩もみ不要ですばやく漬け込み。また、鶏ハムもしっとり仕上げます。

僕の場合はこれで低温調理するというより、低温調理して冷凍したものを解凍するのに使っている

悪いところ

自動調理メニューを使わない

材料が多く作る気にならないものが多い

内部の動き(加熱工程)が隠蔽されており、応用がきかない
調理過程における具合が目視できない以上、多少量を調整したりするということがやりにくい

小学生にお手伝いさせる程度にはいいかも知れないけど、それなりに料理のできる大人が使うものではないと思う

挙げ句上位機種でないと対応していないメニューも多く、不快感は募る

これらがUIの多くの部分を占めているのはかなり無駄
お金も無駄

説明書・レシピはPDFで見ることが出来るので購入前に見るべき

https://panasonic.jp/range/p-db/NE-BS807_manualdl.html

スチームあたための水は毎回入れ替える必要がある

説明書を読んでびっくりこの面倒くささ
でも他の機種も同じようなものかも

グリル皿・角皿の登場機会が少ない割に嵩張る

邪魔!で結局しまいっぱなし

この電子レンジを買ったらオーブンを捨てられるかと思ったが、このせいで未だに捨てられない

普通のオーブンだと食パンをトーストした後、火傷しないよう気をつけつつ庫内に素手を突っ込んでパンを回収して終わりだが、この電子レンジの場合は素手以外で角皿を回収して収納する工程が増える
これは僕にとって許容し難い苦しみを伴う作業だ

もし有効活用するつもりならこれらの置き場所まで事前に検討しておくと良い

○○もこの電子レンジで作れる!が微妙


→ フライパンにクッキングシート敷いてその上で焼いたほうが汚れないし匂いもつかない

ゆで卵・温泉卵
→ 低温調理器

餃子
→ 普通にフライパン

等...

「へぇ~これも出来るんだ便利!」と鵜呑みにせず、代替手段を考えた方がいい
多くの場合においてそちらの方が優れている

総括

元々使っていた電子レンジがポンコツだったため結構褒めてしまったが、良いところの多くは他の機種でも言えることだと思う

今回は調子に乗ってお金を出し、そこそこなモデルのものを買ったが、次はもっとお料理玄人向けに機能を絞ったものを買いたい

Boniqを買って3ヶ月経ったので低温調理おすすめレシピ紹介

Boniq Proを買って3ヶ月の月日が流れた

調理家電あるあるの「買ったはいいが作りたいものが無い」に怯えながらだったけどそこそこレシピを集めたのでその共有をする

買ってよかったかどうかとか、その辺はまた別途書きたい

サラダチキン

boniq.jp

低温調理といえばサラダチキン

一応レシピを載せたけど、僕は味付けに関しては無視して作っている
僕の味付けは液体塩麹クレイジーソルト(両方)
液体塩麹は空気抜いた時に胸肉がちゃんと浸かるくらい... クレイジーソルトは大きい口で8振りくらい... とかなり適当に作っている
大昔に見た低温調理用ではないサラダチキンのレシピの味付けをうろ覚えで低温調理に転化しているけどクレイジーソルト要らないかもしれん

これの何が楽って塩麹使ってるからか普通の低温調理より更にシットリするところ

鶏もも肉のコンフィ

sou-recipe.com

材料が少なくて助かる!

肉のハナマサに骨付き鶏もも肉*4だか5個パックが売っていて、それを利用すると作りおきに便利

焦げ目をつけるのは難しくて未だに上手くいかないのが悩み
フライパンだと上手くつかずバーナーだと焦げすぎる

ローストビーフ

皆美味しく食べてるので適当なレシピで作るといい

僕の包丁は絶望的に切れず、食感がダメダメな分厚いローストビーフしか作れないのであまり好きではない

野菜

アスパラガス

boniq.jp

アスパラガスなんて普段食べないものを食べるとさも健康になった気分になる

かぶと小松菜

boniq.jp

83℃だけどアスパラガス(85℃)と似たようなものだろうとアスパラガスを逆にこっちの温度に寄せて調理している

かぶの皮を剥くのが面倒で、これサボってもいいんじゃないか?!と思っていて近々チャレンジしたい

かぶの葉はコッチのレシピで使う

かぶの葉としらすの中華炒め
https://www.nichireifoods.co.jp/media/8336/

かぼちゃ

boniq.jp

これ正直ボウルに入れて電子レンジ調理でいいんじゃない?個人宅の食卓で軽度な煮崩れが気になるか?と思いつつ、このレシピのメリットは低温調理だからこそ大量に作れるところにある
かぼちゃ切るのすごい大変だけど、時間があるなら作り置きしておくと濃い味のおかずが簡単にストックできて便利

電子レンジだと
冷凍カットかぼちゃを都度解凍 → 調味料用意 → レンチン調理

低温調理だと
かぼちゃをまとめてカット → 調味料まとめて用意 → まとめて低温調理 → 冷凍 → 食べる時はそれぞれ解凍のみ

温泉卵

sou-recipe.com

65℃ 30mで調理したやつをサラダにのせるのがおすすめ

超簡単にできて、まず失敗しないし単品で美味しい

調子にのって卵10個パックで作るのがアツい
が気をつけなくてはいけないのは日持ち

おうちで作ったものの場合は、作ってから2〜3日程度であまり日持ちしません。冷凍すれば長持ちするのではと思うかもしれませんが、食感を損なうため向いておらず、早めに食べるほうがいいんです。
https://cojicaji.jp/cooking/preservation-method/2849#table-of-contents-h2-2

カルボナーラ

boniq.jp

低温調理の本領は卵なのでは?
本当にきれいにカルボナーラが作れる

番外編

普通の調理でいいじゃねえか!と思ったのは野菜に多い気がする

終わりに

はやくこれを作れるようになりたい

ボトムアップDDDを読んだ結果として自分が見返す用のメモ

要約ではないです。メモです
コードでは面倒くさいので色々(例えばコンストラクタとか)端折ってる

色々間違ってるかもしれないのでちゃんと本を読んだほうがいい

https://www.amazon.co.jp/gp/product/479815072X/

本にする前のをWebで見れたりもする

nrslib.com

ドメインオブジェクト(DomainObject)

値オブジェクト(ValueObject)

ValueObjectの定義とは?と問われた場合、例を示してこういうものといった方が楽な気がする

特徴としては...

  • 不変である
  • 交換が可能である
  • 等価性によって比較される
// 不変である
// 交換が可能である
var name = new Name("hoge");
name.Change("fuga");    // NG
name = new Name("fuga") // OK

「等価性によって比較される」についてはIEquatableを実装していることとイコール

オブジェクトをValueObjectとするかどうかという基準:
→ そこにルールが存在しているか、単体で扱いたいか

不変についての補足

例えばclass Moneyがあったとしたらその加算処理はAddによって成され、新しいインスタンスを返す(不変だから)

class Money
{
    // Moneyの新しいインスタンスを返す
    public Money Add(Money arg) {}
}

var milk = new Money(170, "JPY");
var ticken = new Money(900, "JPY");
var result = milk.Add(ticken);

思ったこと:今回の場合、+演算子オーバーロードとかしたくなる

エンティティ(Entity)

ValueObjectと対を成すもの
ライフサイクルがあるのがEntity

特徴としては...

  • 可変である
  • 同じ属性であっても区別される
  • 同一性により区別される
    • 同一性の判断の実態は大概識別子(ID)

可変であるということで、EntityではValueObjectのところに書いた name.Change("fuga"); はOK

・同じ属性であっても区別される
・同一性により区別される

ってのがわかりにくかも

同じ属性というのは例えばユーザの名称
同一性というのは上述の通りID

例えばID:1のユーザの名称が"hoge"だったとして、その名称を変更する処理のなかで以下のように変化したとしても同一ユーザですね。という話

  ID: 1, Name: "hoge"
→ ID: 1, Name: "fuga"

逆に言うと名称が一致していてもIDが違ったら別のもの(同姓同名)

値オブジェクト(ValueObject)とエンティティ(Entity)で迷ったら

可変なオブジェクトより不変なオブジェクトの方が安心ということで、一旦ValueObjectにしておけば安心とのこと

TODO: 本書を読んでいてValueObjectの例があまり思い浮かばなかった
具体例をもって分類して考えた方が良さそう

ドメインサービス(DomainService)

DomainObjectのふるまいでDomainObjectに記述すると不自然なものを書くとこ

例えばUserクラスの重複チェック処理(Exists())なんかは、そのインスタンスメソッドして作ると変なことになる
後述するRepositoryに繋ぎに行ったりすると更に微妙

// 変
User checker = new User(new UserName("checker"));
User target = new User(new UserName("hoge"));
checker.Exists(target);

思ったこと:上記のようにRepositoryに依存する処理はさておき、そうでなければウッカリstaticメソッドとしてDomainObjectに定義しそう

レポジトリ(Repository)

DomainObjectとデータストアの仲介をするとこ

オブジェクトを繰り返し利用するには 、何らかのデータストアにオブジェクトのデータを氷続化(保存)し 、再構築 (復元)する必要があります。リポジトリはデータを永続化し再構築するといった処理を抽象的に扱うためのオブジェクトです。

Repositoryの中でクエリ書いたりするとのこと
ORM(本書ではEF)にてテーブル及びカラムと紐付けするオブジェクトをDataModelと呼んでいるぽい
(場所的には Infrastructure.DataModel.Users.User とか)
DomainObjectが特定のデータ基盤に依存するのを防ぐため、別途DataModelを設けてるイメージ → このクラスはテーブル名ナントカと対応していて... カラムはフィールド名ナントカと対応していて... みたいなことをDomainObjectに記述するのを避けたいってこと

Repositoryの中でDBと直接やりとりするところだけDataModel、メソッドとしてのI/Oは普通にDomainObjectを使う

こんな感じ

public class UserRepository : lUserRepository
{
    private readonly MyDbContext context;

    // ...

    public User Find(UserName name)
    {
        UserDataModel target = 
            context.Users
            .FirstOrDefault(userData => userData.Name == name.Value);
        if (target == null) { return null; }
        // DataModel to DomainObject
        User result = ToModel(target);
        return result;
    }

    public void Save(User user)
    {
        UserDataModel found = context.Users.Find(user.ld.Value);
        if (found == null)
        {
            // DomainObject to DataModel
            UserDataModel data = ToDataModel(user);
            context.Users.Add(data);
        }
        else
        {
            // Merge DomainObject and DataModel
            UserDataModel data = Transfer(user, found);
            context.Users.Update(data);
        }
        context.SaveChanges();
    }

    // ...

DomainServiceとの債務分割に注意

Repository側をなるべくシンプルなCRUDにするように

class UserDomainService
{
    bool Exist(User user)
    {
        // こうではなく
        // repository.Find(user)
        // こっちの方が良いとのこと
        repository.Find(user.UserName)

        // この後nullチェックとか...
    }
}

class UserRepository
{
    User Find(UserName name)
        => // ...
}

// つまり... Service側ではUser単位で存在チェックするけど、
// その裏側のRepositoryではnameで引いてるよ。という感じ

IFを使ってテスト用のRepositoryを設けよう

private Dictionary<UserID, User> Store {get;} みたいなものをテーブルの代わりに使うイメージ

思ったこと:テストはインメモリでやる!ということだけど、これってSQLのテストにはならない
EFとかRailsのARみたいにクエリ書かかないタイプのORMならいいのかもだけど、Dapperとかだと微妙かな~
結合テストをする場合、Mock使うよりこっちの方が便利だとは思う。SQLのテストはさておき

アプリケーションサービス(ApplicationService)

ユースケースを実現するやつ。DomainServiceの対になる

例えばユーザ登録とか取得なんかはApplicationService

// 引数がプリミティブ型なのがポイントかも
void Register(string name)
{
    // nameを持つUserのインスタンス(DomainObject)つくって
    // 存在チェック(DomainService)して
    // 登録(Repository)
}

User Get(string userId)
{
    // userIdを値に持つUserIdのインスタンス(DomainObject)を作って
    // IDを元にデータを引く(Repository)
}

この時、ApplicationServiceを利用する側にDomainObjectを露出するか否かが問題になってくる
→ ApplicationServiceの呼び出し元にDomainObjectを直接触られると、メソッドを予期せず利用される懸念がある(ChangeName()は値を代入し直す処理だけどデータストアのUpdate的なニュアンスで使われたり...)

その対策としてDTO(DataTransferObject)を利用すると良いとのこと

public class UserData
{
    public UserData(string id, string name)
    {
        Id=id;
        Name = name;
    }
    public string Id { get; }
    public string Name { get; }
}

public class UserApplicationService
{
    // 呼び元に返すのはプリミティブ型のみを保持するDTO
    public UserData Get(string userid)
    {
        Userld targetld = new Userld(userid);
        User user = userRepository.Find(targetid);
        UserData userData = new UserData(user.ld.Value, user.Name.Value);
        return userData;
    }
}

思ったこと:DTOを使うかどうかはプロジェクトのポリシーによるところで、正しいけどここまでやるのは冗長かな。と個人的には思ってしまう
最終的にアウトプットに使うオブジェクト(ViewModelとか)に持たせたりするオブジェクトをDTOにする、ってとこまでやるとそこまで無駄に感じられなくなるかもだけど、DTOとDomainObjectで共通する処理が出てくるのではないか?!とか心配になる
ここらへんは実装してみて学ぶしかないか

更新系の処理のためのCommandObject

更新系(Update, Delete)の処理について、「ユーザ名だけ変更したい、アドレスだけ変更したい、...」等々の要望に対応していく場合、素でやると単純にServiceの引数を増やしていくことになる。大変 void Update(string userId, string name = null, string email = null, ...etc)

そういうときはCommandObjectを使うと部分更新に便利で良いらしい

public class UserUpdateCommand
{
    // 更新するフィールドにのみ値を入れる
    public UserUpdateCommand(string id, string name = null, string mailAddress = null)
    {
        Id = id;
        Name = name;
        MailAddress = mailAddress;
    }

    public string Id { get; }
    public string Name { get; }
    public string MailAddress { get; }
}

public class UserApplicationService
{
    // CommandObjectを引数に
    public void Update(UserUpdateCommand command)
    {
        UserId targetld = new Userld(command.ld);
        User user = userRepository.Find(targetid);
        if (user == null) { throw new UserNotFoundException(targetid); }

        // nameのチェック
        string name = command.Name;
        if (name != null)
        {
            UserName newUserName = new UserName(name);
            user.ChangeName(newUserName);
            if (userService.Exists(user)) { throw new Exception(user, "ユーザが既に存在しています"); }
        }

        // emailも似たようにチェック
        // ...

        userRepository.Save(user);
    }
}

// こう呼ぶ
var updateCommand = new UserUpdateCommand(id)
{
    Name = "hoge"
    // メールアドレスも更新したかったら...
    //Email = "hoge@example.com"
}
userApplicationService.Update(updateCommand);

思ったこと:部分更新に便利とは言うけど、いうほど手間は変わって無いのでは?
単純に引数群を1オブジェクトにまとめる意義はあるけど
あとなんとなくRailsっぽくなる気がするけど気のせいかも

低凝集にならないよう気をつける

あとApplicationServiceは低凝集になりがちなので気をつける

例えば以下のような状態だと、コンストラクタDIするRepository, DomainServiceの後者が一部のメソッドからしか呼ばれない状態

class UserApplicationService
{
    // repo
    private readonly IUserRepository userRepository; 
    // domain service
    private readonly IUserService userService;

    public void Register(string name, string mailAddress)
        =>  // RepositoryとDomainService両方使う

    public void Delete(string name, string mailAddress)
        =>  // Repositoryのみ使う
}

そういうときはUserRegisterService, UserDeleteServiceに分割するといいよとのこと

思ったこと:それぞれの処理が激長いなら分割もいいと思うけど、見通し悪くなるから一つのApplicationServiceにまとめちゃっていいんじゃないかなと思う

ファクトリ(Factory)

オブジェクトの生成を債務とするオブジェクト

例えばUserのインスタンスを新規に生成するとき、DBから次のIDを採番して、そのIDをもたせたかったりする
そういう時、UserのDomainObjectにその処理を書くと、その中でデータストア(今回はDB)に接続することになりNG
(DomainObjectは高レベル概念、DB操作は低レベル概念)
そこでFactoryクラスをつくる

こういう感じ

public interface IUserFactory
{
    User Create(UserName name);
}

public class UserFactory : IUserFactory
{
    public User Create(UserName name)
    {
        string seqld;
        // ここでコネクションつくってDB接続してDB固有の関数やらで次のIDを取得
        // Repositoryからもってくるわけではない
        // Factoryから外れるけど、FactoryナシでRepositoryで採番するのもあり
        seqld = /* hogehoge */

        UserId id = new Userld(seqld);
        return new User(id, name);
    }
}

public class UserApplicationService
{
    // 例のCommandObjectを引数に使う
    public void Register(UserRegisterCommand command)
    {
        UserName userName = new UserName(command.Name);
        // ここでFactoryを使う
        User user = userFactory.Create(userName);
        if (userService.Exists(user)) { throw new CanNotRegisterUserException(user); }
        userRepository.Save(user);
    }
}

思ったこと:TODO 上記のようにDB接続が必要な時以外でFactoryを使うシーンがあんまり想定できないので、色々やってみたほうがよさそう

定義するnamespace的な話

Factoryクラスはその存在に気づきやすいように、DomainObjectと同じところに置くとよい

・HogeDomain.Models.Users.User
・HogeDomain.Models.Users.IUserFactory

クラスではなくメソッドでFactory

Factoryクラス(のCreate())そのものではなく、DomainObjectにFactoryとして機能するメソッドを設ける場合もある

// こういうことをするとあんまりよくない
Circle circle = new Circle(
    // サークル主催者のID
    user.id, // DomainObjectのgetterを使ってる(集約的によくない)
    new CircleName("hoge circle"))
);

// なのでこっちにつくる
public class User
{
    // privateにできる!
    private readonly Userld id;

    public Circle CreateCircle(CircleName circleName)
        => new Circle (id, circleName)
}

これら以外にも、コンストラクタで別のオブジェクト作ってしまってたり、処理が複雑すぎる場合にはFactoryとして切り出せばいいみたい

DDDのオブジェクトではないけど:整合性(Consistency)

Validationではなくてトランザクション周りの話

トランザクションをどこで張る?
→ Serviceで張りたい → でもそうするとServiceがDB(低レイヤ)に依存してしまう

なのでC#ではTransactionScope

using (TransactionScope scope = new TransactionScope())
{
    // この中の処理でコネクションを開くと自動的にトランザクションを張ってくれる
}

Javaとかでは @Transactional を利用する(効能はC#のものと同じ)

あるいはユニットオブワークというパターンを使えばいいらしいけど、これはキツそうなので見なかったことにした

参考:https://nrslib.com/bottomup-ddd-2/#outline__3_5

DDDのオブジェクトではないけど:集約(Aggregation)

こんな感じにオブジェクトをまとめて、集約に含まれるオブジェクトに対する外部からの操作をすべて集約ルートを通じて行うようにする感じの話

集約の境界内に存在するオブジェクトを外部にさらけ出さないことで、集約内の不変条件を維持できるようにしているのです。

関連:デメテルの法則

集約は例えば以下のような感じ

Circle ------- 0..n -------User
 ├ CircleId                ├ UserId
 └ CircleName              └ UserName

 CircleとUserがそれぞれ集約ルート(AggregationRoot)

上記に対して...

user.Name  new UserName("hoge");       // NG
user.ChangeName(new UserName("hoge")); // OK

ということでこうなると、集約ルート以外のgetterを完全に隠蔽したくなる。
が、そうするとRepositoryで困ったりする

public class EFUserRepository : IUserRepository
{
    public void Save(User user)
    {
        // DataModelを作る際、DomainObjectのgetterを使ってしまっている!
        UserDataModel userDataModel = new UserDataModel
        {
            Id   = user.ld.Value,
            Name = user.Name.Value
        };
        context.Users.Add(userDataModel);
        context.SaveChanges();
    }
}

この対応として素直にgetterを許すと変なとこで使われる様になってしまうかも...
→ そういう場合、通知オブジェクト(IUserNotification)ってのを使うといいよ。ってことだけど、コレを使うのは大変そうなのでここでもスルーする...

参考:https://nrslib.com/bottomup-ddd-2/#outline__5_5_3

集約の切り方について

上の例でいくとCircleの集約の中でUserを変更するのはだめ

public class Circle
{
    private List<User> members;

    // User集約の外からUserの中身を触るのはNG
    public void ChangeMemberName(Userld id, UserName name)
    {
        User target = members.FirstOrDefault(x => x.ld.Equals(id));
        if (target != null) { target.ChangeName(name); }
    }
}

そもそもこういった操作を防止するためにインスタンス(User)を保存するのではなくて、識別子(UserId)を保存するとよい

// こうじゃなくて
// public List<User> Members {get; private set;}
// こう
public List<UserId> Members {get; private set;}

思ったこと:最終的にオブジェクトをViewModelとかに渡した後でView側で使う時に面倒くさくなりそう
例えば上記Membersをループしてメンバーのリストを表示する時とか、ワザワザ別途List<User>からもってこないといけない ここでそもそもViewModelに渡すのはDomainObjectじゃなくてDataModelを使えばいいのでは?とかそういう話が出てくるのかな

仕様(Specification)

仕様そのものをドメインのオブジェクトとして切り出す

ちょい上の「集約の切り方について」の話のままいくと、CircleはメンバーのリストとしてUserIdを保持するが、Userのインスタンスは保持しない作りになっている

そこでCircle側に「プレミアムユーザの数が10人以上ならメンバー数上限増やすよ」みたいな仕様が増えると大変
Circle側でユーザがプレミアムユーザか判定するために、CircleのDomainObjectがRepository使うのもおかしい
→ という時にこの仕様オブジェクトを作るとよいとのこと

こんな感じ

public class CircleFullSpecification
{
    private readonly IUserRepository userRepository;

    public bool IsSatisfiedBy(Circle circle)
    {
        List<User> users = userRepository.Find(circle. Members);
        int premiumUserNumber = users.Count(user => user.lsPremium);
        int circleUpperLimit = premiumUserNumber < 10 ? 30 : 50;
        return circle.CountMembers() >= circleUpperLimit;
    }
}

// 使う
public class CircleApplicationService
{
    public void Join(CircleJoinCommand command)
    {
        CircleId circleld = new Circleld(command.Circleld);
        Circle circle = circleRepository.Find(circleld);
        CircleFullSpecification circleFullSpecification = new CircleFullSpecification(userRepository);
        if (circleFullSpecification.lsSatisfiedBy(circle))
        {
            throw new CircleFullException(circleld);
        }
    }
}

仕様クラスでRepository使うことについて

仕様もDomainObjectから切り出したものなので、その中でRepositoryを使うのはイヤ...という話もある
→ そういう場合はファーストクラスコレクションというものを使うと良い → が、面倒くさそうなのでここもスルー

仕様を元に検索する

こうした仕様を元に検索をする場合、仕様オブジェクトをRepositoryに渡して絞り込むといい
でもそれって結局、全件( or ページングで区切った分だけ)読み出して、そこからLINQのWhereっぽく1つ1つのインスタンスに対してフィルタをかけることになってしまう
→ そうするとパフォーマンスの問題が発生しがち。なので直接クエリを発行するといい

これがCQRSの概念につながるけど本書ではパスしている

public class CircleQueryService
{
    public CircleGetSummariesResult GetSummaries()
    {
        var connection = provider.Connection;
        using (var sqilCommand = connection.CreateCommand())
        {
            sglCommand.CommandText = @"
SELECT
circles.id as circleld,
users.name as ownerName
FROM circles
LEFT OUTER JOIN users
ON circles.ownerld = users.id
ORDER BY circles.id
";
            using (var reader = sqglCommand.ExecuteReader())
            {
                // readerから結果を読み取って変換してreturn
                return new CircleGetSummariesResult(summaries);
            }
        }
    }
}

DDDにまつわるアーキテクチャ

DDDで重要なのはドメインが分離されること
どのアーキテクチャでなければならないというような縛りはない

レイヤードアーキテクチャ

エリック・エヴァンスのやつ

UI             : MVCのController(UIってかpresentation?)
Application    : ApplicationService
Domain         : DomainObject, DomainService. DomainをサポートするFactoryやRepositoryのIFもここ
Infrastructure : Repository, ORM

上の層がそれより下の層を利用するイメージ

ヘキサゴナルアーキテクチャ

話の中心のアプリケーションとそれ以外のIF(DBとかWebとか)を付け外せるように。という考え方

ポイントはIFを利用した依存関係の整理にある
レイヤードアーキテクチャはあくまでも層分けでIFは任意。でもレイヤードアーキテクチャでも自然とIF使うよね。ってことで今ではここの垣根はほぼ無いみたい

クリーンアーキテクチャ

ヘキサゴナルアーキテクチャ:取り外しOKっていう方針のみ クリーンアーキテクチャ:具体的な実装まで指示

TODO クリーンアーキテクチャの理解は重いのでもっとまともに勉強してから書いたほうが良さそう

ソリューション構成について

言語やFWによって方法は変わるけどC#だとひとまずこんな感じでいいみたい

フォルダ構成について

Domain
    Models
        Circles
            Circle.cs
            CircleId.cs
            CircleName.cs
            ICircleRepository.cs
            RecommendedCircleSpecification.cs
        Users
            User.cs
            UserId.cs
            UserName.cs
            IUserRepository.cs
            IUserFactory.cs
    Service
        UserService.cs
    Shared
        ISpecification.cs

Application
    Circles
        CircleApplicationService.cs
        CircleCreateCommand.cs
        CircleGetCommand.cs
        CircleGetResult.cs
        CircleData.cs
    Users
        UserApplicationService.cs
        UserCreateCommand.cs
        UserGetCommand.cs
        UserGetResult.cs
        UserData.cs

Infrastructure
    Circles
        EFCircleRepository
        InMemoryCircleRepository
    Users
        EFUserRepository
        InMemoryUserRepository
        EFUserFactory
        InMemoryUserFactory

ソリューション構成

構成ってか上記のDomain, Application, Infrastructure, そしてPresentationをプロジェクトとしてどうまとめるか?という話

Domainは他によらず使い回せるようにしたほうがいい。ということで、 本書でのおすすめは「全部別」or「アプリケーションとドメインだけ同じプロジェクト」

PresentationとかInfrastructure、つまりユーザとのI/Oとかデータソースは変わる可能性あるし分けるのも妥当
アプリケーションとドメインはそう離れないよね。みたいな感じっぽい

DDDのさわりを勉強した感想

よくあるMVC文脈ではServiceって言葉がとっ散らかってDDDでいうDomainService, ApplicationServiceが全部入りになった挙げ句巨大になったり、ControllerにDomainやServiceの実装が漏れてたり、コネクション作るのがいろんな層に散っていたり、そういう辛い問題に直面したことが多く、DDDに基づいた開発ポリシーを統一してそれら回避できると嬉しいなとは思う

DDDのDomainってつまるところMVCのMなわけでそこを切るだけでもキレイになるはず

しかし「それは確かに理想的だけど開発現場の統制を取るのはしんどい」みたいなパターンが多かった
例えばアクセサでメソッドやgetter/setterの利用を制限しても、「こういう思想で作ってる」って前提をそもそも理解してもらえないとDDDの理想からズレたところに新しいメソッドが定義されるようになったりすると思う
更にメンバーの全員が全員この方面に勉強熱心ってこともないのが世の常で、0からチームを作るわけでもない時にDDDをガッツリ突っ込んでいくのは厳しい
(ここまでの通り僕もパターンをスルーしまくってるし人のことは言えない)

というところで本書の中でも言葉が出てきた軽量DDDに逃げることになるんじゃないかなぁ

など書いててお気に入りのスライドを思い出した

www.slideshare.net

残課題

ほんとはReactとか触りたいけど、僕の中ではこっちをもうちょっと固めないといけない...

DMMブックスはデバイスによっては文字列選択/コピペができない(マーカー機能が使えない)ことについての対策としてのOCR ~Kindleで買え~

2021年の初回購入限定最大100冊70%オフセールでまんまと大量の技術書を買いせっせと読んでいるが困ったことがある

バイスによっては文字列選択/コピペができない(マーカー機能が使えない)

マーカー機能が使えるデバイス、使えないデバイスがある
→ 文字列選択ができるデバイス、できないデバイスがある

しおり機能のように使えるマーカー機能というものがあり、これがなかなか便利だ

iOS(iPad)、Androidではマーカー機能が使え、その記録はアカウントに紐付いているようでデバイス間同期もされる
Windowsではその機能が使えない(Macはまだ見てない)
Windowsで使えないというのは書籍をブラウザ版で見る場合でもアプリ版で見る場合でも同様

で、しおり機能と同じ区分けでWindowsでは文字列選択が出来ない
そもそもマーカー機能の実現可能性はこっち(文字列選択の可否)に依存している気がする

Windowsでマーカー機能が使えないため、出先ではiPadで読んで家ではWindowsでメモったところを読み直し... という流れがシームレスにいかない

まあマーカーはギリギリ我慢できるけど文字列選択が出来ないというのは辛い
コードコピペしようとしてもできない!令和なのに

文字列選択だけでもOCRでまかり通す

OCRできてオープンソースGUIツールはこれくらいぽかった

Screen Translator のダウンロードと使い方 - k本的に無料ソフト・フリーソフト

書籍内の記述やコードコメントは日本語で読み取り、コードは英語で読み取らないと精度がガバガバになったりするのでそこの切り替えが面倒くさい

けど無いよりはマシかな

DRM選挙カーくらい嫌いだな~

そもそもKindleだと...

普通にWindowsで文字列選択できるのでKindleで買った方がいいと思います