Railsアプリ開発の事例紹介 / A case study for a Rails App

Railsアプリ開発の事例紹介 / A case study for a Rails App

リードエンジニアから学ぶMedPeerのプロダクト開発
https://medpeer.connpass.com/event/181835/

Ecad9d801d79f6c6e5df93094690685e?s=128

Takumi Shotoku

July 30, 2020
Tweet

Transcript

  1. Railsアプリ開発の事例紹介 リードエンジニアから学ぶMedPeerのプロダクト開発 2020/07/30 1

  2. 自己紹介 • 名前: 正徳 巧 • aka: 神速 • 社歴:

    2019年12月入社 • 11月から仕事はしてた • GitHub: @sinsoku (画像右上) • Twitter: @sinsoku_listy (画像右下) 2
  3. 社歴・仕事内容 • 11月〜3月: 30個近く起動する の調教 • 4月〜6月: オンライン診療のWebサービスの開発 • 7月〜現在:

    AWSとTerraform あと、社内Railsアプリを横断的にレビューもしてる。 3
  4. 社歴・仕事内容 • 11月〜3月: 30個近く起動する の調教 • 4月〜6月: オンライン診療のWebサービスの開発 • 7月〜現在:

    AWSとTerraform あと、社内Railsアプリを横断的にレビューもしてる。 4
  5. 5

  6. 主な機能 • 医師はMedPeerアカウントでログインできる • 医師は診療の予約をして、患者にメール/SNSで通知する • 患者とTV電話やチャットができる • 患者は会員登録が不要 •

    医師は診療内容をメモに残せる 6
  7. 7

  8. 話すこと / 話さないこと • ✅ AWSのインフラ構成 • ✅ Railsの設定の紹介 •

    ✅ gemのTipsの紹介 • ❌ リモートワークについて • ❌ プロジェクトの進め方 • ❌ Dockerに関する話 8
  9. AWSのインフラ構成 9

  10. AWSのインフラ構成 10

  11. CloudFrontを置く理由 • 静的ファイルのキャッシュ • 将来のパフォーマンス改善のため • セキュリティ対策(DDoSなど) • デプロイ時にassetsが404になる問題の回避1 1

    ECSのデプロイ時に一定確率で静的ファイルが404になる問題を回避する https://qiita.com/hareku/items/6be1b71e58033b9739fd 11
  12. AWSのインフラ構成(デプロイ編) 12

  13. Gitにプッシュ => 自動デプロイ ブランチへマージすると、自動的にデプロイされる。 • develop => ステージング環境 • master

    => 承認 => 本番環境 13
  14. プロジェクト初期のDockerfile 一番最初のDockerfileは以下の通り。 # NOTE: prd/stgͰڞ௨Ͱར༻͢Δrails༻ͷDockerfileͷμϛʔ࣮૷Ͱ͢ # railsଆͷ࣮૷͕ಈ͘Α͏ʹͳ͖ͬͯͨΒɺదٓमਖ਼͠ɺ࡞੒͍ͯͩ͘͠͞ FROM nginx:latest #

    ALB͔Βͷhealthcheck༻ͷμϛʔϑΝΠϧ RUN touch /usr/share/nginx/html/healthcheck # μϛʔΠϝʔδͰ͋ΔnginxͷϙʔτΛ࠷ऴతͳRailsͱಉ͡3000ʹมߋ͓ͯ͘͠ RUN sed -i -e 's/80;/3000;/g' /etc/nginx/conf.d/default.conf 14
  15. AWSのインフラ構成 15

  16. rails c できない問題 セキュリティを考えると rails c できない方が良いけど、Rakeタ スクくらいは使いたい。 どうやって解決するかはまだ調査・検討中 •

    Session Manager ❓ • ECS runTask API ❓ 16
  17. Railsの設定の紹介 17

  18. Railsの設定の紹介 Rails v6.0.3を使っていたので、使った新機能や遭遇したトラブル を紹介します。 18

  19. assets:precompileでsasscが死ぬ問題 sasscでsegfaultが起きたので、並列コンパイルの設定を無効にし ました。 # config/initializers/assets.rb # NOTE: assets:precompileͰsegfault͕ى͖ΔͨΊฒྻίϯύΠϧΛແޮʹ͢Δɻ # #

    ref: https://github.com/rails/sprockets/pull/469 # ref: https://github.com/rails/sprockets/issues/581 Rails.application.config.assets.configure do |env| env.export_concurrent = false end 19
  20. 複数DBの対応 DBは1つだけど複数DBの設定をしておく。 default: &default adapter: postgresql encoding: unicode # For

    details on connection pooling, see Rails configuration guide # https://guides.rubyonrails.org/configuring.html#database-pooling pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> url: <%= ENV['DATABASE_URL'] %> production: primary: <<: *default database: foo_production primary_replica: <<: *default database: foo_production replica: true 20
  21. ActiveRecord::Middleware::DatabaseSelector • GET, HEADリクエストはリードレプリカ • PATCH, DELETEリクエストはプライマリ リクエスト内で接続先を切り替えることは可能。 def show

    ActiveRecord::Base.connected_to(role: :writing) { # ΞΫηεϩάͷอଘɺ௨஌ͷطಡॲཧͳͲ } end 21
  22. テストの実行順序で稀にテストが落ちる2 2 https://github.com/rails/rails/issues/39205 22

  23. v6.0.4 が出るまでの回避策 RSpec.configure do |config| config.before(:suite) do # NOTE: Rails

    6.0.3ͷόάΛճආ͢ΔͨΊɺ࠷ॳʹwritingͷ઀ଓΛ࢖͏ɻ # ࢀߟ: https://github.com/rails/rails/issues/39205 ActiveRecord::Base.connected_to(role: :writing) { User.count } end end 23
  24. 複数ブラウザのE2Eテスト SystemSpecでChromeシミュレータの機能を使う事ができます。 ウチでは使ってないけど、Chrome拡張も使えるようです。 driven_by :selenium, using: :headless_chrome do |driver_option| driver_option.add_emulation(device_name:

    'iPhone 6') driver_option.add_extension('path/to/chrome_extension.crx') end 24
  25. CircleCIのマトリックスと組み合わせる3 • Chrome • iPhone 6(シミュレータ) • Firefox の3パターンをテストしてい る。

    3 https://circleci.com/blog/circleci-matrix-jobs/ 25
  26. 機微情報のフィルタ rails/rails#342184を参考に設定した。 # config/initializers/filter_parameter_logging.rb Rails.application.config.filter_parameters += [ :password, :secret, :token,

    :_key, :auth, :crypt, :salt, :certificate, :otp, :access, :private, :protected, :ssn ] # config/initializers/rollbar.rb Rollbar.configure do |config| config.scrub_fields |= Rails.application.config.filter_parameters end 4 https://github.com/rails/rails/pull/34218 26
  27. おまけ: ログレベルの変更 productionのデフォルトは :debug なので注意。 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@

    -45,7 +45,7 @@ Rails.application.configure do # Use the lowest log level to ensure availability of diagnostic information # when problems arise. - config.log_level = :debug + config.log_level = :info # Prepend all log lines with the following tags. config.log_tags = [:request_id] 27
  28. デフォルトがまた変わるかも?5 5 https://github.com/rails/rails/pull/39707 28

  29. 変わらない可能性も... 1. rails/rails#166226で全環境をdebugにする 2. breaking changeなのでRevert 3. a6de6f508c で警告を追加(次verからdebugにするよ) 4.

    794a6ca71e で警告を削除(v5からdebugがデフォルト) 6 https://github.com/rails/rails/pull/16622 29
  30. gemのTipsの紹介 30

  31. gemのTips • gemを追加した理由 • READMEには載っていない知見 などを紹介していきます。 31

  32. Gemfileの公開(2020/7/30現在)7 7 https://gist.github.com/sinsoku/5cf04e6cbbb90a8d2155df56895ccaa3 32

  33. rspec 推奨設定があるので有効化し ておく。 • 遅いテストを表示 • テスト順序のランダム化 • Kernel.srandにseedを使う •

    ...など --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -46,7 +46,6 @@ RSpec.configure do |config| -# The settings below are suggested to provide a good initial experience -# with RSpec, but feel free to customize to your heart's content. -=begin # This allows you to limit a spec run to individual examples or groups # you care about by tagging them with `:focus` metadata. When nothing # is tagged with `:focus`, all examples get run. RSpec also provides @@ -92,5 +91,4 @@ RSpec.configure do |config| # test failures related to randomization by passing the same `--seed` value # as the one that triggered the failure. Kernel.srand config.seed -=end end 33
  34. factory_bot バリデーションエラーになるファクトリをテストで検知できるよ うにしておく。 RSpec.describe 'FactoryBot' do describe '.lint' do it

    { FactoryBot.lint traits: true } end end 34
  35. rails_admin 管理画面のルートにはマウントしない。 Rails.application.routes.draw do mount RailsAdmin::Engine => "/tables", as: 'rails_admin'

    end 複雑な要望は素直にRailsで機能を作る。 カスタマイズし始めると学習コストが高く、Railsのアップグレー ドが辛くなりやすい。 35
  36. rails_admin 最低限のテストを書いておく。 RSpec.describe 'RailsAdmin::Engine', type: :system do include_context 'login_as_admin' before

    { visit rails_admin_path } it 'μογϡϘʔυͱ֤ϞσϧͷҰཡϖʔδΛදࣔͰ͖Δ' do expect(page).to have_title 'αΠτ؅ཧ' link_texts = all('.sidebar-nav a').map(&:text) link_texts.each do |link_text| within('.sidebar-nav') { click_on link_text } within('.page-header') { expect(page).to have_text "#{link_text}ͷҰཡ" } end end end 36
  37. pundit, paper_trail • rails_adminのために追加 • 基本は閲覧のみを許可 • 更新は必ず履歴を残す • 誤操作を戻せるように

    • 削除は許可しない class ApplicationPolicy def index? true end def create? true end def show? true end def update? history? end def destroy? false end def history? record.respond_to?(:paper_trail_options) end end 37
  38. rambulance エラーページを良い感じにしてくれる。 • Rack層のエラーに対応できる • rescue_from では対応できない • /xxx.json でhtmlを返す事故を避けられる

    • rescue_from でjsonの対応を忘れているケースが多い rescue_from の利点は何かあるのだろうか... 38
  39. simpacker8 8 https://techlife.cookpad.com/entry/2019/07/08/100000 39

  40. simpacker Webpackerから乗り換えるときは以下に注意。 • ページを表示するときに自動でビルドされない • webpack --watch しておく必要がある • 差分ビルドが効かない

    ! • Webpackerはファイル変更時のみビルドする • ファイルのハッシュ値を計算してる 40
  41. simpacker CIだと事前にビルドするので以下のように制御する。 手元でもCI=1を指定してWebpackのビルドを抑止できる。 RSpec.configure do |config| # NOTE: CIͰ͸ࣄલʹwebpackͷϏϧυΛ࣮ߦ͢ΔͨΊෆཁ unless

    ENV['CI'] compiled = false config.before(:context, type: :system) do compiled ||= system('yarn build:test', exception: true) end end end 41
  42. okcomputer ヘルスチェックを簡単に実装できる。 $ curl https://telemed.medpeer.jp/healthcheck default: PASSED Application is running

    (0.000s) /healthcheck/all でDB, Redisなどの疎通も確認できる。9 $ curl https://telemed.medpeer.jp/healthcheck/all Default Collection cache: PASSED Able to read and write via Redis cache store (0.002s) database: PASSED Schema version: 20200630095822 (0.009s) default: PASSED Application is running (0.000s) redis: PASSED Connected to redis, 9.99M used memory, uptime 6221501 secs, 35 connected client(s) (0.002s) sendgrid: PASSED none: All Systems Operational on 2020-07-29 17:20:18 +0900 (0.456s) version: PASSED Version: d01fd95a61df65252cb98c102001bf73282cf427 (0.000s) 9 sendgridは独自のチェックです。 42
  43. lograge ヘルスチェックのログを記録しないために追加した。 Rails.application.configure do config.lograge.enabled = true # NOTE: ELB͔ΒසൟʹΞΫηε͞ΕΔɺ/healthcheck

    ͸ϩάʹه࿥͠ͳ͍ config.lograge.ignore_actions = ['OkComputer::OkComputerController#show'] end 43
  44. sendgrid-actionmailer SendGrid APIを使って簡単にメールを送信できる。 config.action_mailer.delivery_method = :sendgrid_actionmailer config.action_mailer.sendgrid_actionmailer_settings = { api_key:

    ENV['SENDGRID_API_KEY'], raise_delivery_errors: true } 44
  45. 公式の推しはWeb APIのようです10 10 https://sendgrid.kke.co.jp/blog/?p=5009 45

  46. simplecov テストの並列実行を使うとカバレッジが壊れるので直す。11 # .circleci/config.yml - run: mv coverage/.resultset.json \ coverage/.resultset-${CIRCLE_JOB}_${CIRCLE_NODE_INDEX}.json

    - persist_to_workspace: root: . paths: - coverage/ 11 https://gist.github.com/trev/9cc964a54c8d5b62f4def891eba6b976 を参考に実装 46
  47. simplecov CircleCIでRakeタスクをを実行してカバレッジを集計する。 namespace :coverage do task :report do require 'simplecov'

    SimpleCov.start('rails') resultsets = Dir["#{SimpleCov.coverage_path}/.resultset-*.json"] results = resultsets.map { |file| SimpleCov::Result.from_hash(JSON.parse(File.read(file))) } SimpleCov::ResultMerger.merge_results(*results).tap do |result| SimpleCov::ResultMerger.store_result(result) end end end 47
  48. Codecovを使っていれば対応は要らない 48

  49. おわり 明日からの仕事に何か役に立てば幸いです。 もっと詳細を知りたい人へ 採用ページのURLを置いておきます。 https://medpeer.co.jp/recruit/ 49