ぱろっと・すたじお

技術メモなどをまったりと / my site : http://parrot-studio.com/

自社の社内勉強会に関するBlogを書きました

自分のBlogではなく、会社の技術Blogを更新したのは初めてですが、
こちらの記事を書いております(´・ω・)っ

tech.andpad.co.jp

業務ネタで何か書こうと思っていたのですが、
ちょうど社内勉強会があり、具体的なテーマも見えていなかったので、
そのまま乗っからせていただきました

他にもいろいろ話は出ていて、もっと資料も詳細だったのですが、
社内のあまり細かいところは外に出せないのもあって、
このような形に落ち着いております

わりと真面目な話、弊社はエンジニアが全然足りていないので、
もし興味があれば、ぜひお声がけください

engineer.andpad.co.jp

Railsエンジニアでなくても問題ないと思います(`・ω・´) b

チェンクロパーティーシミュレーター 閉鎖のお知らせ

長いこと更新を続けてきた「チェンクロパーティーシミュレーター」(ccpts)ですが、
2021年の2月末で閉鎖することにしました(´・ω・`)

ccpts.parrot-studio.com

github.com

セガのファンサイトに何か自分のサイトを掲載したいというきっかけから、
自分の欲しいツールを2014年からずっと更新してきましたが、
3部の終了とともに更新が止まっており、正式に閉鎖することにしました

理由の一つはチェンクロ自体のモチベーションが下がったことですが、
技術的にいろいろ試そうとは思っていたものの、
新しい仕事の関係で時間が取れず、大幅な刷新を入れるのが難しくなったのも大きいです

今もう一度作るのであれば、front-backend構成で、
frontを純粋なNuxtで作り直すと思いますが、
そこまでやる時間が捻出できませんでした

RubyやRailsなどのアップデートを試すプライベート環境は欲しいので、
また何か作るとは思いますが、いったんccptsについては終了ということで

どの程度の方が使っていたのかは全く把握できていない・・・というより、
あえてしていなかったのですが、ご利用ありがとうございました

tslintからeslintに移行した件

この春からお仕事が変わった関係で、
なかなか技術的なもろもろをいじる時間がなくなっておりました(´-ω-)

ccptsの新しいバージョン設計を考えてはあるのですが、
それに着手する余裕がなくてですね・・・

そんな中でもccptsの更新は続けていたわけですが・・・

ccpts.parrot-studio.com

github.com

・・・お仕事でTypeScriptを触っており、久々に環境構築したところ、
tslintが実質お亡くなりになっていることを知りましてΣ(゚Д゚)ガーン

探せば記事がたくさん出てくるので、詳細は割愛しますが、
2020年時点でtslintはすでに非推奨です

ということで、正しくtslintからeslintに移行することにしたのですが、
ここで問題になったのがprettierというフォーマッタです

だいたいセットで使われているものの、
eslintほど融通が利かず、既存の定義とコンフリクトしまくって、
なかなか使いづらくてですね・・・(´-ω-)

結局、prettierを使わず、eslintへの移行のみをやりました

github.com

TypeScript導入前に、eslintを細かくカスタムしており、
その設定ファイルを消し忘れていたおかげで、
initで生成された定義に昔の設定コピペして、問題なく移行できました(`・ω・´) b

ということで、早速typescript-eslintを使ってコードを調べ、
指摘を受けて修正したのがこちらなのですが・・・

github.com

・・・これ、今までtslintって動いてたんですかね?Σ(・ω・ノ)ノ

手持ちのアプリでドメイン駆動設計(DDD)を試してみよう〜導入編〜

きっかけは「WEB+DB PRESS」の記事なのですが・・・

WEB+DB PRESS Vol.113

WEB+DB PRESS Vol.113

・・・最近「ドメイン駆動設計(DDD)」の勉強をしております...φ(・ω・`)

今までも漠然と把握した「つもり」になっていたのですが、
今後のことを考えると、「正しい知識」を身につける必要があるな・・・と

結局のところ、原典にあたるしかないのですが・・

エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

実践ドメイン駆動設計 (Object Oriented SELECTION)

実践ドメイン駆動設計 (Object Oriented SELECTION)

・・・これがですね、めっちゃ難しいんですよ(´-ω-)

DDDは「概念」というか「設計手法」であり、
「原典(=エリック・エヴァンスの〜)」を読み始めたものの、
わかりづらいので「実践」を読み始めたら、また「原典」に戻ってるというΣ(・ω・ノ)ノ

途中まで読んだだけでも間違いなくわかるのは、
今までやってきた「設計」は「業務を抽象化した設計になってない」ということです

あまりに「アーキテクチャ」や「インフラ」が中に入り込みすぎており、
「業務ロジック」がこれらの(本来であれば従属であるはずの)概念に、
だいぶ影響を受けてしまっています(´・ω・`)

まあ、実際それでも動作しているし、
多くのシステムは単純なCURDアプリケーションの組み合わせでいけるのですが、
今やっているような大きめのシステムを再設計するには、あまりに手法が弱すぎます

・・・というところまでは理解しても、
正直、どこから取りかかったものかさっぱりだったので、
「WEB+DB PRESS」の記事を書いた方のBlogを参考にすることに

little-hands.hatenablog.com

とりあえず、ここを起点にいろいろ見ていくと、
なんとなく概要はつかめます(`・ω・´)

あとはこちらの同人誌とか

techbooster.booth.pm

基本的な概念に触れられており、
実際の現場を想定した例も載っている「本」なので、
こちらもおすすめです

(ただし、いずれにせよDDDに関しては「原典」に戻るしかない気はします
 用語とか初歩的な概念とか、だいたいは「原典」から引用され、
 そこからの発展的な話をしているので・・・)

それをふまえた上で、何をやるか、なのですが・・・

業務に導入するには敷居が高い技術を、
とりあえず試すために、私はいいものを持っています(`・ω・´)

ccpts.parrot-studio.com

github.com

ということで、まずは「チェンクロパーティーシミュレーター」(ccpts)の
設計を見直してみることに...φ(・ω・`)

ポイントは以下です

  • クライアント側を主体にDDDを適用する
    • ccptsの業務ロジックはクライアント側であり、サーバはデータを提供するだけなので
    • サーバAPIは「アルカナ検索」のコンテキストの一つとして考え、おそらくはリポジトリから呼ばれる
  • 実装にTypeScriptを使う
    • クライアントは元々TypeScriptで書かれている
    • TypeScriptならオブジェクト指向の機能をだいたい持っている
    • 設計から起こしたコードをそのままサービスに適用できる

ということで、軽く実装してみたドメインモデルがこちらです(´・ω・)っ


ccptsのドメイン設計 · GitHub


ccptsの主要な機能は「パーティーを編成すること」であり、
「アルカナを検索すること」はその支援機能なので、
「パーティー編集」がコアドメインで、「アルカナ検索」がサブドメインです

まずは「パーティー編集コンテキスト」に属するドメインモデルを考えて、
コードに起こしたものがgistの実装部分です

ポイントはいくつかあって・・・

  • 「ユビキタス言語」を意識して仕様を書いたので、実装に落としやすい
  • アルカナは集約ルートで、以下に大量の値オブジェクトを保持しているが、仕様に書かれてないので無視している
    • モデルの関連を仕様として記述するのが重要で、アルカナの構造は今回の範囲だと関係しない
  • 「値オブジェクト」として定義したモデルは、getterしか持ってない
  • 新たに「プレイヤーパーティー」の概念を導入したことにより、「同じアルカナを一つしかセットできない(ただしフレンドを除く)」の仕様がシンプルにカプセル化された
    • 以前はフレンドを除外するのに面倒なロジックを書いていた

・・・ざっくりこんな感じですか...φ(・ω・`)

元のコードは今までの経緯で継ぎ足してきたものなので、
ある程度整理されているとはいえ、どうしても業務ロジックが分散しています
それがUIや取得方法などと無関係に、モデルに制約や仕様が閉じた状態になりました

また、DDDの重要な概念である「ユビキタス言語」を意識して仕様を書いたので、
コードに反映させるのが簡単で、テストも何を書けばいいのか明らかになります(`・ω・´) b

あくまで「ドメインモデル」の範囲で適用しただけですが、
これだけでもなんとなく方向性は見えてきたので、
このドメインモデルを育てながら、本を読み進めてみます

ReactOnRailsとSSRとWebpackerを捨ててWebpackに移行した件

久々に「チェンクロパーティーシミュレーター」(以下「ccps」)のお話です

ccpts.parrot-studio.com

github.com

Rails6のRC2がリリースされたので、夏休みの課題的な感じで、
Rails6への移行を進めようと思ったのですが・・・

・・・なぜかReactOnRailsとWebpackerを捨てて、
ピュアなWebpackに移行していたのですΣ(・ω・ノ)ノ

react_on_railsを破棄し、webpackに移行 · parrot-studio/cc-pt-viewer@592a34d · GitHub

正直、短期間にいろいろやり過ぎて、私の中でも整理しきれていないのですが、
夏休みの課題をレポートとしてまとめておくのも必要なことなので、
覚えている範囲でいろいろ書いていきます...φ(・ω・`)


今回言いたいこと
  • サーバサイドレンダリング(SSR)はかなり重たいので、他の部分で最適化できないか検討してみた方が良い
  • Railsの「Webpacker」を理解するには「Webpack」の知識が必要で、WebpackがわかるならWebpackerはむしろ冗長なのでは?
今回の経緯

面倒なので、ざっと経緯をまとめておきます...φ(・ω・`)

  • SSRで運用中にアプリレベル監視を入れたところ、頻繁に警告が来るようになった
    • 雑だった箇所を修正したり、攻撃してきたIPを弾いていった結果、監視そのものが重くする要因と判明
    • 5分に1回監視して落ちるようなサイトって相当に問題では?
    • ReactOnRailsでSSRのキャッシュを使おうと思ったら、有料でないと使えない
  • 結果、どこかのタイミングでSSRをやめて設計変更しようと決める
  • その後、Rails6のRC2がリリースされたことを知る
    • いつものように新規でRails6プロジェクトを立ち上げ、コードを移植していた
  • Railsでjsã‚„cssをまとめていたSprocketsが、Rails6だとデフォルトではないことを知る
  • この時点で、完全にWebpackerに移行すると決め、いい機会なのでReactOnRailsを切り捨てる
  • 試行錯誤の結果、Webpackerのみでdevelopmentの動作を確認する
  • stagingでのデプロイを試そうといじっていて、運用のための要件(例:圧縮等)が満たせない・・・というより、Webpackerの情報が少なすぎて破綻する
  • 「Webpacker」ではなく「Webpack」の情報なら山ほどあり、Webpackerを破棄する事例をみて、Webpackerの破棄を決める
  • Webpackのconfigでかなり苦戦したが、結果的に無駄に含まれていたコードを排除することに成功
    • productionデプロイも圧倒的に高速化
    • js/cssも軽量化
    • SSRしてないのに、思ったより表示が速い
  • 「SSRã‚‚Webpackerもいらなかったんや・・・(´-ω-)」 <- イマココ
参考にしたサイト

以降は私自身のためのメモ書きです...φ(・ω・`)




SSRの限界と結果的に得られたメリット

ccptsはjQueryとReactで構築されているため、
SSRは難しそうに思いますが、「ブラウザが必要とするコード」と、
「静的なHTMLを生成するためのコード」を分離することで、SSRに成功しました(`・ω・´)


これ自体は満足していたのですが、問題はやはりサーバの負荷です
ある時期からさくらVPSの機能で監視をセットしたところ、
監視アクセスの負荷で警告が来るようにΣ(゚Д゚)ガーン

5分に1回程度、TOPにアクセスされる程度で重くなる*1ようでは、
Webサイトとして機能してないですし、
そのためにVPSをバージョンアップするのも何か違います

そもそも、SSR化の記事でも書いたように、SSRは最後の手段であり、
可能であれば最初から適切なファーストビューを組める方がいいのです

ReactOnRailsの切り離し

そもそもReactOnRailsは何をしていたのでしょうか?

  • jsの依存関係解決と統合
  • まとめたjsã‚’Railsのassetsとして扱えるようにする
  • Rubyの文脈で渡したデータを、Reactの初期propsとして渡す
  • まとめたjsからサーバサイドでHTMLを生成する(オプション)

他にも、デプロイ時の「assets:precompile」にフックをかけるとか、
細かいことをやっていますが、大枠ではこんな感じです

しかし、RailsがWebpackerを採用して以降、
Rails側と協調していろいろな機能が整理されていった結果、
「Webpacker+α」くらいの立ち位置になっていました

つまり、クライアントサイドだけを考えるなら大きなメリットはなくて、
SSRまで考える時に楽(になる可能性がある)・・・ということです

で、今回SSRをやめることにしたので、
純粋なWebpackerを使ってもそれほど問題にはなりません

  • jsの依存関係解決と統合 => そもそもWebpackerの仕事
  • まとめたjsã‚’Railsのassetsとして扱えるようにする => Rails側でSprocketsを使わない手法が実装されているので、本質的には不要
  • Rubyの文脈で渡したデータを、Reactの初期propsとして渡す => 初期データの塊をJSONで出力し、Reactã‚’appendする前にparseして渡す

今までReactOnRailsがうまいことやってくれていたWebpackerの仕組みについて、
自分で調べる必要はありましたが、そこまで大きな問題はありませんでした

・・・少なくとも、「ReactOnRailsを取っ払う」という文脈においては(lll゚Д゚)

Webpackerの辛さ

問題が一気に出たのは、何とかdevelopmentで「気持ち悪いけど一応動く」まで到達したので、
stagingで動かしてみよう、言いかえれば、「運用できるレベルで構築しよう」と考えた時です

今まではReactOnRailsがいい感じにコードの圧縮やzip化をやっていてくれていたので、
あまり意識していなかったのですが、Webpackerで自前でやろうとすると、
一気に必要な情報が膨れ上がりましたΣ(゚Д゚;≡;゚д゚)

しかも、「Webpacker」でググっても全然情報が出てこないし、
出てきた情報も「なんでそういう書き方で動くのか?」がわからなくて気持ち悪いわけです

やっと手に入れた情報通りに設定しても動作が想定通りにならず、
これが「新しいWebpackerがリリース前でバグがあるから」なのか、
「新しい書き方をしないとダメ」なのか、全く見えなくなりました(´-ω-)

結論として、この記事の通りです

今日から簡単!Webpacker 完全脱出ガイド - pixiv inside

この記事を読んでめちゃくちゃうなずきまくったので、
Webpackerを捨てる決意をしました( ゚Д゚)y─~~

「Webpack」について理解することで、
「WebpackerのDSL」が何をしようとしていたのかは理解しましたが、
Webpackを理解してからだと、あまりに回りくどいです

アップデートも遅くて、Webpackerが依存しているライブラリのために、
GitHubからセキュリティ警告が来ていたりして、
アップデートが来ないとどうにもならなかったりとか
(Webpackに移行して、各ライブラリを最新にしたらもちろん警告はなくなりました)

Railsが「Webサイトに必要な技術的要素」をフルスタックで容易にしているのは利点ですが、
クライアントサイドについてはRailsと無関係なプラクティスがあふれており、
それをRailsの文脈にわざわざ変換させるのは、実際の業務においても面倒だと思います(´-ω-)


「webpack.config.js」を一から理解する

ということで、純粋な「Webpack」について、一から学んでいきました

webpack 4 入門 - Qiita

webpackのTree Shakingを理解して不要なコードがバンドルされるのを防ぐ - Qiita

Webpackも4になってだいぶ使いやすくなったそうで、
modeパラメータを指定することで、
最低限想定した処理をしてくれるとか、簡単になっています

とはいえ、今回はあくまで「Railsの上で動かす」ことが前提であり、
「index.html」からアクセスさせるわけではないので、
一般的なwebサイトとは少々やり方が異なります

ポイントは以下です

  1. 元のWebpackerにあわせて、「app/javascript/packs」を起点とする
  2. ビルドしたコードを「public/packs」に吐き出す
  3. viewから自分で書いたhelperである「javascript_pack_tag」等を呼び出す
  4. Railsのフロントにあるサーバ(Nginx等)から直に「public/packs」以下を参照させる

1.2.のディレクトリはどこでもいいのですが、引き剥がしたとはいえRailsのプロジェクトなので、
一応Webpackerの作法に合わせて決めてあります

3.が最大のポイントで、Railsが用意していた「javascript_pack_tag」を自分で実装する必要があります

  1. 指定された名前からmanifest.jsonを見て、実体となるpathを取得する
  2. そのpath(あるいはURL)をRailsの文脈で出力する

実際にはこんな感じです(´・ω・)っ

結果的に、このようなタグが出力されます

<script src="/packs/application-d1623c84a0914a0a63d9.js" defer="defer"></script>

Railsのデフォルト設定ではpublic以下に直アクセスさせてくれないので、
Nginx側でこのような設定が必要です*2

location ~ ^/packs/  {
  root /path/to/public;
  gzip_static on;
  expires max;
  add_header Cache-Control public;
}

これ自体は、以前「public/assets」に対して設定したものと全く同じです


その他、気になったこと

「依存するファイルを全て起点から参照させる必要があるのはわかるが、
 CSSまで一緒にされると、jsが解釈されるまでCSSが適用されない」


なんとなくで理解しないWebpackのCSS周辺 - Qiita

GitHub - webpack-contrib/mini-css-extract-plugin: Lightweight CSS extraction plugin


「fontawesome5はwebfontからSVGによる描画に変わったようだけども、
 jsで組み込まれるのでbuildしたファイルが肥大化してやばい」

「React上でiタグによるフォント描画をさせると、
 動的な変更を加えてもにアイコンが固定されて変わらない」

1年後に差がつくFont Awesome5 ~フロントエンド開発(ES6,Webpack4,Babel7)への導入~ - Qiita

GitHub - FortAwesome/react-fontawesome: Font Awesome 5 React component


「productionでもsource-mapが含まれてしまってサイズがでかい」

そもそも、developmentでだけ出力する

const config = {
  // 共通の設定
}

module.exports = (env, argv) => {
  if (argv.mode === 'development') {
    config.devtool = "source-map"; // developmentでだけ

    // webpack-dev-serverの設定とか
  } else if (argv.mode === 'production') {
    // jsとかcssとかzipで圧縮するとか
  }

  return config;
}

この「環境ごとにwebpack.configをいじる」をDSL的にやりたかったのが、
webpackerの「config/webpack」だと思うのですが、
このように書けばいいだけの話なので、やはり冗長だと思います(´-ω-)


「Reactから参照していないが、htmlから参照している画像(例:favicon.ico)が
 Webpackのbuildに含まれない」

最初は起点として設定していたのですが、
要は起点になるjsにimportされていればいいので、雑に追加しました

// appilication.js

import './images/ccpts.png'
import './images/favicon.ico'

これによりmanifest.jsonにも画像が登録されるため、
自前のhelperからいい感じに参照できます

デプロイの修正

今もcapistranoによるデプロイをしており、
以前は「assets:precompile」のタイミングで、
ReactOnRailsがいい感じにbuildをしていてくれました

しかし、もうWebpackerすらも使ってないので、
どうデプロイしたものか悩んだのですが、
要するに「ローカルでbuildして、適切なpathにupload」でいいわけです

# もはや不要
# set :assets_roles, :app
# set :yarn_roles, :app
# set :yarn_flags, '--prefer-offline --silent --no-progress --production'

namespace :deploy do
  namespace :webpack do
    desc 'build packs'
    task :build do
      # 手元でbuildを実行
      run_locally do
        execute "yarn run build:production"
      end
    end

    desc 'upload packs'
    task :upload do
      on roles(:app) do
        within release_path do
          with rails_env: fetch(:rails_env) do
            # リモートにbuildしたファイルをupload
            execute "mkdir #{shared_path}/public/packs/images -p"
  
            files = []
            Dir.glob('public/packs/*').each do |f|
              files << f if File.file?(f)
            end

            images = []
            Dir.glob('public/packs/images/*').each do |f|
              images << f if File.file?(f)
            end

            pack_path = "#{shared_path}/public/packs"
            files.each do |f|
              upload! f, pack_path
            end

            image_path = "#{shared_path}/public/packs/images"
            images.each do |f|
              upload! f, image_path
            end
          end
        end
      end
    end

    desc 'build, upload with webpack'
    task :precompile do
      invoke 'deploy:webpack:build'
      invoke 'deploy:webpack:upload'
    end
  end

  before :migrate, 'webpack:precompile' # migrateの前にwebpack関連を実行
end

今まではサーバでbuildしていたので時間がかかりましたが、
自分のマシンでbuildすれば速いし、
assets関連のタスクも全部削っていいので、デプロイがシンプルになりました

これだとAPサーバにyarnどころかnodeもいらないので、
サーバ構成そのものもシンプルになります(`・ω・´) b


今回のまとめ

これはSSRをやめた時に気づいたことですが、
SSRのためにはサーバサイドで、
画面表示に必要な情報を先に揃える必要がありました

これは当たり前のようですが、以前は画面を表示してから
非同期にデータを取りに行く設計になっていました

これにより、クライアントとサーバサイドの責務を切り離し、
わりと自由にクライアントをいじれるようにしたわけです

しかし、当然ながらこれがクライアントが遅くなっていた原因でもあります

  • データ取得のための通信が数回発生する
  • 取得したデータで画面をレンダリングしなおすので、再描画コストがかかる

SSRをやめても「初期表示に必要な情報を渡す」という設計は維持したため、
クライアントの処理でも以前に比べて格段に高速化されました(`・ω・´) b

(技術的な興味もあったとはいえ)この速度感なら
面倒なSSRにしようとは思わなかったはずで、
一周回ってやっと適切な設計にたどり着いた気がします


そして、Webpackについてですが、
webpack.configについてきっちり理解し、
自動生成されたままだとか、コピペしただけの設定はなくなりました

それぞれが何をしていて、どのタイミングで適用されるのかを理解しているので、
余計な設定はなくなり、全体がスリムで見やすくなりました

その結果、jsやcssにも余計なコードが含まれなくなり、
jsのサイズだけ見ても、1/2以下に減っており、
起動時の待ち時間もだいぶ減りまして、SSRほどではないにしても、十分高速になりました*3

なにより、「Webpackの知識」はRailsと関係なく、
他のアーキテクチャでも応用できる知識であり、
フロントエンド開発フローの構築に役立ちます

ReactOnRailsがなければ、そもそもReactを使おうとも思わなかったので、
非常に感謝はしていますが、やっと補助輪が外れて、
「本来の運用」ができるようになった・・・ということですね(`・ω・´)

*1: 実際には監視の負荷+ちょっと多めのアクセス いずれにせよ、あまり使われていないから問題になってなかった・・・というだけです(´-ω-)

*2: productionのconfigを一箇所書き換えれば可能なのですが、そもそもパフォーマンスのためにそのような設定がデフォルトなので、それに従う方が適切です

*3: Googleの分析だと警告が出まくりますが、そもそも複雑なサイトであるため、ある程度はあきらめてます