Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

自己紹介 • 名前: 正徳 巧 • aka: 神速 • 社歴: 2019年12月入社 • 11月から仕事はしてた • GitHub: @sinsoku (画像右上) • Twitter: @sinsoku_listy (画像右下) 2

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

5

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

7

Slide 8

Slide 8 text

話すこと / 話さないこと • ✅ AWSのインフラ構成 • ✅ Railsの設定の紹介 • ✅ gemのTipsの紹介 • ❌ リモートワークについて • ❌ プロジェクトの進め方 • ❌ Dockerに関する話 8

Slide 9

Slide 9 text

AWSのインフラ構成 9

Slide 10

Slide 10 text

AWSのインフラ構成 10

Slide 11

Slide 11 text

CloudFrontを置く理由 • 静的ファイルのキャッシュ • 将来のパフォーマンス改善のため • セキュリティ対策(DDoSなど) • デプロイ時にassetsが404になる問題の回避1 1 ECSのデプロイ時に一定確率で静的ファイルが404になる問題を回避する https://qiita.com/hareku/items/6be1b71e58033b9739fd 11

Slide 12

Slide 12 text

AWSのインフラ構成(デプロイ編) 12

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

プロジェクト初期の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

Slide 15

Slide 15 text

AWSのインフラ構成 15

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

Railsの設定の紹介 17

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

複数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

Slide 21

Slide 21 text

ActiveRecord::Middleware::DatabaseSelector • GET, HEADリクエストはリードレプリカ • PATCH, DELETEリクエストはプライマリ リクエスト内で接続先を切り替えることは可能。 def show ActiveRecord::Base.connected_to(role: :writing) { # ΞΫηεϩάͷอଘɺ௨஌ͷطಡॲཧͳͲ } end 21

Slide 22

Slide 22 text

テストの実行順序で稀にテストが落ちる2 2 https://github.com/rails/rails/issues/39205 22

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

複数ブラウザの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

Slide 25

Slide 25 text

CircleCIのマトリックスと組み合わせる3 • Chrome • iPhone 6(シミュレータ) • Firefox の3パターンをテストしてい る。 3 https://circleci.com/blog/circleci-matrix-jobs/ 25

Slide 26

Slide 26 text

機微情報のフィルタ 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

Slide 27

Slide 27 text

おまけ: ログレベルの変更 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

Slide 28

Slide 28 text

デフォルトがまた変わるかも?5 5 https://github.com/rails/rails/pull/39707 28

Slide 29

Slide 29 text

変わらない可能性も... 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

Slide 30

Slide 30 text

gemのTipsの紹介 30

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

factory_bot バリデーションエラーになるファクトリをテストで検知できるよ うにしておく。 RSpec.describe 'FactoryBot' do describe '.lint' do it { FactoryBot.lint traits: true } end end 34

Slide 35

Slide 35 text

rails_admin 管理画面のルートにはマウントしない。 Rails.application.routes.draw do mount RailsAdmin::Engine => "/tables", as: 'rails_admin' end 複雑な要望は素直にRailsで機能を作る。 カスタマイズし始めると学習コストが高く、Railsのアップグレー ドが辛くなりやすい。 35

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

rambulance エラーページを良い感じにしてくれる。 • Rack層のエラーに対応できる • rescue_from では対応できない • /xxx.json でhtmlを返す事故を避けられる • rescue_from でjsonの対応を忘れているケースが多い rescue_from の利点は何かあるのだろうか... 38

Slide 39

Slide 39 text

simpacker8 8 https://techlife.cookpad.com/entry/2019/07/08/100000 39

Slide 40

Slide 40 text

simpacker Webpackerから乗り換えるときは以下に注意。 • ページを表示するときに自動でビルドされない • webpack --watch しておく必要がある • 差分ビルドが効かない ! • Webpackerはファイル変更時のみビルドする • ファイルのハッシュ値を計算してる 40

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

lograge ヘルスチェックのログを記録しないために追加した。 Rails.application.configure do config.lograge.enabled = true # NOTE: ELB͔ΒසൟʹΞΫηε͞ΕΔɺ/healthcheck ͸ϩάʹه࿥͠ͳ͍ config.lograge.ignore_actions = ['OkComputer::OkComputerController#show'] end 43

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

公式の推しはWeb APIのようです10 10 https://sendgrid.kke.co.jp/blog/?p=5009 45

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

Codecovを使っていれば対応は要らない 48

Slide 49

Slide 49 text

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