CLOSE

先行開発!クリーンアーキテクチャ -- ゼロから始める新規開発(全5記事)

Javaでクリーンアーキテクチャする方法 Part.5:クリーンアーキテクチャの課題と解決

開発者にはしばしば必要なものがないという状況が訪れます。デベロッパーエバンジェリストの成瀬氏が、実際に10名弱のチームで新規プロダクトを作る際にクリーンアーキテクチャの構成を実践してみた話をしました。 Part.5はクリーンアーキテクチャの課題と解決について。動画はこちら。

Presenter は捨てよう

みなさんも課題わかったかな。1個目が、まずMVCフレームワーク合っていないよね。2つ目が、DIする、ユースケースを作るたびにControllerを修正しなきゃいけない。さらにもう1個が、とにかくいっぱいクラス作るの大変だよね。

それぞれ解決してみましょう。まず1個目、Presenterが使えない問題ですね。これJavaで話していますけど、僕もともとC#とかでこれやっていたんですね。そのときにいろいろ考えた結果、ちょっと怒られるかもしれないけど、捨てました。具体的にどうやったかというと、素直に戻り値を返しています。

なんでこれでよしとしたか? まずInteractorがOutput Dataを生成するところまではいいんです。それを実際にBoundaryに渡すか渡さないかの違いです。インターフェースに渡すか渡さないかの違い。「これって戻り値でデータ返すのとほぼ一緒だよね」みたいな。

Output Dataを作っているかぎりは、そこで参照が切れているんですよ。データを1回移し替えているので、内側に依存していないんですよね。ということで、Output Dataを作っているかぎりはもうその依存が切れているから大丈夫でしょうと。唯一考えるべきは、ControllerがViewのために若干Fatになる。でも、ユースケースの実行した結果オブジェクトを受け取って、それをView Modelに変換する処理が入る。

ここで若干Fatになるけど、一番大事なことは、最初のほうで言ったとおり、Controller、依存の方向を真ん中に向けて、ビジネスロジックを守りたい。ちゃんとアウトプットを定義しておけば、フレームワークに依存しないで済むので、差し替えできますよね。重要なドメインロジック守れますという感じで、逃げました。

(コメントで)「いいと思います」、ありがとうございます(笑)。

実を言うと、これね、解決方法あるんですよ。それは既存のフレームワークを全部駆逐すること。作ればええねん。PHPで僕も軽く作ってみました。楽しかったけど、これ流行らないだろうなと思いました。

気になる方は、僕のPHPでのクリーンアーキテクチャの発表とか、PHP Conferenceのときにしゃべったやつとか、あとはPHPのQiitaもあるので、そこを見てみてください。けっこうエグい改造をしています。

解決方法がなかったのでMessage Busを作った

次いきましょう。Controllerのフィールド増えすぎ問題。Controllerがフィールドいっぱい増える、@Inject Hell。どうしたかというと、これね、実を言うと、Javaとか固い言語特有の問題なんですね。

PHPとかって、例えばメソッドインジェクションってあって、メソッドのほうでこれができるんですよ。それ使うとこれが解決するんですけど、Javaのとき解決方法なかったので、じゃあどしようかって思って作ったのがMessage Bus。

どういったものかというと、例えば思い浮かべてほしいんですよ。Commandオブジェクトを作る。ユーザーをAddコマンド、ユーザーを追加するコマンドを作る。これってユーザーを作ることを期待していますよね。つまり、何が言いたいかというと、「処理の実体なんでもよくない?」という。

(コメントを見て)今フェイサンが言っている「アプリ系(Android/iOS)とかはPresenterがあると便利」そうそう、そうなんですよ。アプリは便利なんですよ。変えられるんですよ。AndroidとiOSで。

でも、そのときにね、Webで妥協せざるを得なかったので。今はWebで作った戻り値が返るバージョンでも、そのままiOSとかにもっていけるので、JavaだからAndroidか、にももっていけるので、そこがすごくいいですよね。ほら、今ちょっとさりげなく言いましたけど、WebからAndroidにクライアント変えられちゃうんですよ。戻り値、そのInput Data以降、そのままコードをもっていけば動く。というイメージです。

同じ動きするんだったら処理の実体は何でもいい

ちょっとごめん。話がズレました。ユーザーを生成するCommandオブジェクトを作るってことは、そのコマンドが実際に実行されるという結果を期待しているんですよ。さっきテストでモック作ったじゃないですか。仮で動いても、とりあえずそれが動けばいい。そうすればフロントがテストできる。

つまり、処理の実体は本番のときはちゃんとやってほしいけど、何でもいいんですよ。基本それが同じ動きするんだったら、なんでもいいですよね。

どういうことかというと、ユースケース、MessageBusというのを作って、そこにUserGetList InputData、さっきの入力データですね、渡すと、このときにもうこれを作っているってことはUserGetList OutputDataを期待しているんですね。

実際にこれを渡すとそれが返ってくる。処理はわからない。何が起きるかわからない。具体的には中身でこうやって、UserGetList Interactor……Bus。我々が乗るバスをイメージしてください。バスの中に乗っている乗客の誰か。UserGetList InputDataを見て、「あっ、それ僕ができます。僕が処理しますね」って、UserGetList Interactorちゃんが手を挙げて処理して、UserGetList OutputDataへ出力する。そういうイメージ。

今「Presenter層がいらないなとか思っていたけど、アプリ系だからこそかな?」と言ったのは、まさにそうだと思います。アプリとWebで考え方は変わっています。ただ、結局はOutputDataにしてしまえば依存が切れるので、それでもいいかなと僕は認識しています。ただ、うんね……。ちょっと歯切れ悪いね。

続きいきましょう。例えば同じようにUserAdd InputDataを渡したら、乗客のUserAdd Interactor君が「僕やりますよ」って言って処理して、UserAdd OutputDataに作って出す。

(コメントで)「中身は空っぽ???」、そうですね。中身は空っぽなんですけど、それはさっき言った、最初に登録しておくDIの設定ですね。最初にMessageBusにUserGetList InputDataが来たらUserGetList Interactorを使ってくださいねというBusの住人を決めているんですよ。

具体的にどうやってやっているかというと

(プログラム例を見せながら)具体的にどうやってやっているかというと、よく見るとここね、C#のコードでごめんなさい。RegisterUseCase。CircleCreateInputDataが来たらCircleCreateInteractorを使ってくださいって書いてあるんですね。UserData、UserGetInputDataが来たらUserGetInteractorを使ってください。

じゃあテストのときどうやっているかというと、インメモリモジュールを見てみましょう。インメモリのモジュールは、これですね。UserGetInput DataがBusに渡されたときはUserGet……あっ、しまった。そっか。これもあれか。こっちでインメモリが動くからか。っていうと保存する場所がインメモリなので、本番用でいいんだな。ここのやつをStubとかに変えたりできるって感じですね。Stubとか設定してうまく動くようにする。

(コメントで「あーOCP なるほど」)そうそう、Open Closed Principle。

(スライドを指しながら)ここのoutputData、実際にそのMessageBusを使っているパターンなんですけど、見るとUseCaseBusというのが定義されていて、これがDIされています。さっきわかったように、事前に設定されています。そうすると、ここにUseCaseBus、実体が入ってきて、これを使って、次の、ここですね、inputDataをデータをここで生成します。そのinputDataをbusに渡せば、結果としてoutputDataが返ってくる。こういう動きする。

処理自体は何が起きるかわからないんですよ。busの中の住人なので。これだったらすべてbusなので、さっきみたいにアクションごとにフィールドいっぱい書かなくていいんですよ。inputDataを渡せば返ってくる。

Javaで難しいところがこの型をなんとか保持するってことですね。気になったら実装を見てみてください。

退屈なことはプログラムにやらせましょう

(コメントで)「どっかに定義をたくさん書く場所が移動しただけ」。まさにそのとおりです。じゃあそれをなんとかする方法もこのあとあります。もちろん変えるときは修正していますけど、実際この定義するものが多いという問題があるので、どうすればいかというと……。

僕たちよく言うじゃないですか。「コードを書くときに努力しましょう」って言うんですけど、さっきのこれってやっぱ退屈なんですよね。これけっこうやってというと、おそらくプログラム開発者チームの人からけっこう反感を買います。じゃあどうすればいいか? 我々退屈なことをどうするか?

退屈なことはプログラムにやらせましょう。ツールを作ればいいんですよ。入力すればさっきのテスト用のモジュールとか、あとはDIの設定とかも自動で作る、さっき言った「書くの面倒くせえな」ってやつを全部やってくれるツールを作っちゃえばいいんです。

今回の社内のCA用、クリーンアーキテクチャ用のスキャフォールディングツールの名前は「NORIO」にしました。やっぱり名前って大事ですから、ツールとかもやっぱりキャッチーな名前にすると愛着が出るんですね。NORIO、なんかかわいいですよね。

「NORIO」はどういうものかというと、「NO Repeat creating Inteface Over many time. (なんでもインターフェース書かなくていいんだよ)」の頭文字を取ったかたち。いい名前でしょ?

ぜんぜん関係ないけど、これうちの「のりお」って言うんですよ。だいたいこういうツールの名前って、まずどういう名前にするかを決めたあとに、そこにがんばって当て字するって感じなので、こっちが先ね。のりおちゃんかわいいね。

実際どういうことやっているかというと、これProduct用。本番用のDI設定。よく見るとUserAddInputDataとかが渡されたときにはUserAddInteractorを使いなさいね。同じようにDeleteのときにはDeleteInteractor。そして、デバッグのときは、UserAddInputDataを渡されたら、StubUserAddInteractorでがんばりなさい。とりあえず動かしなさい。この設定、これも自動で作る。

だからこれ、コードを読み込んで、そしてコード差し込んで、またファイル出力って感じですね。よくあることです。

そしてこのDebug用のStubモジュール。こういうのもコードを今のツールで作っちゃえばいいんですよ。ツールで作ったあと、やっぱり動かないと格好悪いので、仮で動くようにしました。not implementedで例外が入ってもいいんですけど、ここでJsonsLoaderってあるじゃないですか。UserGetListOutDataのクラス渡す。これもうこのままスキャフォールディングされるんですね。

何やっているかというと、まったく同じ名前でjson形式のファイルができるんですよ。ここにjson形式で書くと、データを入力できるんですね。だから仮で作ったら、もうこれがすでに設定されているので、ここをちょちょっと欲しいデータを書き換えれば、そのままプログラムが動く。なんなら1回目、2回目、3回目って分けて書いておけるんですよ。もちろんサーバ起動中にこれ中身を変えれば、返るデータを変えられるので、例外を起こす、エラーのデータとかも返したりできるので。

それってすげえテストしやすくない? フロント、最近のUIフレームワーク使っているとそんなこといらないんですけど、バックエンドを含めたBackend for Frontendとか、バックエンドを含めて導通を確認しつつやりたいときは、こういうのがあるとここのコードを書き換えるだけでプログラムをテストできるので、すごい楽です。

実際の開発の流れ

実際の開発の流れを見ていきましょう。「こんな感じでお願い」ってされたら、「OK、わかりました。やりましょう!」。そして画面から必要なデータを検討します。

データがわかったらツールでスキャフォールディングですね。ユースケースの名前を決めたり必要なデータを入力します。

そうすると全部スキャフォールディングされて、フロントはいろいろなパターンがありそのときのパターンでテストデータを用意するのは大変なので、さっきのJSONS、デバッグモードでとりあえず動きだけ確認して開発できます。最終的には確認するんですよ。でも、とりあえず動かせます。

しかも、このJSONSファイルは、ファイルなのでテスト用にデータを残しておけるんでず。例えばいろいろなユースケースを全部組み立ててできあがるエラーとかあるじゃないですか。その流れを模したセットを用意しておいてどっか保存しておいて、必要なときにそれを全部引っ張ってもってきて置いておけば、同じ動きが確認できる。けっこう楽ですよね。

(コメントで「このようなテストコードをつくるってところを理解してもらえないんだよなー」)けっこうテストコード作るのをなかなか理解してもらないのか。そっか。(コメントで「おぉ! JSONからコードになるのはうれしい!」)でも、JSONからコードになるのがうれしいよね。けっこうコードに公開しているので、よければ見てみてください。けっこう適当です。

あとは、これやっていたときに、やっとAPIチームから「APIできたよ!」「おっ、待ってました」ってときに、やっとそれのAPIを叩くようなモジュールに差し替えたり、データベースを使うようにしたり、まさに先行開発クリーンアーキテクチャという話です。

実際にこれやったときに、僕、データベースがいくつかあったんですけど、1つのMongoは本当にリリースの1週間前まで来なくて。でも、これでとりあえず動かしていたんで、最後差し替えて全体的な動きを通して確認して終わり。もう1週間前だろうが、間違えなければうまくいく。最後の最後で間違えるときもありますけど、もちろんテストすれば大丈夫です。

おお、今Twitterのほうでね、「クラス多い問題→社内ツールでコード自動生成」という発想、けっこう大事です。ぜひとも使ってみてください。

今日のサンプルコード、これはあとでYouTubeの概要欄に貼っておきますね。サンプルコードがあるので見てみてください。あと、これ以外にもさっき貼った……俺が見ていたコードね、これC#なんですけど、きっとJavaの人も読めると思います。

僕が書いた本の『ドメイン駆動設計入門』のコードなんですけど、ここに「CleanLike」というプロジェクトなんですね。同じプロジェクトなんですけど、レイヤードアーキテクチャとかいろいろな書き方していて、それのクリーンアーキテクチャで書いた場合、こういう感じだよというのもあるので、こっちほうがかなりサンプルコードが豊富なので、ぜひとも見てみてください。

アーキテクトの役目はプロジェクトの進め方に責任を持つこと

では最後にね、ちょっといい話で締めましょうかね。「こういったアーキテクチャの役目って何なんだろうな?」ってところなんですよ。最初はヘキサゴナル話しました。クリーンアーキテクチャ、やっぱりこれに役目があるんですよ。どういった役目なのか?

僕がイメージしているアーキテクチャの役目って、やっぱり電車とかのレールをイメージしているんですよね。例えば、迷路って複雑じゃないですか。でも、迷路がこんな感じだったらもう簡単にゴールできますよね。毎回この流れだったら、毎回同じようにできますよね。

プログラムを書いているときってやっぱりすごく自由なんですよね。僕はそんな自由さもすごく大事だと思うし、それこそがプログラマーのクリエイティビティというか、クリエイターなところだと思うんですね。

でも、自由なんですけど、サービスを作っているときにみんながこれ、まっさらなところで自由に動くということは、ある意味探検になっちゃっているんですよ。「俺ちょっとあっち行ってみるわ」「じゃあ俺あっち行ってみるね」。さっきのわかりやすい迷路、常に同じ方向じゃなくて、みんながいろいろなほうに進んでしまう迷路。探検なんですよね。

ここにじゃあ自由をちょっと制限します。制限するんだけど、そこにレールを通すんですよ。レールを通した結果、探検が快適な旅になるというイメージ。それがアーキテクチャです。

だからこそ、クリーンアーキテクチャ、こういったアーキテクトの役目というのは、やりたいことはアーキテクチャを採用することじゃないんですよ。アーキテクチャを採用して、チームのみんながいかに快適な旅ができるかをサポートすることです。

だから、さっきのツールとか作るんですよ。アーキテクチャ使えって言うだけじゃなくて、「ツールを用意しました。これならきっとみんな楽でしょ」。もちろん自分も使う。ドッグフーディングして「あっ、ここやっぱり使いづらいな」って作るんですね。

実際これを開発したときも、サービスをやったときも、開発を「2週間、時間くれ」ってツールを作ったりしました。それこそがアーキテクチャの役目です。もちろん一緒にやっているときに自分もコード書いてみて、「ここやりづらいな」ってどんどんツールを開発していくんですよ。

最初NORIO君は今のスキャフォールディングしかなかったんですけど、ほかにもいろいろなことができる。例えば英語とか日本語、言語対応をうまくするためのツールとかいろいろな機能を作って【。

とにかくアーキテクトの役目はプロジェクトの進め方に責任を持つことなのかなと思っていて、アーキテクチャを使ってみんながすてきな旅を楽しめるようにするのが、アーキテクトのやりがいかなとは思っています。なんか話変わったね(笑)。そんな感じです。

ビジネスを中心にしてシステムを組み立てる

やっぱりクリーンアーキテクチャを見て思うのは、依存関係逆転の原則をとにかく重要視していることです。もちろんSOLID原則の1つです。SOLID原則は全部重要なんですけど、その中でも依存関係逆転の原則が大事と。抽象が詳細に依存してはならない。詳細が抽象に依存すべきである。

どういったことかというと、インターフェースを使って依存関係を逆転するんですけど、伝統的なプログラミング開発を思い浮かべると、ロジックの中にSQLとかを書いたりしていましたよね。データを保存したり取得したり。ビジネスロジックの中でデータ保存・出力できる。

そうするとおもしろいことに、詳細、データベースですね。抽象、ビジネスロジックって抽象的ですよね。抽象・詳細、このメタファーはどういうものかというと、我々人間と機械の間でどっちに近いかって話なんですよ。

機械に近ければ近いほど、低レベル・低レイヤー・詳細。そして、我々人間に近ければ近いほど、抽象である。そのときに、データベースビジネスロジックとデータベースを比べたら、ビジネスロジックが抽象、データベースは機械に近いですよね。なので、詳細である。

依存関係逆転の原則でしたかったことは何かというと、逆転することではなくて、それで成し遂げたかったことは、ビジネスロジックに書かれたデータベースのコードって、データベースが変わったらビジネスロジックが変わっちゃいますよね。つまり、低レイヤー、詳細の変更が抽象に波及してしまう。

じゃあどうすればいいかというと、これを逆転させたいんですよ。抽象が変更の方針をもつ。データベースが変更されても抽象を変更しない。そのためにやる方法が依存関係逆転。DIとかを使って依存関係を逆転する。

さっきみたいなDIコンテナを使ってSQLを使うリポジトリを使うパターンと、場合によってはStubでインメモリで動くものを使う。でも、そのときにビジネスロジックは変わらないですよね。インターフェース使っているから。依存関係が逆転しているんですよ。

伝統的な手法では、データベースに依存して詳細に依存してしまった。これを逆転させて、主導権、抽象的な我々人間に近いものが変更の主導権をもつべき。両方が抽象に依存するようなことに……依存することによって、それが達成できるというお話でした。

だからこそ、このようにアーキテクチャを採用する、アーキテクチャをメインに据えるんじゃないって話なんですよね。アーキテクチャを使ってやりたかったことは依存関係逆転の原則。ビジネスに主導権をもたせたい。技術的な決定、詳細です。ビジネス、抽象です。

だから、クリーンアーキテクチャだろうがヘキサゴナルだろうが、それがうまく回っていればなんでもいいんです。レイヤードでも。とにかく言いたかったことは、そうやってビジネスを中心にしてシステムを組み立てる。

そうすることによって、冒頭でお話しした、フレームワークが変わってもビジネスロジックそのまま使えます。データベースが変わってもビジネスロジックを使えます。そして、ビジネスの変更によってビジネスロジックを変えると、データベースも変わりますよね。

という感じでした。以上、みなさん、おつかれさまでした。疲れた(笑)。

続きを読むには会員登録
(無料)が必要です。

会員登録していただくと、すべての記事が制限なく閲覧でき、
著者フォローや記事の保存機能など、便利な機能がご利用いただけます。

無料会員登録

会員の方はこちら

関連タグ:

この記事のスピーカー

同じログの記事

コミュニティ情報

Brand Topics

Brand Topics

  • 大変な現場作業も「動画を撮るだけ」で一瞬で完了 労働者不足のインフラ管理を変える、急成長スタートアップの挑戦 

人気の記事

新着イベント

ログミーBusinessに
記事掲載しませんか?

イベント・インタビュー・対談 etc.

“編集しない編集”で、
スピーカーの「意図をそのまま」お届け!