CLOSE

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

Javaでクリーンアーキテクチャする方法 Part.4:クリーンアーキテクチャの処理の流れ 

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

処理の流れを確認する

では処理の流れを確認していきましょう。どういった処理の流れでやるか。いろいろなものがありましたけど、なんとなくこのData StructureとInterfaceがあって、あと実装クラスはいっぱいあるなということだけわかればいいです。

最初はどこからいくかというと、まずControllerから入りますよね。Controller、UserController、ここで入力のデータを生成しています。具体的にはボタンを押した事実を、コンピュータがわかる、ビジネスロジックがわかるかたちに、アプリケーションがわかるかたちに、Input Dataに変換しています。インスタンス化して。そしてそれをInput Boundaryを呼び出すことで、引き渡している。Callしています。

そしてInput Boundaryはこういうコードでしたよね。インターフェースです。だから、これが実際に処理するのは、これが処理するんじゃなくて、これを移譲して、実際に処理するのはUse Case Interactor。この処理ですね。さっきのデータベースにデータを保存したりする処理です。

やっていることは、ドメインオブジェクト、Entityと呼ばれるもの、ビジネスのオブジェクトを生成して、これをまたData Access Interfaceを呼び出すんですよ。そいつに対して「保存お願いします」って呼び出します。

そうすると、Interfaceなので、このインターフェースはそれの処理の実装クラスに処理を移譲します。Repositoryクラスの中で実際に……O/Rマッパーなりなんでもいいです。とにかくデータを保存すればいい。保存するために、これはO/Rマッパーを使ってデータベースにアクセスしています。ここまでいいかな。

インターフェースを実装すると複雑になるって言う人もいるんですよね。慣れの問題ではあると思いますよ。クラスをいっぱい書くことに慣れていない方はやっぱり複雑に感じちゃうよね。

重要なのは、単純なことがシンプルじゃないってことなんですよね。「複雑さ」の反対って「単純」ってよく言われるんですけど、「複雑」と「シンプル」だと思っていて、やっぱりそこはちょっと違うのかな。けっこう慣れなので、なんとかやるしかないですよね。

重要なことは、整理整頓されているかどうか

データを保存する処理を投げて、そのあとです。出力データを生成します。結果、今回で言うと、生成したユーザーのuuid、今回ランダムに作っています。それを返すために生成します。それを出力データとして作って、その出力データをOutput Boundaryに渡す。Output Boundaryはインターフェースなので、それの実装クラス、PresenterにDelegateします。処理を移譲します。

これ、こういう流れです。あとはもう Viewに渡すだけ。今の流れ、アーカイブがあるので、また読んでください。もしくはスライド見るだけもわかると思います。

そうそう、(コメントで)「こうやって全体図がわかれば、コードの量が多くてもかえって見やすくなる」、まさにそうです。重要なことは、何がどこに書いてあるかわかること。まとまっていることはわかりやすくないんですよ。例えば1万行って書いてあったらみんな引くでしょう? それが100行のクラス、100個なのが……。

で、整理整頓されている場合なんですよ。重要なことは、整理整頓されているかどうか。されていないと、1つの……例えばよくあるのが、みんなフォルダー作るのが怖いんですよ。俺フォルダー切りまくるんですね。1つのフォルダーに100個クラスあると、すごいわからない。何を見ていいかわからない。名前でなんとか制御していてもやっぱり見づらいんですよ。でも、それを「フォルダー作って、10個に分けて、10個に分けて」ってやるだけでも、100個が1つフォルダ10個ずつ、ちゃんと分けていればわかりやすくなるってイメージですね。

とりあえずテストはできる

話を戻します。今日はクリーンアーキテクチャの話をするんじゃないです。クリーンアーキテクチャをどうしたかって話なんです。今日の先行開発どうやってやるのかというと、とりあえずフロント組みたいなってときに……わかるこれ?

さっきのInteractor。実際にユーザーを生成する処理。生成して保存する処理というところを、Stub、仮の動きをするオブジェクトを作っておくんですよ。そうすると、この処理で実際にデータ保存されないんですよ。データストアがないし保存されないけど、testってIDは返ってきますよね。

プログラム、フロントエンドをとりあえず作っておけば、サーバロジックできてなくてもtestってフロントに返せますよね。フロントを動かせる。

あとは、例えば例外のときの動作確認。例外ってけっこう動作確認難しいんですよ。実際のデータベースを使って例外を起こそうとすると、整合性のあるエラーのデータを作らなきゃいけなくて、それってすごく難しいんですよね。しかもそれ本番でやりたいんだとすごく大変ですよね。

そのときにどうすればいいかというと、こうやってクラスを作ってすぐ例外を投げちゃえばいい。もちろん例外がちゃんと投げられたらテストは別にしなきゃいけないんですけど、「フロントでこのRuntimeExceptionが出たときの動き見たいな」とかできるんですよね。なんとなくメリットある感じしてきましたよね。

ほかにも、今日の最初の話で、データベースはまだ選定中だけど、ロジックを組みたいときにどうすればいいかというと、これはデータベースがなくても動くんですけど、さっきみたいにこのインメモリ、このMapね。ハッシュテーブルを使ってとりあえずデータを保存するやつ。

インメモリなので、このプログラムずっと常駐して……常駐はしているんですけど、……シングルトンね。このオブジェクトを使い回すんですけど、そのロケールデバッグ中はインメモリに保存できます。だから、サーバを落としたら消えますが、とりあえず仮でデータ保存してテストはできるんですよ。

初期段階で実際に使うユースケースを設定するクラスを作る

そう、今Twitterのほうでね、「Interfaceを含めた全体像を図で俯瞰できると……」、そうそう。今の流れが見えるとインターフェースが必要なのが客観的に理解できると思います。そうそう、今おっしゃってくれた、「UseCaseのStubを作っておいて、フロントで動作確認できるようにすると……」、そう、まさにそのとおりです。

いや、これならすばらしいじゃないですか。もう勝ったな、ガハハ……本当に?

みなさん、Controllerのときに実はこの「return ?????????????」ってなかったんですよ。何が言いたいかというと、我々がWebアプリを作っているときは、必ずMVCフレームワークってレスポンスを返さなきゃいけないんですね。つまり、さっきの出力をPresenterに渡してViewを表示するということは、うまく動かない。

もともとクリーンアーキテクチャ自体はiOSクリーンアーキテクチャって、iOS向けに作ったんですよね(※のちに勘違いだったとYouTube説明欄で訂正)。iOSはもちろんPresenterがうまく機能します。Webアプリ、WebはHTTPリクエストレスポンスなので、どうしても、Presenterってリターンしなきゃいけないんで、Presenterがそぐわない。

今ちょっとね、Twitterのほうでも、DIコンテナとか、あと今コメントでもいただいた、「インターフェースの実装クラスは差し替えどうやるんだろう?」ってあるんですけど、どうやってやるかというと、例えば設定ファイルにDI、実際に使うユースケースとかを設定するクラスを作るんですね。プロダクションのとき、本番のときは実際に本当のやつを登録。ローカルで動かすときはモックのものを使うように登録する。

そのDIコンテナってご存じかな。DIコンテナがどういったものかというと、さっきの……ちょっと戻るね。ちょっとわからなかった方がいるので。これ……あっ、そう、これこれ。これuserAddUseCaseはインターフェースです。なので、ここに設定されるものは何も決まっていません。

Javaの場合、こうやって@Injectってやると、インターフェースをどこかで、例えばプロジェクト、サーバのスタートアップで設定しておいたオブジェクトがここに入る。もしくは生成してバインドされる。代入される。そういうDIコンテナというのがあります。

要するに何が言いたいかというと、毎回毎回ここを自分で設定するんじゃなくて、プロジェクトの初期段階、実際実行した瞬間に設定しておける機能があります。

詳しくはコードにも実際それやっているところあるので、それをあとでお見せするか、GitHubを見ればたぶんわかると思うんですけど、スタートアップのところで設定しています。1回設定しておけばそれがずっとそのサーバ起動中使い回されるので、もう毎回毎回そのインスタンスを決めなくていいよね、というものです。

設定ファイルとか扱ったり、実際クラスでそのままやったりすることもあります。ローカルで実行するときはモックのユースケースをバインドするように、このInjectの中に代入するように設定します。

実際のプログラム例

本番のときはプロダクション用のUse Case……あのInteractor、実際の実装クラス。あとインメモリで動作するデータベースとかを使うようにしなさいとか、実際にMySQLを使って保存するモジュールを使いなさいとか、そういう感じでやっていきます。なんとなくわかったかな。

DIコンテナを初期で設定する。ちょっとね、先に、これはたぶんそこが気になっちゃうと意外に進めないかもしれないので、ちょっと今コードを見せよう。ちょっとごめんね。

みんなわかっている、ここらへんすでにいろいろやっている人はわかっていると思うんだけど、けっこう気になっちゃうとあんまり進めなくなっちゃうからね。今パッと出てきたのがC#なんですけど、C#とJavaほぼ一緒だから大丈夫だよね。

(プログラム例を見せながら)例えばなんですけど、今これはインメモリなので、デバッグで動く用ですね。とにかく本番でデータベースに保存しなくても動くようにしたいパターンですね。データを保存するとき、テストのときはこうやってインメモリで動くモジュールを使ってくださいねって設定します。

反対にじゃあ本番のときどうするかというと、本番は例えばUserRepository、データを保存するモジュール、インターフェースが求められたら、SQLのユーザーリポジトリ、SQLでユーザーを保存するようなリポジトリ使ってくださいね、モジュール使ってくださいねという設定ができます。そういうイメージです。

課題をどうするか?

このMVCフレームワークがあんまり合っていないという問題が1つあります。そして次、これ。さっき言ったDIの設定でこうやってDIをいっぱいする。MVCフレームワークを使うコントローラって、アクションがいっぱい書いてありますよね。このアクションごとにユースケースを作る。

つまり、それぞれこうやって@Injectする。DIする。フィールドにユースケースをもつ。これ@Inject Hellとよく言うんですけど、これつらいよね。だってユースケース作るたびにControllerを修正するんだよ。これは大変だよね。アクションが増えるたびに。

あともう1個は、まずこれで1個のロジックをつくるときに何をやらなきゃいけないか、思い浮かべてほしいんですよ。まずControllerを作ります。それはいいでしょ。必要だから。

その次。Input Dataを定義します。そして、Input Boundaryを定義します。さっきのInterfaceね。Input Interface。そしてUse Case Interactorで実際処理するところを作ります。出力のためのデータ構造体を作ります。出力インターフェースを作ります。そして、Presenterを作ります。そのあとView Modelを定義して……つらいよね。つらいよねー。

これ明らかに嫌がられるやつだよね。こんだけファイル定義しているのに「よろしく!」ってやってもさ、みんなやりたくないよね。

じゃあ今回のついに主題ですよ、やっと。どうするか? 今のが課題です。

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

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

無料会員登録

会員の方はこちら

関連タグ:

この記事のスピーカー

同じログの記事

コミュニティ情報

Brand Topics

Brand Topics

  • 1年足らずでエンジニアの生産性が10%改善した、AIツールの全社導入 27年間右肩上がりのサイバーエージェントが成長し続ける秘訣

人気の記事

新着イベント

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

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

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