読者です 読者をやめる 読者になる 読者になる

Railsで導入してよかったデザインパターンと各クラスの役割について

3年ほどRailsを書いてきてある程度知見が溜まってきたので、忘れないためのメモとしてKPTと導入例を交えながらダラダラと書いています。

見出しの命名規則は クラス名/ディレクトリ名の単数形をupper camel caseにしたもの + KPT です。

Keepは今後も使うもの、Problemは開発規模によっては問題が発生する(した)もの、Tryは現在使用していないが使用したほうが良いと思っているものです。

これらすべてを導入すれば上手くいくというわけでもないので、開発規模に合わせて適切に採用していくと良いと思います。

DDDやデザインパターン等見聞きはしているものの詳しいわけではないので間違っている部分等あるとは思うのでその辺りはコメントでご指摘お願いします。

はてブコメント欄で頂いた指摘内容等についてはまとめの後でまとめて返答を記載しています。

Asset (Keep)

app/assetsに配置します。

最近はフロントエンド開発ではGruntを使いassetsを使わないというパターンもあるようですが、

現状ではそうする必要があるほどのフロントエンド開発をやったことがないので私は今後もassetsを使っていくと思います。

Controller (Keep)

app/controllersに配置します。

クラス名の命名規則として、接尾辞にControllerを付与します。

Controllerの仕事としては、パラメーターやセッションの状態、Validation結果による処理の分岐、ページ遷移、テンプレートの出し分けに留め、

具体的なビジネスロジックについては下記に紹介するService, Form, Factory, Validator等に任せてしまったほうが良いと思います。

Decorator (Keep)

app/decoratorsに配置します。

クラス名の命名規則として、接尾辞にDecoratorを付与します。

Draperというgemがとても便利で、READMEがわかりやすいのでそちらを読むのが良いと思います。

GitHub - drapergem/draper: Decorators/View-Models for Rails Applications

Helperの代替として、Modelの状態に応じたViewの文字列の出し分け、文字列のフォーマットを行います。

Exception (Problem)

app/exceptionsに配置します。

クラス名の命名規則として、接尾辞にExceptionを付与します。

Railsの開発ではraise "message"でRuntimeErrorを投げて済ませることが多いため必須ではないと思います。

必ず補足したい特異な例外のみExceptionクラスを実装し、それ以外ではRuntimeErrorで済ませるくらいの使い方が開発速度を落とさずに実装できて良いかと思います。

結局のところ例外設計が難しいという問題があるので導入については慎重に検討したほうが良いかなと思います。

Factory (Keep)

app/factoriesに配置します。

クラス名の命名規則として、接尾辞にFactoryを付与します。

Factoryでは主に以下の3種類の処理を行います。

複数のオブジェクトから単一のデータオブジェクトの生成

# 現在ログイン中のユーザーのユーザー名、投稿内容を持った
# comment_confirm_formインスタンスの生成
comment_confirm_form = CommentConfirmFormFactory.create!(
  current_user, 
  comment_params
)

・データ構造を持たないデータからデータオブジェクトの生成

# CSVを読み込み、在庫データオブジェクトを生成
CSV.open("/tmp/path.csv") do |row|
  stock_parameter = StockParameterFactory.create!(row.fields)
end

・共通のインターフェースを持つオブジェクトの透過的生成

# CreditCardPaymentServiceの生成
payment_service = PaymentServiceFactory.create!(:credit_card)
# CarrierPaymentServiceの生成
payment_service = PaymentServiceFactory.create!(:carrier)

上記3種類に属さないインスタンスの生成に関してはFactoryを作る必要は無いと思います。

Form (Keep)

app/formsに配置します。

クラス名の命名規則として、接尾辞にFormを付与します。

Modelの生成に必要なデータを入力するformを作る際、

ある場面ではバリデーションAを実行し、ある場面ではバリデーションBを実行するということは頻繁に発生するため、

formからのデータ送信は原則として一度Formオブジェクトにしてしまうのが良いと思います。

APIリクエストの入力値も一度Formに入れています。

Helper (Problem)

app/helpersに配置します。

クラス名の命名規則として、接尾辞にHelperを付与します。

お馴染みのHelperですがグローバルなメソッドなので、グローバル変数と同じような問題が発生するということはよく言われており、Decoratorに取って代わられようとしてします。

個人的には、Modelの状態によってCSSのクラス名を出し分けるような極めてフロントエンドよりな処理の場合に関してはDecoratorよりもHelperに処理を置いたほうが責務がはっきりするかなと思っています。

とはいえ、最近ではAngularJSやReactJS等があるためHelperで頑張らなくても良いと思います。

Job/Worker (Keep)

app/jobsまたはapp/workersに配置します。

クラス名の命名規則として、接尾辞にJobまたはWorkerを付与します。

ActiveJobやSidekiqなどでお馴染みの非同期処理を行うクラスです。

Model (Keep)

app/modelsに配置します。

Modelにはassociaion, scope, enum, データベースレベルでの制約(not null, unique等)に関するvalidation, 状態の取得、変更に関するメソッドのみ記述するのが良いかなと思います。

scopeについては後述するRepository/Finderに記述しても良いかと思っていますが、この辺りは検討中です。

Notifier (Keep)

app/notifiersに配置します。

クラス名の命名規則として、接尾辞にNotifierを付与します。

通知に関するクラス群です。

ActionMailerに関してもNotiferでラップしたほうが良いと思います。

一例として、例外の通知をHipChatに行っていたが、チャットツールの移行に伴いSlackに通知したくなったといった場合に

Notifierの中だけ修正すれば良いという状態になっているのが理想だと思います。

Parameter (Keep)

app/parametersに配置します。

クラス名の命名規則として、接尾辞にParameterを付与します。

複雑な属性を持つデータを格納するデータオブジェクトです。

CSVから読み込んだデータを格納するデータオブジェクトや、複数のオブジェクトから生成されるデータオブジェクトとして使用します。

# ログイン中のユーザー情報、カートの情報、
# 入力されたクレジットカードの情報を元に決済用データオブジェクトを生成
payment_parameter = PaymentParameterFactory.create!(
  current_user, 
  cart_form
)
# 決済処理を実行
payment_service = PaymentServiceFactory.create!(:credit_card)
payment_service.pay!(payment_parameter)

命名がParameterで良いのかという部分については悩んでいますが、こういったデータオブジェクトがあったことは便利でした。

Repository/Finder (Try)

app/repositoriesまたはapp/findersに配置します。

クラス名の命名規則として、接尾辞にRepositoryまたはFinderを付与します。

複雑な検索処理を記述するクラスです。

Model(ActiveRecord)がDDDで言うRepositoryの機能を持っているため、classとして実装するのではなくconcerns moduleとして実装し、Modelでincludeしてしまうのが良いかなと思っています。

module UserRepository
  extend ActiveSupport::Concern

  module ClassMethods
    def find_by_oauth_token(provider, token)
    end
  end
end

class User
  include UserRepository
end

Resource (Try)

app/resourcesに配置します。

クラス名の命名規則として、接尾辞にResourceを付与します。

近年マイクロサービス化が叫ばれており、各システムで使用する共通機能を提供するAPIシステムというものも増えてきていると思います。

RailsにはActiveResourceというマイクロサービスのための機能があるため、その機能を使用して実装したクラスはResourceとして定義するのが良いと思います。

サービスがRESTfulでなく、ActiveResourceを使用しない場合でも外部APIを使用するのであればResourceとして良いのかなと思っています。

Service (Keep)

app/servicesに配置します。

クラス名の命名規則として、接尾辞にServiceを付与します。

Controller, Form, Model, Repository, Resource, Task, Util, Validator以外のビジネスロジックに関する処理を記述します。

何をServiceとするかについては設計が難しい部分だとは思いますが、専門性の高い処理はほとんどここに配置することになると思います。

Task (Keep)

app/tasksに配置します。

クラス名の命名規則として、接尾辞にTaskを付与します。

rake taskの実処理を記述します。

rakeファイルにはTaskへのパラメーター受け渡しのみを記述し、実際の処理はTaskに記述しています。

Util (Keep)

app/utilsに配置します。

クラス名の命名規則として、接尾辞にUtilを付与します。

Utilは雑多なものが置かれ解りづらくなりやすいのですが、専門性の低い処理(=複数のクラスから共通で使われる処理)にのみ特化して作っていけばそれほどひどいことにはならないと思います。

ファイルの圧縮展開に関わるクラス、システムコマンドを実行するためのOpen3をラップしたクラス、ユニークなIDを払い出すクラスといったものはここに置いています。

Validator (Keep)

app/validatorsに配置します。

クラス名の命名規則として、接尾辞にValidatorを付与します。

独自バリデーションを実装する際にはここに配置します。

View (Keep)

app/viewsに配置します。

特に述べることは無いです。

ViewObject (Try)

app/view_objectsに配置します。

クラス名の命名規則として、接尾辞にViewObjectを付与します。

Viewで使用するデータオブジェクトです。

基本的にはFormやModelをそのまま使うのが良いと思っているため必須では無いと感じています。

ただし、1件目はblockAに、2件目以降はblockBに表示するといった形で表示するなど、Viewにロジックを入れたくなるようなパターンではViewObjectに1件目と2件目以降を格納してViewに渡すのが良いと思います。

まとめ

上記で述べたもの以外にもMailerやCarrierWaveを使用する際のUploaderなどありますが、そのあたりは処理が明確なので特に紹介していません。

こういった形で分かれていると責務がはっきりするのですが、Concernsに名前空間を付けないと管理が煩雑になる、定数の管理方法が定まっていないなどまだまだ問題はあるためより改善していきたいと思っています。

1年おきにやっぱりこうしたほうが良かったなと思う機会は発生するので、来年にはまた違った作りの方がよかったと言っているかもしれませんが当分はこの形で作っていこうと思います。

後出しジャンケン

Parameterが2通りの使われ方をしているので、Parameter(複雑なデータ構造を持つ引数)とEntity(CSVの行データのようなものをオブジェクト化したもの)と分けたほうがよかったかもしれない。

Exceptionについては、例外の種類を増やすよりメッセージをデバッグ可能なよう詳細に書いたほうが良いという判断です。

決済処理に失敗した、データ生成に失敗したというような重要度の高い例外については専用の例外を書くべきですが、重要度と発生頻度が低い例外はRuntimeError程度で済ませて良いと思っています。

modelsをモデル層として捉えていない、モデル==ActiveRecord::Baseを継承したものではないという意見についてはその通りなのですが、モデル層と呼ばれるものは実はかなり大きな層であると感じました。

そのため、あえてmodelsにActiveRecord::Baseを継承したもの以外を置かないようにしていると考えてもらったほうが良いかと思います。

例外的にRedisのデータを保持するクラスについては状態を保持するモノとしてmodelsに置いています。

Modelについて上記説明はちょっと曖昧な気がしました。Resource以外の永続化層のみ置くようにしたほうが良いが答です。

株式会社ドワンゴモバイルを退職しました

退職のご挨拶

2013年5月7日をもって株式会社ドワンゴモバイルを退職しました。
4月12日が最終出社日だったので、とても長いゴールデンウィークを過ごしました。

2010年新卒としてドワンゴに入社したので、ちょうど3年ほどドワンゴグループにいたことになります。

入社からのお仕事

2010年4月の入社から6月までは研修なのでその間は良いとして、
2010年6月にドワンゴとしては大所帯のエンジニア20人ほどのプロジェクトに配属となりました。
当初は大規模プログラミングの経験も無く、右往左往することが多かったのですが、
大変優秀なリーダー、先輩方のおかげでなんとかまともなエンジニアになることが出来ました。
笑いあり、涙あり、炎上ありのプロジェクトで、とても良い経験が出来たなと思います。

余談ですが、2010年6月は社内ベータリリースのサービスの脆弱性で遊んでいたら、
「このサービスリリースまでに修正終わらないんじゃね?」という雰囲気になり、
鋼の男に呼び出され、開発会社へ共に出向き、脆弱性実証係をやることになった月でもあります。

2012年1月にはモバイル関連事業の分社化があり、
それに伴いドワンゴモバイルへ転籍となりました。

結局2013年3月の異動まで最初に配属されたプロジェクトを担当し続け、
安定稼働するようになったため、20人超いたエンジニアは3人になり、
異動の話が舞い込んでくる直前、退職の決意をすることになりました。

2013年3月から退職までの1ヶ月半は
とても先進的で、とてもハードな期間だったなという気がします。

乗るしか無い、このビッグウェーブに!

いろいろと思うところがあって、
そろそろ会社をやめようかなと考え始めていたのは2012年末だったか2013年年明けだったか。
他人の話を聞いたり、人に話を聞いてもらったりする中で、
退職しようという決意を固めたのはその2ヶ月後のことでした。

転職しようと決めた一番大きな理由は『音楽性の違い』です。
自分が創り出しているサービス、自社が創り出しているサービスがそれほど好きでなくなった。
というのが一番大きな理由です。

就職活動をしていた2009年、入社時の2010年のニコニコ動画というのは
インターネット上に生息している変な人たちが集まって、それぞれ全く違うジャンルで、
それぞれができる最大限のパフォーマンスで競い合うというまさに カオス という空間だったように思います。

dwango.jpについても、入社時にはビジネスとして安定していることもあり
それほど面白いことをやっていたわけではありませんでしたが、
巫女巫女ナースや西村ひろゆきさんを起用したCMのインパクトは今でも覚えています。

ですが、入社から3年経ち、
サービスが変わってしまったのか自分が変わってしまったのかはたまた両方なのか、
ニコニコ動画を見る機会も少なくなり、
アニメを見ることも減り、
聴く音楽のジャンルもだんだんとマイナー路線に偏っていったため、
だんだんと、自分が作り出しているものが楽しみではなくなっていきました。

それがやはり、自分の中でドワンゴドワンゴモバイルをやめるに至った一番の理由のように感じます。

他に理由を上げるとすれば、
会社全体の真面目路線化 (ちょっと午前出勤とか辛いっていうか・・・)
信頼できるエンジニア、優秀なエンジニアの減少、
エンジニアリングをしていく上での政治的要因
(言語の選択、ミドルウェアの選択における制限)
等がありました。

いろいろと理由は挙げましたが、
やはり、もっと大人になって仕事が生きていくための義務になるまでは、
自分の好きなもの、好きなサービスを創りだしていきたいなと思いました。

これから

転職について考えた時、
自分の好きなサービスを作れそうな会社って他にあるかな、
サービスを作る上で自分の使いたい言語、そのサービスに最適な言語/ミドルウェアを選択できる会社って他にあるかな。
と考えた時、やはりそこまで自由にできる会社は無いんじゃないかなと思いました。

ならば、自分でその選択をしていける仕事をすればいいじゃないか。

東京に来て3年、そういった界隈の知人もたくさんでき、
いつかやってみたかったということ、若いうちだから怪我しようと思ったこともあり、
次の仕事はフリーランスとしてやっていこうと思います。

ありがたいことに、次のお仕事はすでに頂けており、
RoR + AWSでのサービス開発をしばらく行なっていくことになりました。

とはいえ、次のお仕事も必要ですので、
何か御座いましたらコメント欄等にてご依頼よろしくおねがいします。

ありがとう

f:id:masato_hi:20130507215611j:plain

追伸

え、最後まで読んでくださった方からなにか プレゼント をしていただけるんですって? ありがとうございます!