patorashのブログ

方向性はまだない

「現場で役立つシステム設計の原則」読書会 vol.9

読書会自体はGW前にやっていたのですが、諸々あって書くのが遅くなってしまった…。

第9回の感想です。前回の感想はこちら。

patorash.hatenablog.com

会社のブログのレポートはこちら。

tech.rhizome-e.com

9章 オブジェクト指向開発のプロセス

  • 開発の基本はV字モデルではあるが、そのサイクルが異なる。今はアジャイルで開発することが主流なので、同じチームで全部を担当することが普通になっている。大体うちでもそういうプロセスでやってる。
  • しかし、ここに書いてある通り、分析・設計にほとんど時間をかけずにとにかくプログラミングするという流れはある。特に、Railsでは顕著ではないかと思う。ちょっと規模が大きくなるとコードの見通しが急速に悪化するというのは、見に覚えがある。
  • ドメインモデルを中心にしたソフトウェアの考え方。従来の開発の仕方は、今いるメンバーは殆ど経験したことがないんじゃなかろうか?オブジェクト指向らしい開発の進め方について。設計した人が開発もするのでドキュメントの確認も減るし大変効率が良い。
  • 口頭でのやりとりをラフスケッチとしてホワイトボードに起こしていくというのは、写真を撮るだけでいいしよくやっていた。うちの会社だとスマホで写真を撮って共有、がやりにくいが…。リモートワークが主流になった現在だと、そういうツールを使いこなせばいいのかもしれないが、あんまりやってない。Miroとか?一応MS Whiteboardはある。ペン付きのPCならいいんだけれど、Macだとそのあたり厳しい。
  • ドキュメントに関して。データベースのテーブル名/カラム名とコメントは書くようにしている。I18nのデータを元にコメントにしていく仕組みを準備している。gemにしてもいいかもしれない。
  • 全体を俯瞰するドキュメント。これはインセプションデッキと役割が似ている。時々見返したり、見直したりする必要がある。
  • 技術方式のドキュメントもソースコードで表現できるというところ。Infrastructure as Codeの話。現代においては、この辺りはもう全てコード化しておくべきもの。学ばなければならないものが多い反面、自動化できることの恩恵は大きい。
  • 分析と設計が一体になった開発のやり方をマネジメントする
  • 受託開発の行うときに発注する側、受注する側で気を付けるべき点等が書かれているのはよい。準委任契約のほうがいいという点は、そうだろうなぁと思う。
  • 進捗の判断…チケット単位でドメインオブジェクトを作るように組んでいけば、この通りに進捗管理できるんだろうか?ドメインオブジェクトの組み合わせて機能を作っていけるので、例え機能が未実装でも、ドメインオブジェクト作成の進捗があれば、進んでいるとみなせるということか。その発想はなかった。
  • 品質保証について。ドメインオブジェクト単位で独立しているから、テストしやすく、ミスがあったとしても簡単に修正できる。
  • 他の章にも書いてあったが、プログラミングスキルとドメイン知識の両方を備えていかないと、優秀なエンジニアとは言えないと思う。業務知識の勉強会を行うことも大事と思われるので、そういうことも計画していったほうがよいかもしれない。

雑感

読書会のメンバーは自社製品の開発をしているので、受託開発する際の契約に関するところは、「うーん?」という感じであった。設計と、設計レビューにもっと時間を割いて、熟練者の知見を吸収していったほうがいいんでないか?ということを話したように思う。大して設計せずにすぐ作り込んでから、実装に対するレビューをしてしまうとレビューの視点もブレるし、実装者もあんまり変更したくないもんだから設計がおかしいまま実現しようとしてしまいがちだと思う。わかったふりをせずに、貪欲に質問したほうがいい。みんな答えてくれるから。

「どうやって業務知識をつけていったんですか?」という質問があった。「扱っている製品が違うので、一概には言えないんだけれど、定例会のタイミングとかで疑問をぶつけたり、お客さんからの反応を聞き出したり、おすすめされた記事を読んだり等はしていたよ」という話をしたり等。先輩社員にドメイン知識をどうやってつけたのか?を聞いてみたら、ヒントがあるのではないか?と話したら「そういえばあんまり聞いたことがなかった」ということだったので、聞いてみよう!ということに。

RSpecでブロック引数をmockで置き換える

RSpecで、ブロック引数に渡されたオブジェクトが、とあるメソッドを実装しているかどうかによって処理を変える件のテストをしたかったのだけれど、どうやったらいいかわからなかったので調べました。

テストしたい処理

class Hoge < ApplicationRecord
  def foo
    # 略
  end
end

class Fuga

  # @param [Class] model_class ApplicationRecordを継承したクラス
  def initialize(model_class)
    @model_class = model_class
  end

  def execute!
    @model_class.find_each do |record|
      # fooメソッドが実装されていたらfooを実行したい
      value = if record.respond_to?(:foo)
                record.foo
              else
                record.bar
              end
      # 略
    end
  end
end

これで、model_class#fooがあれば、それが実行されていることを確認したかった。

やったこと

【NG】stubでnewの戻り値をmockに置き換える

当初はHoge#newメソッドを置き換えておいたらええんちゃうか?と思ったけれど、ダメでした。binding.pryfind_each内のループに仕込んでも到達しなかったので、やめました。

RSpec.describe Fuga do
  context 'fooメソッドがある場合' do
    let(:record) { instance_double(Hoge) }

    before do
      allow(Hoge).to receive(:new).and_return(record) # NG
      # record.respond_to?(:foo) の戻り値をtrueにstubしておく
      allow(record).to receive(:respond_to).with(:foo).and_return(true)
    end

    it 'Hoge#fooが呼ばれること' do
      expect(record).to receive(:foo)
      described_class.new(Hoge).execute!
    end
  end
end

【OK】stubでfind_eachのブロック引数をmockに置き換える

and_yieldを使えば、ブロック引数を置き換えることができることを知りました。

RSpec.describe Fuga do
  context 'ApplicationRecord#fooメソッドがある場合' do
    let(:record) { instance_double(Hoge) }

    before do
      allow(Hoge).to receive(:find_each).and_yield(record) # OK
      # record.respond_to?(:foo) の戻り値をtrueにstubしておく
      allow(record).to receive(:respond_to).with(:foo).and_return(true)
    end

    it 'fooメソッドが呼ばれること' do
      expect(record).to receive(:foo)
      described_class.new(Hoge).execute!
    end
  end
end

これで無事にHoge#fooが呼ばれることを確認することに成功!✌️

参考にしたページ

Rspecで環境変数をstubしていたのが動かなくなった件(解決済)

小ネタです。rubocopのバージョンアップを行ったら、新しいルールで怒られが発生しました。

 C: [Correctable] Style/FetchEnvVar: Use ENV.fetch('FOO') or ENV.fetch('FOO', nil) instead of ENV['FOO'].

Correctableなので、bundle exec rubocop -Aで自動修正を行ったところ、ENV.fetch('FOO', nil)になりました👍

これでテストを実行したところ、思わぬエラーが発生😵

環境変数をstubしているテストが落ちる

環境変数によって、とある項目をチェックするかどうかを切り替えている処理がありまして、そのテストのために以下のようにしていました。

context '環境変数BARに0が設定されている場合' do
  before do
    allow(ENV).to receive(:[]).and_call_original
    allow(ENV).to receive(:[]).with('BAR').and_return('0')
  end

  it 'チェックされないこと' do
    # テストケース
  end
end

※この書き方は、以下の記事を参考にしています。

qiita.com

該当箇所のコードは、元々はこんな感じ。

if (ENV['BAR'] || '1') != '0'
  # チェックする
end

これが、rubocopのAutoCorrectによって、こうなりました。(カッコが多かったため、手でも修正しています)

if ENV.fetch('BAR', '1') != '0'
  # チェックする
end

これが全くstubされないように…😢

原因

これはstubの定義がENV['BAR']の場合にしか対応していないためです。この変更に対応するには、ENV#fetchの結果をstubしなければなりません。

解決方法

環境変数の参照の仕方のstubをfetch経由に変更します。

context '環境変数BARに0が設定されている場合' do
  before do
    allow(ENV).to receive(:fetch).and_call_original
    allow(ENV).to receive(:fetch).with('BAR', '1').and_return('0')
  end

  # 略
end

肝は、with('BAR', '1')のところで、with('BAR')だとstubされませんでした。きちんとデフォルト値まで渡さなければなりません。

まとめ

環境変数をちゃんとstubしているのに何故!?と5分くらい考えてしまいましたが、当然っちゃあ当然です😅 環境変数をfetch経由で受け取る修正をした際は、テストのstubも見直しましょう。

「現場で役立つシステム設計の原則」読書会 vol.8

第8回の感想です。前回の感想はこちら。

patorash.hatenablog.com

会社のブログのレポートはこちら。

tech.rhizome-e.com

8章 アプリケーション間の連携

  • 1つのサイトであっても、様々なシステムが連携しあっている。
  • アプリケーション間の連携方式。
    • ファイル転送
    • データベース共有
    • WebAPI
    • メッセージング
  • うちでよくやる方式なのはファイル転送だろう…。オンプレの環境との連携とかがあるので仕方ない面はある。WebAPI連携は多少やっている。自分の担当製品とその派生製品で、API連携している。WebAPIはGraphQLで作っていっているが、ちょっとわかりにくい。自分の実装が中途半端だからだと思う。知見が足りない。
  • 非同期メッセージングは、RailsでいえばActiveJobのようなものだろう。Redisなどを介して処理をキューイングして、取り出して実行するような形だ。JSのMVVMの仕組みも非同期メッセージングと言えるんじゃないだろうか?Pub/Subのようなやつ。
  • WebAPIの仕組みのところは、RESTful APIの話が中心。更新や削除もPOSTで行うのはRESTに反するので、ちょっとどうかなぁとは思った。
  • このURL設計やレスポンスの設計も、ドメインオブジェクトを軸として考えるとスッキリしたものが作れそう。ただ、ここまで細かくドメインオブジェクト単位で取得しようとすると、WebAPIへのリクエストが増えていって大変そうな気もする。ただ、よいAPIとして書かれていた「多様性を維持しつつ、組み立ての負担が増えすぎない適切な大きさの部品を用意すること」というのはわかる。
  • SwaggerUIの話も出てきた。前にチームの仲間と話したことがあるやつ。スキーマ駆動開発をするにしても、そこでドメインオブジェクトの設計が重要になりそう。
  • WebAPIが出力するJSONなりXMLも、Viewだから、関心事とドメインオブジェクトの不一致は起きそう。Railsだと、そこをjbuilderを使って関心事にフォーカスさせるのかなと思う。そうでなければ、to_jsonだけで成立するからだ。
  • 複雑な連携に取り組む。のところ。WebAPIを作るところをずっと課題にしているにも関わらず、要望対応やリファクタリングをしているので、なかなか進めていないのだが、ここにヒントがあった。コア機能と拡張APIと個別対応APIを分けて考えて、それぞれ完成させていけばいいかなと思った。
  • マイクロサービスの話もあった。マイクロサービスについての知見をあまり持っていないので、一概には言えないけれど、この本に書いてある通り、試行錯誤がし辛そうだなぁと思った。サービス単位の分離を上手にやらないといけない。対象業務への理解が不十分な場合はモノリスで作っておき、後々分けていくのがよさそう。マイクロサービスについての勉強もしていかなければならない。サーバーレス&マイクロサービスが今後の主流になっていくことは間違いないとは思うが、それはある程度サービスが枯れてからかなぁと思う。
  • 非同期メッセージングをアプリケーション間で使ったことはない気がする…。dRubyを使えば、もしかしたらできるのか?

雑感

今回参加していたメンバーが自分以外、Web APIを作ったことがない(作ったと思っていない?)、かつ、利用したことがないようだった。世界には様々なPublicなWeb APIがあるよってことをWeb APIまとめサイトを紹介しながら教えた。

Heroku CLIAWS CLIにしても、HerokuやAWSのWeb APIにリクエストを投げてるんだよって話をしたら、なるほどって理解してくれた感じに思えた。

Web APIを自前で作るにしても、外部に公開する場合には、SDKとか作って利用しやすくしたようがいいよねって話したら、「SDKって何ですか?」っていうメンバーもいたので、その辺りから説明した。見たこともないっていってたけれど、「いや、君のプロジェクトのGemfileにaws-sdkってあるやろ!?」と言ったら、「あ、ほんとだ」という感じで、ほとんど意識したことがなかった模様。まぁ実際に使う機会がないとわからないもんだろう。そういうのも知ってもらえるいい機会になったんじゃないかなぁと思う。

Web API設計のここ悩むよね~的な話はほぼできず(だってみんな作ったことがないし…)、今回はひたすら説明要員の役割だった。まぁみんながこんなWeb APIがあるのか~とか、マッシュアップアワードとかあるのか~って知ってもらえたと思うので、それで良しとする!!

「現場で役立つシステム設計の原則」読書会 vol.7

第7回の感想です。前回の感想はこちら。

patorash.hatenablog.com

会社のブログのレポートはこちら。

tech.rhizome-e.com

なお、この回の会社のブログのレポートの担当が私だったので、書きたいことはそちらにほぼ書いちゃってる…。

7章 画面とドメインオブジェクトの設計を連動させる

  • 画面にロジックを埋め込むと複雑化してくるのはすごくわかる…。
  • 表示のためのロジックと業務ロジックを分ける意味では、ビューヘルパーを使ったり、デコレーターを噛ましておくといいとは思う。Railsの場合はビューヘルパーはグローバルメソッドのようになってしまうので、あまり好みではないけれど、契約による設計を重視した形にすれば、あまり散らからないかもしれない。定義場所は整理できるし。
  • 今作っているサービスだと、何でも入力画面になっている。関心事を分離することができれば、入力ステップ数は増えるものの、ドメインオブジェクトで整理できそうに感じた。なんでも入力画面だと、全部入力するのに時間がかかって疲れて離脱してしまうので、本当によろしくない。後で入力すればいいものまで、必須にする必要はない。
  • 最近感じているのだが、バックエンドのドメインオブジェクトとフロントエンドのドメインオブジェクトが流用できないのが歯がゆい。Rubyドメインオブジェクトを作り、さらにJSでドメインオブジェクトを作らなければならない。ほとんど似たようなものを、だ。そしてそれぞれをテストしなければならないのが面倒。共通で使えたらいいのにと感じる。
  • pixtaが次のシステム刷新でRailsをやめてTypeScriptのフレームワークを使うという話をしていた*1と記憶しているのだが、それはバックエンドでもフロントエンドでも同じドメインオブジェクトを使うためなのではないか?JSでは表現力がイマイチだが、TypeScriptだと型も列挙型も使えるので表現力もある。と読みながら思った。
  • 検索項目単位でドメインオブジェクトを作っていくと、扱いがやりやすくなりそう。
  • タスクベースのユーザーインターフェース、確かに最近多い。入力するものが限られるので、入力する側もサクッと変更できてよい。内部の設計はタスクベースに分けておくべき、とあったので、そうしておきたい。
  • どこに画面用ロジックを集めるか問題。論理的なビューに関してはドメインオブジェクトでいい、というのはわかる。しかし結局苦しんでいるところは物理的なビューだったりする。物理的なビューに関してはデコレーターに閉じ込めるようにして、論理的なビューはドメインオブジェクトに寄せるようにすれば、納得感はあるかなと思う。
  • 画面とソフトウェアを関係づける。わざわざBookSummaryクラスを作ってしまうのか。ぱっと見、データクラスじゃない?という感じがあるが…。まぁメソッドを定義してないだけか。これもまた、一覧と詳細では関心事が違うよということか。ついついRailsで考えがちなので、モデルで取ったデータをわざわざサマリ用のクラスに詰め替えるんかい、と思ってしまったが、普通に考えたら、データ取得用のクラスでgetSummariesを定義して、そのサマリ用クラスのインスタンスの配列を返せばいいわけだから、Railsじゃなければ、そっちのほうがスッキリするかなと思えた。
  • 画面もドメインオブジェクトで管理したい、となってくると、MVVMを使うようになっていくことになるのは必然。そりゃあReactやVueが流行るのもよくわかる…んだが、よくわかってない人がやってるのを見ると、画面と値が連動していて便利、止まりになっている気がする。
  • プレスリリース、リリースノート、利用者ガイドの内容がドメインオブジェクトに反映されているのが良いソフトウェアという話。言われてみれば、たしかにそうだなと思う。しかしこれら全ての整合性をとり続けるのは相当難しい。うちの体制だと、メンバーが少ないのでそちらのメンテに手が回りにくい。やはり、せめて1製品につき最低でも3人体制くらいにはしたいものだ。

雑感

物理ビューと論理ビューの分離どうするかというところを課題として考えているのだけれど、Decoratorに論理ビューを作るメソッドを定義し、ViewHelperに物理ビューを作るメソッドを定義していくように分けたら良いかなと思うようになった。という話を会社のブログで書いた。最近はこれを意識してRailsのコーディングしているのだが、どうにも気持ち悪いことが起こる。

気づいたらDecoratorでViewHelperのメソッドを呼んでしまっていたりする。link_toとか。リスト出力でリンクを出したいときなんだけれど、このときに渡したい引数は文字列の配列なので、リンクのHTML文字列を含んだ配列にしておきたいのだが、それを呼ぼうと思うと加工が必要になる。そこをどこでやるか?という話になるんだが、なんだかんだでDecoratorがしっくりきてしまう。

Decoratorはそもそも装飾するのが担当だから、それでいいんじゃないか?とも思うのだが、論理ビューを担当させるだけにしたほうがいいのでは?という結論になったにも関わらず、現実だと結局物理ビューも担当しちゃうようになるから、悩んでいる。

モデルに論理ビューのロジックを移動させて、Decoratorは物理ビューを担当させるようにしたほうが、スッキリするかなぁと思い始めている。Railsのモデルはドメインオブジェクトでもあるので、そのほうがスッキリするかなぁ。

この辺りで悩んでいるのでご意見求めたい。

「現場で役立つシステム設計の原則」読書会 vol.6

第6回の感想です。前回はこちら。

patorash.hatenablog.com

会社のブログのレポートはこちら。

tech.rhizome-e.com

6章 データベースの設計とドメインオブジェクト

  • データベースには事実を記録する。事実にはNULLなどない。基本的にNOT NULL制約を付けなければならない。これは中国地方DB勉強会に参加したときだったと思うが、そう言われたので納得しており、それ以降はNOT NULL制約を必ず付けるようにしている。レビューのときにも「NOT NULL制約を付けろおじさん」になっている。
  • NULLが存在していいのは、外部結合(OUTER JOIN)や集約のときくらいなもの。文字列の場合は、デフォルト空文字でいいのでNOT NULLにする。
  • データの整理に失敗しているデータベース、めちゃめちゃ見に覚えがある…。今だったら絶対にこんなテーブル設計にしないと自信を持って言えるのだが、昔はそこまでデータベースの知識がなかったので、酷い設計をしていた。現在進行形で苦しんでいる。少しずつテーブル含めてリファクタリングしているが、既に稼働しているシステムのリファクタリングになるので、変更の影響度を調査しながらの修正になるので、修正にかかるコストが大きい。最初の設計時にレビューを受けたりして、ちゃんと見直しておくことが大事。
  • 特にRailsの場合はgem由来のテーブルがあったりして、それが制約が緩々だったり、ドメインオブジェクトになっていなかったりするケースもありそう。本に出てきたテーブルの都合が反映されたコードになっていたり等。
  • ここに書いてあるNOT NULL制約、ユニーク制約、外部キー制約などは基本的なものなので、出来る限り使うこと。データベース側の機能のチェック制約やenumなどを使うと、更にデータを守ることができる。
  • コトに注目するデータベース設計の話。楽々ERDレッスンの話を思い出しながら読んだ。記録のタイミングが異なるデータはテーブルを分ける、というところ。マジでそれ!と思った。今の仕様がそうなっていなくてかなり辛い。コトを記録するという発想がないと、モノに関連するもので一緒くたに詰め込んでしまおうと考えてしまいがち。ドメインオブジェクトの考え方を持っていたら、せめてドメインオブジェクトと一致するテーブルを作ったりしていたのだろうが、当時はそんなことは知らないのでできなかった。
  • コトの記録ではUPDATE文は使うべきではない、というのは、なるほどと思った。取り消しを記録する。元データ、取り消しデータ、新データ。INSERTだけで実現可能。例えば、予約テーブルがあったとして、予約をキャンセルされたら、予約取消テーブルに外部キーとして予約IDを持たせてINSERTするということだろうか。
  • カラムの追加はテーブルの追加をする、のところ。これくらい大胆な発想のほうがいいんだろうか。確かにそのほうが副作用がないけれど、テーブルが増えるし、JOINが大変になりそうな気もするんだが…。プログラムへの影響は少ないのはそうだけれど。事実を記録する観点だと、そっちのほうがいいのかな。たしかに途中から追加すると、NOT NULL制約が付けられなかったりする。データの関連付け処理を全て終えた後にNOT NULL制約を改めて付けたりしていたけれど、それだとNOT NULL制約が存在しない期間があるので、気を遣うことがある。
  • 状態の参照。コトの記録を徹底すれば、状態の算出は可能。動的に出力するのはパフォーマンス的に辛い場合もあるので、この辺りはRailsだとカウンターキャッシュを使ったり、DBのマテビューを使ったりしている。
  • DELETE, INSERTを使ったほうがいいケースの話。ロジックがシンプルになる。記録の同時性に違反するから、という方針を持っておくと、UPDATEで対処するべきデータかどうかの判断に使えるのでよさげ。
  • 残高更新は同時でなくてもいい、一か所でなくてもいいという考え方は、柔軟性が生まれる。事実はDBに保存し、状態はKVSに持たせるようにすると良さそう。状態の参照は頻繁に行われると想定すると、KVSのほうが向いている。
  • オブジェクトの設計とテーブルの設計。コトを記録するテーブルとドメインオブジェクトがほぼ1対1に対応することがある。しかし、似て非なるものという意識を持っておくべき。ドメインオブジェクトとデータベースのアクセスは、基本的に疎結合にしておいたほうがいい。そうでないと、互いに引っ張られ過ぎる。ドメインオブジェクトには業務ロジックを、データベースには事実の記録を。関心事が異なる。しかしそうなるとやはりRailsだと難しい。RailsActiveRecordドメインオブジェクトでもあり、データベースへのアクセス手段でもあるからだ。
  • Railsを使うんなら、それを分かったうえで、「それはそれ、これはこれ」と割り切っていくのが、現段階ではよさそう。

雑感

データベースの章なので他の参加者に比べて感想が多くなってしまった😅 みんな、あまり制約に気を遣った経験がなかったとのことだった。最近気を遣い始めたというメンバーもいた。後輩氏は私が「NOT NULL制約を付けろおじさん」になっているのでレビューで散々指摘されてきたことと、去年、楽々ERDレッスンを読むように促していたこともあって、この辺りに関してはできるようになってきていると思う。

参加メンバーのプロジェクトでは、状態の導出でパフォーマンスが悪化しているケースがあるということだった。状態テーブルを作ったほうがいいんじゃない?とか、カウンターキャッシュなり、マテビューを使うとかで対処できるよって話をした。

状態に関してはKVSに持たせるとかでもいいと思う、という話をしたのだが、そもそもKVSとは?というメンバーがいたので、そのあたりも説明した。「Redisとか普段使ってるんちゃうの?」と言ったら、「多分使ってると思うんですけど意識して使っているわけではないからほぼ分からないのと同じです」とのことだった。うちのプロジェクトだと、ActiveJobのキューの管理に使っていたりするわけだが、まぁそういう感じで、入れてるけれどActiveJobでよしなにしてるという感じなのだろう。

「まぁでもAWS使ってやっていくんなら、ElasticCacheとか使うことになると思うよ」とか、「Azure使うんでも似たようなものはあるはずだから、その辺り調べて使えるようなら使っていったらパフォーマンス改善するよ」という話をした。「KVS自体が頭になかったので考えもしなかったのですが、たしかに良さそうですね。調べてみます」と言ってもらえた。

UPDATE文を使わないケース、どういう時?という話も出た。予約の取消の場合、取消をお願いした記録が残ってないとマズくない?というケースでディスカッションした。あと、家を建てようとしたときの打ち合わせで決まっていったことは常にINSERTしているなぁと思った、という自分の実体験の話をした。

「こういうテーブル設計について勉強するにはどうしたらいいんですかね~?」と言われたが、後輩氏が「それはやっぱり楽々ERDレッスンを読んでほしいですね」と発言してくれた。頼もしくなってきていて嬉しい限り😂

楽々ERDレッスンの感想記事はこちら。

patorash.hatenablog.com

「現場で役立つシステム設計の原則」読書会 vol.5

第5回の感想です。前回はこちら。

patorash.hatenablog.com

会社のブログのレポートはこちら。

tech.rhizome-e.com

5章 アプリケーション機能を組み立てる

  • アプリケーション層がサービスクラスとモデルになるのかな?(Rails脳)
  • 複数のモデルにまたがるような複雑なビジネスロジックをサービスクラスにするもんだと考えていたのが、そうでないことに気付かされた。つまり、アクションでやっていることは、殆どがサービスクラスにしたほうがよいことだ。
  • サービスクラスでドメインモデルを組み合わせて処理するけれど、複雑になりやすい。原因の1つに、プレゼンテーション章の関心事に振り回されるって書いてあったが、これはアクションでやっていることがまさにそうであるので納得。
  • サービスクラスが複雑になるということは、ドメインモデルに成長の余地があるということ。サービスクラスに業務ロジックを書くくらいなら、ぎこちない名前でもドメインモデルを作ること。これは意識しないとすぐにロジックをサービスクラスに書いてしまいそう。というか現在が絶賛書いている状態…。リファクタリングしたい!
  • サービスクラスも、小さいサービスクラスを作っても良い。登録系と参照系に分けるのが基本というのはわかりやすい。登録系は副作用があるけれど、参照系は副作用がないので、変更を加えたときにテストしなければならない対象を考える範囲が減るのはいい。
  • 例外を発生させてるのが、どこでも使えるのでいいなと思った。
  • あと、契約による設計の話と防御的プログラミングの話が出てきた。これはプリンシプル オブ プログラミングを読んだ際に感銘を受けたので、チームのコーディングルールとしても、契約による設計を取り入れている。でもやっぱり同時実行の可能性を考えてチェックを入れたりはするんだなと思ったので、その辺りは見直したほうが良いかもしれない。
  • サービスクラスにシナリオクラスという名前をつけるの、イケてる。業務シナリオでプレゼンテーション層から分離できるから、テストしやすくなるのが最高。
  • データベースの都合から分離する、のところ。Railsはかなり分離しづらい。まぁできなくはないけれど、そういう視点を得たエンジニアでないと難しそう。メソッドにしていくくらいしかないだろうか?

雑感

参加者のほとんどが業務でRailsを使っているので、「Railsで考えると、こう」というふうに置き換えて考えてしまいがち(自分含めて)。他のアーキテクチャを見たことがない人は言葉で混乱しているみたいでした。 対照的に、.Netを使っている参加者は、業務で使っているのが3層アーキテクチャと、本に載っているJavaのコードに近いので、よくわかるとのことでした。

この本を読めば読むほど、RailsではないDDDをやりやすいフレームワークのほうが大規模開発には向いてるだろうなぁ~とふつふつと感じてます。まぁRailsはOne Person Frameworkだからなぁ~という話をしたりしました。

world.hey.com

あとは契約による設計についてと防御的プログラミングに関する認識について意見交換したり等…。引数の型が違ったら例外を上げるコードを書いてるという話があったので、それ契約による設計じゃなくて防御的プログラミングのほうやでって言ったり。

契約による設計は契約があるのだから、そもそも引数に異なる型の値を与えるのは、基本的に呼び出した側の責任となるはず。メソッド側で型が違うかどうかを毎度毎度チェックしてたら大変じゃんっていうのが私の意見というか契約による設計に対する認識。その代わり、ドキュメントをしっかり書いて、どの型を与えるべきかを明示しなければならないので、yardをしっかり書くことっていうルールにしてるよって話した。

だいたい、こんな話してたと思う。

  • 「とはいえ1行、型のチェック入れるだけじゃないですか?」
  • 私「だったら引数がある場合に全部のメソッドに対して型チェック入れてくの?って話になるので、面倒じゃない?」
  • 「うーん、たしかに…。しかしRubyだと実際入れられちゃうじゃないですか。そこどうするんですか?」
  • 私「だから、契約なんだから、そんなことをする人は契約違反でしょ?」
  • 「いやまぁ確かにそうなんですけど、実際入れられちゃうじゃないですか」
  • 私「そこをめっちゃ気にするんならRuby使うべきじゃないんじゃない?Javaなら起きないよ」
  • 「うーん、そうですねー…」
  • 私「この本に載っているように、canWithdrawメソッドのような可能かどうかのチェックを入れて、そもそも呼ばれるのを事前に防ぐべきだと思うよ」
  • 「なるほど」
  • 私「同時に呼ばれたりして副作用が起きかねない場合は、本の中のwithdrawメソッドでも例外上げてたりするから、そうするべきかなぁ」

意見交換したり、認識の確認とかできたので有意義な会となりました。