システム開発で思うところ

Javaで主にシステム開発をしながら思うところをツラツラを綴る

レガシーなコードにドメイン駆動設計で立ち向かった5年間の軌跡 に参加してきました

ddd-alliance.connpass.com

に参加してきました。
懇親会にも参加*1

さて、ブログに勉強会のことを書こうかな、と思ったのですが

私が、いい加減に まとめるよりも 遥かに良く かつ 素早いまとめがありましたので、そちらを紹介。

dev.classmethod.jp

私は、懇親会や その時に考えたことを ツラツラと綴ることにします。

勉強会

立ち上げ時の難しさと、継続するための難しさと、それぞれに施策を 現場の立場(つまり 知った顔のコンサルの営業トークではなく)で聞かせていただけたのは、本当に貴重な経験でした。

ごめんなさい、というところは「ドメイン駆動設計 失敗した事と成功した事」を聞いた後は、ちょうど 自分がアプリケーション層について 色々と考えているタイミングだったので、以降の話は 話半分しか入ってこなかったというところです。

感想と考察

ちょうど学びたかったことだった

アプリケーション層の実装について、ちょうど整理をしようとしているところで、個人的に色々と考えていたこともあって ドンピシャの内容でした。
ValueObjectやEntityの話は、DDD関連だと良く出てくるのですが、アプリケーション層については、ドメイン欠乏症にならないようにしましょう、という感じで その役割については「とにかく薄く」くらいしか無かったりするのですが、何を業務ロジックとするのか?ということについての考察を示していただけたのは 本当に有難かったです。

何をもって業務ロジックとするのか?

これは、なかなか難しいところ。
業務にとって意味あるものを 業務ロジックとする、というのはあるとしても、例えば「データが所得出来なかったとき」という表現で扱うロジックは その判定も含めて 業務ロジック? そうではない?
言ってしまえばケースバイケースだけれど、埋もれてしまった時に見つけるのが難しくなるくらいなら、変に「業務ロジックだけ」という分類をしないで「業務知識に相当するドメイン知識」「システムに近しいドメイン知識」として置き場所を決めておいた方が良いかもしれないと思うヒントになりました。

DomainServiceの役割

DomainServiceは、EntityやValueObjectでは表現できない業務ロジックを表現するもの、もしくは 複数のEntityを跨った業務ロジックに適用するもの、というのが私の認識だったのですが、BIGLOBEさんの 今の整理では DomainEventのFactroryとして使っているみたいでした。

確かに、そうすることで ApplicationServiceは スッキリとなっていました。

そこで気になったところと、それに対する自分の考察をツラツラと。

DomainService作りすぎてしまうのでは?

FatControllerからFatBusinessServiceになる、という話。
ApplicationServiceの進行役という役割は、どこまでシンプルにすべきなのか?という言い方でも良いかもしれないです。
ここについては、今回の話を踏まえつつ、自分なりに思うところもあり、考えるための良いヒントを頂けたので非常に良かったです。

DomainEvent とは?

私の理解では、ですが Repositoryで扱えるAggregateは基本的に1つ。
複数のAggregateにまたがった更新を行いたい場合に、
「このイベントでA(メイン)を更新したときに、一緒にB(サブ)も更新する」
という感じで「更新する」という行為そのものを DomainEventとしてオブジェクトにして Aの更新とあわせて Eventとして 通知しておいて、あとはシステム側で 例えば Observerだったり Interceptorなりが イイ感じに捌いて 結果整合性で なんとか満たす、という認識だったところ。

これは、既存のシステムがある場合、そうも言っていられないことが多くて「更新するためのオブジェクト」として 更新に必要な情報をかき集めて(これはEntityとは違う構造体)Repositoryに渡してあげることで、2つ以上のAggregate(もしくは、集約とかではなくて 単純にテーブルと言っても良い)を更新する という割り切りの産物なのかもしれない、と後から思いました。

あとは、メインであれサブであれ、結局のところ更新をするんだから分けずに全部 DomainEventに統一する、という考え方もあるかもしれません。

事前条件は どこで表現するのが良いのだろう?

ドメイン欠乏症の例で、ApplicationServiceに記述された前提条件について 確かに条件実装そのものを ベタで書くのは 止めるとして、DomainServiceの中に隠れてしまったら それはそれで ApplicationServiceにおける事前条件を確認するために DomainServiceを確認しないといけないとなるのは どうなのかな?と思いました。

Entity#verifyOrder は、OrderEvent を生成するための事前条件? それとも OrderApService#申込 の事前条件? 、同じように OrderDate#isInvalidも。

これは、私が ApplicationServiceって結局 どうあるのが良いのだろう?と、まさに悩んでいるところだったりします。

例えば、ApplicationServiceを使用する Controllerで、そのApplicationServiceが実行可能であるか 事前にチェックをしておこうと思った場合に、ApplicationService管理下の(publicではあるけれど)DomainServiceを Callするのが正しいの? という感じです。

私としては ApplicationService#validatePreCondition(entity) というものを ControllerでCallしたいというイメージです。

ざっくりいうと「このサービスの内部は知らないけど、依頼をしても良いのかい?」くらいしか Controllerとしては 意識しないという感じです。

もちろん、ApplicationServiceは 検証ロジックを呼び出す順番だけを管理して 実際の検証ロジック自体は 別クラス(DomainServiceや Domain)で実装します。

RepositoryImpl(Infrastructure層)にもロジックを実装する事もある

業務ロジックではない、条件分岐(?)は実装しているケースもあるとのこと。

この辺りは、私も悩ましいと思っているところで、例えば データ検索をして対象が無かった場合の戻り値への条件分岐を設けたいとして(データが無かったら実行時例外)、それは どうするのが良いの?という問題。
例えば、システム全体としてデータが無かった場合は、実行時例外をスローするので利用側で処理しましょう となり、かつ ApplicationServiceに条件分岐を書きたくないということだったら、あとはDomainServiceの出番になるわけですが、そうすると上述の DomainServiceまみれ、になってしまいます。

じゃあ、実行時例外はスローしない!としたところで、データの取得が出来なかったときに 例えばnullオブジェクトであれ、Optionalであれ返却するとしても、結局、条件分岐は必要です。

もちろん、条件分岐を設けることに意味のあるケースであれば DomainServiceで 実装するべきというのは 当たり前としても、常に必要というのとは訳が違います。

今、個人的に考えているのは、データ存在有無くらいで、業務ロジックとは言えないものであれば、Repository(interface)に defaultメソッドを設けて対応してしまっても良いのではないか?と思っています。
ちょっと試した感じでは、そこまで悪手でも無い気がしています。

ロジックを書くなら抽象クラスでしょう というところもあるのですが、抽象クラスは 拡張を目的としたものに使いたくて、DIの境界には使いたくないということと、インスタンス変数を保持できる仕組みだと 実装できる情報が増えすぎてしまうということで、まさに「ちょっとしたこと」しか実装できそうもない、interfaceのdefaultメソッドが ちょうど良いかな、と考えました。

Form

おっ、自分と同じ感じだ、ということで勝手に安心感を得てしまいました。
まぁ、それだけの話です(笑)

Converter

Enumによる変換クラスの実装は、学びが多かったです。
これ 一番初めに こうしよう と言った人、賢すぎ と思いました。

日本語を使っていることについても、対向システムの「表現」なので、変に自分たちの言葉に置き換え(英語化も含めて)たりせずに、そのまま使うというのは 納得感がありました。

語弊を恐れずに言うならば、対向システムの表現は 単なるラベルでしかない、という割り切りを持っておいた方が 変に言葉に流されないし 良いと思います。
また極端な話ですが、言語がアラビア語のような 不慣れなものだった場合、変に英語に翻訳して管理(アラビア語⇔英語⇔自システムドメイン)をするのか?ということを想像したら「まぁ、無いな。だったらアラビア語を翻訳して、それを ドメインで どう変換するのか考えるだけで十分」と思う気がします。

コードの中にマルチバイトが含まれることを嫌う人や、国際化対応ガーー、という人からすると、不快に思うかもしれませんが、少なくとも 私は この Converterにおける割り切りは 良い割り切りだと思いました。

Adapter

MyBatisは使っていないので分からないけど、DataStoreでDomainObject(Entity)に変換するというのは納得です。
この辺りは、自分がDataStoreを考えるときに参考にすると思います。
例えばJPAコンバーターでValueObjectから変換を頑張るんじゃなくて、がつっとAdapterで変換してしまった方がイイよね、というくらいの感じだと思っています。
なお、既存システムがあって DBの構造が複雑だと DxOを素直に準備する(作業的であったとしても)と割り切った方が、精神衛生上よろしい、と私は考えているので それとも合致すると思いました。

ユースケースという表現について

利用者において価値のある振る舞いはビジネスユースケースといい、システムにおける一連の振る舞いはシステムユースケースといっているとのことです。

この辺りは、私は

ユースケースじゃなくてサービスとしよう - システム開発で思うところ

として、割り切ってしまったところです。
そして、とりあえず このままの整理で進めてみようと思っています。

まぁ、この辺りは どう表現するのか?というところだと思うので良いのですが、いずれにしても ユースケース という言葉が意図するものについて 何かしら補足をしないと、人を悩ませるということは 再認識しました。

境界づけられたコンテキストとチーム構成

複数のチームにまたがって、 1つの境界づけられたコンテキストを扱うことはあるのでしょうか?
その場合は、どうやって認識を統一しているのでしょうか?

Ans.
またがって開発はしている。
リードエンジニアが、それぞれいるので そこで一旦検討して、それが 個々のチームに反映されて、そこから個々のチームで共有・設計をする感じ。

ということでした。
設計をチーム全体で、ということだったので どこまで「全体」とするのかな?ということでの質問でした。
なんだかんだいっても、やっぱり 何かしらの粒度で まとめ役(調整役)がいないといけないよな、という確認的な質問でしたが丁寧に答えていただきました。

ちなみに独自言語って?

詳細は、勝手に公開するのは良くないと思いますので臥せますが、本当に独自なものでした(笑)。

言語というよりも、定義という感じでしょうか?

定義を読み込むところは、何かしらの「言語」だと思いますが、少なくとも ロジックを表現するものは 独自言語という名前の定義 という感じだと思います。

増田さんの本 読みました?

読んだ人もいれば、読んだことのない人も という感じでした。

逆に EvansもIDDDもCleanArchitectureも増田さんの本も、その他多数 読んでいる人もいました。

私は 増田さんの本は読みましたが、他のDDDネタは ネットで まとまったものを見て それっぽくかじっただけなので まだまだ ですね*2

他にも・・・

西 秀和さん と帰り道に色々話をさせていただきましたが、私自身が まだ整理できていないことを 聞いて頂いた という感じで、どう文章にしたら良いのか難しく・・・。

全体を通じて

実際のロジックを示して話をしてくださったおかげで、具体的に色々と考えることが出来ました。
概念で「こんな感じ」ではなく、やっぱり コードで表現されたものを見ると、それを踏まえて さらに考察が深まるので本当に ありがたいです。
準備する側は、自分たちの実装を晒すわけで、結構 準備が大変だったりする(これは出してよい、出してはいけない。出すとしたら 分かりやすいように手直しをしようとか)と思います。
その準備のおかげで、とても分かりやすかったです。

改めてありがとうございました。

増田(@masuda220) さんと

isolating-the-domain のメンテナンスは?

github.com

増田さん自身としてメンテナンスは どういう感じでされているのでしょうか?

Ans.
教育用のライブラリとしてメンテナンスはするんだけど、 そのタイミングは 次の教育用に使う時に、がーっと見直しをする感じ(けど、それは今じゃない)

期待してます

上述のプロダクト以外で、個人的に 温めているものもあるみたいです。
公開される日を楽しみに待っています。

その他

「チームの中核になった人や立ち上げメンバーが去ってしまう…」というところについて 別の勉強会にて 会場を提供いただいた際にも仰っていましたが*3、なかなか難しいところです。
私のSIerにいた経験から勝手に考えたところとしては

社歴で役割を強制しない方が良いかも

例えば、X年目だからそろそろリーダーを経験しよう、みたいな空気の強制。

もちろん、組織として役割を委譲したいという本音も分かるし、立場を変えることで分かってくることも沢山あるし、その経験をもって改めてメンバーの立場になることで よりよいチームにもなるという経験則はあります。
例えば後輩育成(メンター)とか 結構大事で、今まで他の人がやってくれていたことに対して不満ばかり言っていた人が「あれ?あの上司は意外とやってくれていたな」とか「なんで わかんないの!→あぁ、以前の自分もそうだったのか・・・」とか経験を通じて学べることも多々あります。

POになりたい人もいれば、PMになりたい人もいるし、アーキテクトになりたい人もいれば、開発プロセスをやりたい人もいると思います。
いずれにしても 他者との関わりは増えるし、立場の違いによって シンドイけど学べることを経験しておいて 損は無いでしょう。

ただ、組織として経験をしてもらうことで肉厚なチームになるということと、社歴による役割設定 は必ずしもイコールではないように思います。
研究者タイプにゼネラリストもどきのことを長期にわたって強要すると、研究者タイプは 研究出来うるところにステージを変える可能性があるかもしれません。
むしろ 組織(この場合は チームやプロダクトよりも大きい意味)において、並行して考えておくのは 多様なキャリアパスを実現できる土壌づくりかもしれません。

経験を積ませて視野を広げてもらうという趣旨でのリーダー配置である場合、例えば スクラム風に 期限を設けて その期限の中で 精一杯 失敗を含めて試行錯誤してもらって、期限になったら続けたいか 他に取り組みたいことがあるか 改めて話す場を設ける ということを先に宣言してしまうというのもアリかもしれません。
目に見えない空気の強制よりは、マシかもしれません。

この組織なら 搾取されていない(もしくは この程度の搾取なら まぁ大丈夫)という実感を得られるのであれば、結果として 組織から離れなくても良いかな?となるかもしれない という勝手な想像です。

なお、待遇、報酬、裁量 については 別の話としておきます。
逆に 待遇、報酬、裁量 を求めるけど、他の人への影響力の実績を積むつもりが全く無いとなると、あとは組織(企業)としての在り方なので まさに一期一会になるのではないでしょうか。*4

プロダクトの成長を自分の成長にしてしまう

残った(?)側のメンタリティーとしては プロダクトの成長を、自分たちの成長として 協力してもらった人達は、それぞれの別のステージに行ったんだ という一種の割り切りかもしれません。
あとは、もし何かあれば戻ってきて経験をフィードバックしてもらえる土壌を並行して作っておくよ、という感じでしょうか?

組織に所属しても出来る外部発信

今回の勉強会のように、外部に対して情報発信をすることで 自分の組織の良いところ 改善するためのヒント みたいなものを聞く機会を増やすというのも良いかもしれません。
ひっそりと勉強会にいって、その熱に当てられて スッとフェードアウトされるよりは マシな気がします。
これは 昨今言われている副業禁止を止めて 別の業務経験をする機会を与える、というのも一つの手段だと思います。
もちろん、副業先の方に 引き抜かれる可能性は十分ありますが、フェードアウトされるよりは マシだと思います。
BIGLOBEさんの場合、社内システムなので SESや受託開発と違って 軸足となる側の会社だと思うので、やりようはある気がします。*5

開発経験をコンサル(外販)してみる

社内システムに閉じた経験しかできないという不満・不安に対して、DDD&Javaによる基幹システムの開発経験を 他の企業へ外販することで 開発対象の多様化を図るというのも あるかもしれません。
外販するのは経験です。実際の開発者が傭兵よろしく 他企業に乗り込む感じです。
これまでの アジャイルで得た知見や、エンジニア育成経験とその継続的な取り組みノウハウ、実績のあるDDDを支える開発基盤の分は 少なくとも 外販先よりも10年近いのアドバンテージがあり、それを 余すことなく提供してもらえるということであれば、悪い話では無いと思います。
例えば、BIGLOBEさんが 増田さんを およびしたように、呼ばれてはいないけど 売り込みに行くという感じです。
言うまでも無く 失敗すると思いますが、Java未経験でも2回目で それなりの成果を出せた背景を含めて 先方にも「おそらく短期的には失敗はする。けど、経験とチームが残って それが次の成長につながるというのが大事なところ。能動的に失敗をしましょう、でも、我々には その失敗を乗り越えた経験もノウハウもあります。大事なのは失敗した後で、それを乗り切るための併走者が我々です。」という感じです。

実際、アジャイルやDDDの導入において、何かしら社内政治は必要になるわけですが、その社内政治を「売り込まれたものを試してみよう」という言い訳にしてもらって現場に導入するという感じです*6

なお、外販で得た失敗経験は、内部にフィードバックをして、「立ち上げ時を知らないメンバーに疑似経験をしてもらう素材」として活用することで、立ち上げメンバーしか知らない暗黙知となった経験を 新たに作ることが出来ることも期待できます。

ちなみに

私は 待遇、報酬、裁量だけでなく、そもそも評価にも興味が無かったので 上長の方々にとっては非常にやりにくい部下だったと思います。*7

私の場合、石の上にも10年的*8な発想で 逆に時期が来たら 始めから会社は辞めると決めていたので、余計に 扱いにくいところがあったと思います。

なお、私の場合「リーダーをやりなさい」「火消に行きなさい」と言われたら、基本的に二つ返事で応えていました。断ったのは 昇級をエサ(?)にして組織としての役割を求められたケースだけです*9

とまぁ、そんな変な考えの人の思い付きなので、的を射ていない と思いますが せっかくなので のこしておくことにしました。

謝辞

会場を貸していただき、また貴重なノウハウを開示していただいたBIGLOBEさんには感謝感謝です。
エンジニアを募集されているとのことですので、興味があるかたは

crecruit@ml.biglobe.co.jp

まで、とのこと。
SESじゃなくて、自社プロダクト開発に関わりたい&組織としてDDDに取り組んでいるところ を考えている人は、話をするだけでもされてはいかがでしょうか?

さいごに

勉強会そのものから、随分と外れたことばかり書いてしまいました。
その辺りも含めて刺激を受けた良い勉強会だったと思います。

追記

Serviceについて、自分なりの整理をしてみた。

vermeer.hatenablog.jp


*1:無駄にドキドキした。見知らぬ人と話すのは やっぱり気が張る

*2:とはいえ、今後 読むかというと・・・

*3:http://vermeer.hatenablog.jp/entry/2018/06/19/133913

*4:転職時に 見せかけで売り込むことは出来るかもしれませんが、結局 渡り鳥になるだけのように思います。それが目的であれば それは それで良いとは思いますが

*5:SESや受託開発メインの場合は、傭兵的なビジネスモデルなので 軸足側になるには 自社プロダクトを持つなどしないと難しいというのが個人的な理解です

*6:そもそも外販の場合、買い手は、相手企業の社長だから必然的にトップダウンです。

*7:個人的には概ね上司の方々には恵まれていたと思っていますが、逆の立場で考えたら まぁまぁ扱い辛かったと思います

*8:3年だと仕事を覚えることは出来ても、サラリーマンの辛さ的なところを実感するまでには至れないと思っていたので、もう辞めたい!と思っても 10年は頑張ろう という感じの期限設定でした。実際 8年目くらいの時と、実際に辞めることを決める直前、それぞれ種類は違いますが「もう限界かも」という局面を経験できるくらいまでは そこそこ走ったつもりです

*9:最終的に、自分が昇級しないと 上司や後輩の昇級に悪影響があるかも、という別の理由で ある一定のところまでは昇級しましたが、本質的には本意ではなかったという感じです