サーバーサイドTweetこんにちは、delyコマース事業部エンジニアの小川です。先月11月に入社し、エキサイティングな毎日を過ごしています。Qiitaの24日目の記事です。「ソフトウェア開発」がほぼ同時に進行しており、プロジェクトにおける確定要素と不確定要素が複雑に絡み合っています。スピード重視でゴリゴリ実装していくのも興奮しますが、変化に耐えづらい実装をしてしまうと、その後の開発スピードに影響していまい、事業のスピードが落ちるなんて事にもなりかねません。そこで、プロジェクトの保守性や拡張性をあげるには、どういった設計をしたら良いかを、OSSであるGitLabを一例として見ていきたいと思います。以外のどんなディレクトリ(=クラス)を導入しているか、それがどんな役割を担っているかを見ていきましょう。それではいってみましょう。最後にGitLabが導入しているディレクトリGitLabはRailsで実装されています。app配下のディレクトリで、デフォルトで作成されないものを抽出すると以下のようになりました。findersさまざまな条件に基づいたコレクションを取得するクラスgraphqlGraphqlに関するクラスpolicies権限確認系に使われるクラス。独自のDSLで実装されている。presentersviewに関わるロジックやデータを持つオブジェクトをviewに提供するクラスserializersフロントで使われるJSONを構築するためのビジネスルールをカプセル化しているクラスuploadersCarrierWaveに依存しているUploaderservicesビジネスロジックが取りまとめられているクラスvalidatorsActiverecordが提供しているカスタムバリデーターworkersSidekiqのWorkerクラス今回はこの中から、個人的にあまり導入したことがない、finders、presentersあたりを見ていきます。findersfinderクラスは「さまざまな条件に基づいたコレクションを取得する」責務を持つクラスになっています。params)下記のように実装すると、よりモデルを薄く保つことができるよ!っていうイメージですね。filter).executeGitLabのFinderクラスは、基本的に#executeのみをパブリックメソッドとして持っているみたいです。実装を見ていくでは実際にProjectsFinderを例に挙げて見ていきましょう。どこで#executeが実行されているか探してみます。ありました、Admin::ProjectsControllerで以下のように実行されています。.page(finder_params[:page])paramsにfinder_paramsを、current_userにはcurrent_userを指定しています。find_paramsは、取得するプロジェクトの条件、つまりフィルタリングするパラメーターや、ソートの条件などのパラメーターを含めています。ProjectsFinderの実装はどうなっているのでしょうか。#initializeには、paramsとcurrent_userとproject_ids_relationをキーワード引数で渡します。project_ids_relationend#excuteの実装は以下のような形です。sort(collection)end変数collectionに、フィルタリングのベースになる、プロジェクトのコレクションを代入しています。その後、#filter_projectsでプロジェクトのフィルタリングをおこなった後、#sortにて結果のソートをおこなっていました。フィルタリングとソートの条件は、#initializeの時に渡したparamsで指定しています。メリットになりそうな事パブリックメソッドである#executeが見通しがよく、コードリーディングしやすいと感じました。デルメルの法則にも違反していなく、依存も少ない(浅い?)と言えそうです。ProjectsFinderに実装されている、多くのprivateメソッドがmodelにも実装されることになります。modelになってしまう要因の一つだと思います。取得のロジックが単純なうちは良いですが、上記まで複雑になってきたり、必要なパラメーターが増えてきたら、Finderクラスの実装を考えていいかもしれません。プロジェクトの初期段階でも、取得系の実装が大きくなることが分かっている、かつ不確定要素がたくさんありそうならば、取得系のロジックをFinderに集約し、呼び出し側が変更に影響しないように実装するのもありだと思いました。presenterspresenterは、viewに関わるロジックやデータを持つオブジェクトをviewに提供するクラスとなっています。viewに直接書いてあるロジックや、modelにviewに関連するロジックやデータのメソッドは、presenterに実装します。実装をみていく使われ方を見てみます。今回はProjectPresenterを見ていきます。presenterはviewで下記のように呼び出されていました。返り値を渡しています。anchor.labelのようになっています。anchorsで渡されたデータを使って、リンクを生成していますね。#class_modifierのメソッドもつオブジェクトに依存しています。次に、ProjectPresenter#statistics_anchorsの実装見てみます。下記はProjectPresenterの実装の一部です。endendProjectPresenter#statistics_anchorsは#commits_anchor_dataや、#branches_anchor_dataを集めて返しています。#commits_anchor_dataは、構造体であるAnchorDataを生成・返却しています。ProjectPresenterは、Gitlab::View::Presenter::Delegated(Rubyの標準のSimpleDelegatorを使用)を継承しているので、Projectのインスタンスメソッドである、#statisticsにもアクセスできます。ちなみに、#branches_anchor_dataなど、#*_anchor_dataという命名のメソッドは全てAnchorDataを返却していました。他にもオブジェクトの生成の仕方や、URL生成のヘルパーなど、しっかり作り込まれているのですが、長くなりそうなので割愛します。オブジェクト生成の部分を端的に紹介すると、直接ProjectPresenter.newすることを禁止しており、下記のようなパターンで生成するようにしています。Factoryクラスを使って生成するパターンGitlab::View::Presenter::Factory.new(@project).fabricate!メリットになりそうな事viewの表示のルールがpresenterに集約されていて読みやすいと思いました。表示してはいけないものを表示してしまったなどの事故が起きにくくなりそうです。viewがコンフリクトした時の恐怖からバイバイできるのも個人的に好きです。viewに比べかなりテストしやすくなっています。導入するときには、presenterがどういった粒度で実装されるかをチームで決めておいた方が良いかもしれません。GitLabはmodelに対してpresenterが実装されている風に見受けられました。(models/project.rbに対して、presenters/project_presenter.rbのイメージ)プロジェクトによって、違う粒度での実装もありえると思います。viewで関連データが複雑かつ、多くのところで使われているパーシャルがあったら、それと1対1で対応させるようにすれば幸せになれそうですね。コントローラーにも同じロジックを書かなくて済むし、変更に対する影響範囲も明確です。コマース事業部で導入するとしたら、draperを使ったDecoratorとどんな住み分けするか(そもそも導入しない)とか、チーム内で議論してみたいです。最後に今回はこんな風にGitLabのクラス設計を見ていきました。プロダクトの特性によって設計の仕方も変わってくると思うので、違うOSSも読んでみるのも楽しいと思います。「保守性がモリモリ上がるクラス設計」と題しましたが、クラス設計に銀の弾丸はない気がするので、プロダクトの変化しやすい部分はどこか、設計することでどんな問題を解決したいかを、チーム内で議論を重ねて実装・検証することが良さそうです。コマース事業部では事業も開発も挑戦することが多く、エンジニア・デザイナーを強く募集しています。qiita....2019-12-16Rails初心者がハマったCapistranoの環境変数こんにちは。delyコマース事業部エンジニアのjohnです。もと...2019-12-14Rails6の複数データベースの仕組みと実装時にハマったところこんにちは、開発部の高橋です。こんにちは、フロントエンドエンジニアのall-userです...2019-12-01マルチモジュール時代のDagger2によるDIこんにちは。(1)月別アーカイブはてなブログをはじめよう!delyjpさんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。