クラウドワークス エンジニアブログ

日本最大級のクラウドソーシング「クラウドワークス」の開発の裏側をお届けするエンジニアブログ

新卒エンジニアによる配属後2ヶ月の振り返りと、成長するために必要そうなこと。

はじめに

こんにちは
今年の4月に入社した欧州サッカーが大好きな19卒エンジニアの伊達(@b0ntenmaru)です。

大学時代は体育会系の部活で汗を流しながら、独学でプログラミングを勉強してサービスを作ったりしていました。

今年のクラウドワークスの新卒エンジニアは自分一人で、周りのエンジニアはほぼ中途で入社された方という環境で、その中で日々課題ドリブンで成長するために試行錯誤しています。 今回は新人の自分が配属後2ヶ月を振り返りつつ、その中で感じた新人エンジニアが成長するために必要そうなことを書いて行きたいと思います。

配属後2ヶ月で何をやったか

クラウドワークスでは 4月1日 の入社式から2週間の新卒研修を経て、各部署に配属されるのですが、同期の中で唯一エンジニアの自分はエンジニアリング部の玉露チームに配属されました。

玉露チーム

玉露チームはクラウドワークスのマッチング UX をよりよく改善するためのチームで、現在はワーカーがやりたいお仕事をすばやく見つけられるようにお仕事検索画面の再設計を行なっており、最近のリリースではお仕事検索機能に新しい絞り込み検索機能を追加し、それを Vue.js で実装しました。

crowdworks.jp

ちなみに玉露の由来は、過去に存在した玉露チームの前身であるまっちゃ(抹茶)チームからきていて、抹茶よりカフェインの量が多い(進化?)玉露という名前になったそうです。 自分が入社する以前にはなりますが、玉露チームの施策については是非こちらもご覧ください。

qiita.com

qiita.com

機能開発からリリースまでの流れは以下の図のようになっていますが、実装に関しては基本的にモブプログラミング(「以下、「モブプロ」と略します)*1での実装で、その後タスクによっては個人での実装に入っていく、という流れになっています。

※ POとはプロダクトオーナーの略で、スクラム開発における、チームが生み出す成果物の方向性を決める「舵取り役」にあたります。

モブプログラミングとは?

モブプログラミングとは3人以上(2人はペアプログラミング)で1つのプログラムを書く手法のことで、1人がコードを書き、他の人はそれを見ながら意見を言って、プログラムを作っていきます。コードを書く人をドライバー、意見を言う人をナビゲーターと言います。
つまりみんなであーでもない、こーでもない、と言いながらその内の一人がコードを書いていくということですね。 新人の自分からすれば先輩エンジニアのコーディングをみるだけで勉強になりますが、チームで課題に直面するので、その課題を解決するための知見を共有できたりします。

2ヶ月間での失敗

玉露はチームとしての評判もよく、社内 MVP になったりするほどいいチームで、配属後もすぐに馴染むことができ、意見や質問をしやすい環境でした。

ですが最初は技術の話になったりすると周りのエンジニアが何を言っているのか理解できなかったり、自分のタスクだけ時間がかかったり、モブプロではわからないことを教えていただいてもなかなか理解できず、せっかく教えていただいているのに理解できないことに加えて、自分が教えていただいている時間が長くなったことでコードを書く時間がなくなってしまうこともありました。

これらの体験からチームに貢献できていないという気持ちが強くなり、一人で考え込む時間が多くなりました。

そこから「こんなことを聞いていいんだろうか」「相手の時間を奪ってしまうし、もっと調べた方がいいか」とどんどん一人で解決しようと迷走してしまったり、 モブプロ中は後で自分で調べると決め、理解が曖昧なままその場では質問しないということをしてしまいました。

その結果、個人に振られたタスクはリリースには間に合わず、モブプロは実装がどのようになっているか曖昧なまま進んで行きました。

相談してみた

わからないこととの付き合い方に悩んでいる時期に、メンター(@t0yohei)・チームの先輩エンジニア(@mayoxtuna)・マネージャー(@ysk_118)に自分が思っていることを 1 on 1 や slack で相談したところ、

  • 「最初は分からない事が沢山あって当然だから沢山聞いていいよ」
  • 「分からないことを追求することで、チームは学びを共有できる」
  • 「技術に関しては知ってるか知らないかってだけでわからないこととどう付き合うか大事、てか俺もわからんことあるよ」

と言われ、それまでしていた周囲への遠慮などは不要なものだと気づきます。

まとめ

振り返ると、性格的に気軽に聞くということが苦手だったり、学習の仕方だったり、いろいろ要因はありそうですが、それらの根底にあるのは 考えこんでしまい、自分をさらけ出していなかったことでした

なのでこれらを踏まえて、新人が成長していくために必要そうな最初のステップは、悩みすぎず、わからないことをオープンにすることだと思っていて、そこから自分で少しずつできることの幅を広げていけばいいと思っています。

現在試行錯誤している最中ではありますが、1週間前わからなかったことがわかるようになったりするなど、小さな成功体験を積み重ねることができている気がしていますし、これを実行して成長していける環境がクラウドワークスにはあると配属後2ヶ月経って実感しています。

おわりに

いかがでしたでしょうか。まだ自分自身毎日試行錯誤している段階(ブログを書くこともTry)ですが、自分と同じ境遇の方の参考になればと思います。

twitter.com

We're Hiring !

クラウドソーシングのクラウドワークスでは成長し続けていたいエンジニアを募集しています www.wantedly.com

*1:モブプログラミングについてはこちらも是非ご覧くださいengineer.crowdworks.jp

engineer.crowdworks.jp

Gemの更新時に、依存関係でアプリケーションが死んだ際のデバック記録

はじめに

みなさんこんにちは、クラウドワークスのじゅんてつです。

ここしばらく何かしらの更新(gem、rubyrails)をしています。そんな中、rubyの更新をしてたらgemの依存関係で大変な目にあったので事例を紹介しようかと思います。

何があったのか

非同期メール送信処理の例外通知がすべての始まりでした・・・

アプリケーション上で例外が発生するとdelayed_jobにrollbar通知の処理を引き渡していたんですが(仕様)、gem ojとrollbarのバージョンの組み合わせに問題があり(原因)、rollbarの通知でojを呼び出す処理が無限ループを引き起こし(事象)、delayed_jobのプロセスを落としてしまう事象がありました。

Gem補足

delayed_job: メール送信やバッチ処理をバックグラウンドで非同期的に実行するためのgemです。 rollbar: 例外が発生するとリアルタイムに報告をあげてくれる、運用のためのgemです。 oj(Optimized JSON): JSONパーサー

対応のサマリ

この問題はojを3系に上げることで回避できました。

Ruby2.3のEOL対応としてRuby2.4への更新をしていたところ、ojを2.18以上に更新する必要がありました。ojを更新が原因で、delayed_jobのプロセスを落としてしまうことがデバッグして分かりました。

なぜoj最新版の3系ではなく2.18だったのか?

  • rollbar 2.15.0
  • delayed_job 4.0.6
  • oj 2.12.2
    • update to 2.18.6
  • 内製gem

当時は上記のバージョンで稼働していました。この状態から bundle update oj を実行すると内製のgemの依存関係に引きずられて2系の最新2.18.5に更新される状態でした。

spec.add_runtime_dependency "oj", "~> 2.12"

oj 2.18からRuby2.4に対応していたので要件は満たしていましたし、内製のgemを一緒に更新すると話がややこしくなるためまずは2.18に更新することにしました。

どんなエラー通知が飛んできたのか

ojを更新して間もなく、Datadogから「delayed_jobのプロセスが既定値の8件以下ですよ」という通知がslackに届きました。確認してみるとたしかにオンライン用に動いているはずのプロセスがすべて落ちていました。(2件ほど動いているのはバッチ処理用)

通知などのバックグラウンド処理が止まったままはまずいので、詳しい調査よりdelayed_jobのプロセスを回復させることを優先。ほぼ間違いなくoj更新が原因なのでリバート。

プロセスが落ちる原因はrollbarなのでは?

サーバのログを漁ったり、原因の切り分けをしたり、長い長い調査の末に「プロセスが落ちる原因はrollbarなのでは?」という仮説にたどり着きました。アプリケーション例外の発生はサーバログに残っているのに、その通知をするrollbarが動いておらず、その例外発生の時間を境にdelayed_jobのプロセスが落ちていたからです。

rollbarが非常に怪しいことがわかりましたが「どうしてdelayed_jobのプロセスが死ぬのか」を突き止める必要がありました。

まずは検証

rollbarの例外通知が有効化されているステージング環境で検証をしてみたところ、確かにrollbarが通知するタイミングで落ちていることが分かりました。ローカルの開発環境では通知の必要がないため無効化されていたため、有効化してデバッグできるようにしました。

動作確認すると、事象が再現。delayed_jobのプロセスが落ちました。

どうしてdelayed_jobのプロセスが死ぬのか(原因調査)

本当にrollbarなのか、実際に何が起こっているのかを確認するため bundle install されたgemに binding.pry を仕込んで動作させてみました。

Frame number: 0/77

From: /usr/local/bundle/gems/multi_json-1.13.1/lib/multi_json/adapter.rb @ line 26 MultiJson::Adapter.dump:

    24:       def dump(object, options = {})
    25:           binding.pry
 => 26:        instance.dump(object, cached_dump_options(options))
    27:       end

[1] pry(MultiJson::Adapters::Oj)> continue

Frame number: 0/78

From: /usr/local/bundle/gems/multi_json-1.13.1/lib/multi_json/adapters/oj.rb @ line 39 MultiJson::Adapters::Oj#dump:

    37:       def dump(object, options = {})
    38:     binding.pry
 => 39:  options.merge!(:indent => 2) if options[:pretty]
    40:         options[:indent] = options[:indent].to_i if options[:indent]
    41:         ::Oj.dump(object, options)
    42:       end

[1] pry(#<MultiJson::Adapters::Oj>)> continue

Frame number: 0/38

From: /usr/local/bundle/gems/multi_json-1.13.1/lib/multi_json/adapter.rb @ line 26 MultiJson::Adapter.dump:

    24:       def dump(object, options = {})
    25:           binding.pry
 => 26:        instance.dump(object, cached_dump_options(options))
    27:       end

[1] pry(MultiJson::Adapters::Oj)> continue

Frame number: 0/39

From: /usr/local/bundle/gems/multi_json-1.13.1/lib/multi_json/adapters/oj.rb @ line 39 MultiJson::Adapters::Oj#dump:

    37:       def dump(object, options = {})
    38:     binding.pry
 => 39:  options.merge!(:indent => 2) if options[:pretty]
    40:         options[:indent] = options[:indent].to_i if options[:indent]
    41:         ::Oj.dump(object, options)
    42:       end

[1] pry(#<MultiJson::Adapters::Oj>)> continue
rake aborted!
NoMemoryError: Too deeply nested.
/usr/local/bundle/gems/multi_json-1.13.1/lib/multi_json/adapters/oj.rb:41:in `dump'
/usr/local/bundle/gems/multi_json-1.13.1/lib/multi_json/adapters/oj.rb:41:in `dump'
/usr/local/bundle/gems/multi_json-1.13.1/lib/multi_json/adapter.rb:26:in `dump'
/usr/local/bundle/gems/multi_json-1.13.1/lib/multi_json.rb:139:in `dump'
/usr/local/bundle/bundler/gems/rollbar-gem-e3a35525d1b1/lib/rollbar/json.rb:18:in `block in dump'
/usr/local/bundle/gems/multi_json-1.13.1/lib/multi_json.rb:147:in `with_adapter'

原因はrollbarで、multi_jsonからのoj呼び出しがループしていました。 rollbarとojのバージョンの組み合わせに問題があったという結論。 内製gemの依存関係を解消したうえで、oj 3系で試してみたところループが解消され一件落着となりました。

まとめ

普段はgemにpryを仕込んだりしないんですが、今回はRubyのEOL対応の兼ね合いで踏み込んだデバッグをしました。依存関係で古いバージョンで固定されていたため難儀していた印象が強かったです。日頃から依存関係を解消したうえでキャッチアップしていくことが大事だと思いました。

おまけ

gem更新が滞っていた課題から、弊社ではDependabotを導入しました。自動でgemの更新を検知してくれてPRまで作ってくれるようになり、Gemfileが放置されにくい運用になりました。

slackにも更新があったことが通知されるため、いまでは日々gemが更新されています。まだ導入していないみなさんも、一度試してみてはいかがでしょうか。

We're Hiring !

クラウドソーシングのクラウドワークスでは成長し続けていたエンジニアを募集しています www.wantedly.com

crowdworks.jpのエンジニアリング戦略(2019年6月現在)

f:id:yo-iida:20190612101305p:plain:w600

最近は人事のほうにも片足突っ込んでおりエキサイティングな日々を送っております飯田です。

今回は現在のcrowdworks.jpのエンジニアリング戦略についてお伝えできればと思います。

  • 状況の整理
  • これまでの中長期戦略の反省
  • 中長期の技術的投資のための専任チームの発足
    • フロントエンドチーム(チーム名: トリュフ)
    • リファクタリングチーム(チーム名: バグハンター)
  • 継続的バーションアップの浸透
  • 今後の組織戦略
    • エンジニアの役割定義
    • 今期エンジニアリング部の目標
  • 個人的なこと
  • We Are Hiring

状況の整理

crowdworks.jpはサービスインから7年が経過し、モノリシックなRailsアプリケーションのコードは30万行を超える規模感に成長しています。

f:id:yo-iida:20190611210040p:plain:w600

一方で、コード行数の増加量はここ最近の傾向としては鈍化しており、ファイル変更数の推移も低下の傾向となっています。以前から巨大なクラスの変更が難しいという課題はありましたが、最近の傾向からは日常的な施策としても大きな打ち手を打ちづらくなって来ているという課題が顕著になってきました。

f:id:yo-iida:20190611210036p:plain:w600

ここ最近は何を進めるにしても事前にそれなりの規模のリファクタリングを行なっているチームが増えてきているように感じています。 現在短期施策を担うチームはプロダクトオーナーがチームにコミットしていて、組織的プロダクトマネジメントも機能する体制にはなりましたが、施策をシャープにできてもリリースが追いつかなければ事業の持続性は作れません。

また、フロントエンドに関してもここ数年大きな進化を遂げられていない課題感があります。crowdworks.jpのフロントエンドはRailsと密結合になっており、サーバーサイドのデータをViewで受け渡しする実装が画面単位でもグローバルでも様々なアプローチで実装されていました。詳細は、元クラウドワークス社員で現在副業としてフロントエンド開発を強力に支援してくれている @suusan2go の記事が詳しいですが*1、フロントエンドもなかなかに技術的負債がたまり開発速度が低下した結果、レガシーで無秩序なUIが増えてデザイナー・エンジニア双方での懸念が高まりました。

engineer.crowdworks.jp

インフラレイヤーに関してはSREのチームメンバーが主体となって開発を進めていて、現在の大きなテーマとしては本番環境のDocker化です。動かすアプリケーションの規模感に準じてインフラも複雑になりがちで、例えばログの出力であったり、メール送信の処理であったり、コンテナに移し替えるにあたりアプリケーションの広範囲に渡って設定の変更が必要になります。SREのメンバーはアプリケーションにも強いメンバーが多く、アプリケーション側の修正までチーム完結で動けますが、トイルなどの課題も多い中で地道に前に進んでいる状況です。

*1:@suusan2goことスズケンさんは退職されてからも難易度の高い技術課題に取り組んでくれており本当に有難い限りです。

続きを読む

メンテ不能になったフロントエンド環境を立て直す話

エンジニアの @suusan2go です。2017年の10月まではクラウドワークスに社員として参画していましたが、現在はフリーランスのエンジニアとして、主にフロントエンド環境の改善・支援を行ったり、ちょっとだけRailsのアップグレードを手伝ったりしています。

Railsのアップグレードを手伝っている様子

今回は、わかるエンジニアがいなくなり無人化してしまいメンテナンス不能になったフロントエンド環境を立て直す話です。

クラウドワークスのフロントエンド事情

クラウドワークスは最初のコミットが2011年です。当然その頃はVue.jsやReactなんていうものは登場もしておらず、2015年ころまではSprocketsを使ったアセットパイプライン、CoffeeScriptjQuery によりフロントエンド環境は記述されていました。

f:id:suzan2go:20190527111914p:plain
当時のCTOによる記念すべき最初のコミット

しかしながら、2015年の頃にはよく言われるように、大きくなったアプリケーションでjQueryで複雑な画面を記述して動作を変更するのは大変に辛い状況になっており、そのタイミングでReactやVue.js、ES6、Babelといったキーワードがバズりはじめたということもあって、当時在籍していたエンジニアによりSprocketsとは別のフロントエンドビルド環境が誕生しました。

2015年夏 frontend ディレクトリの誕生

Railsアプリケーションと同じリポジトリの中に frontend/ というディレクトリを切る形で、ES6(ES2015)・Vue.jsを記述するためのフロントエンドビルド環境が誕生しました。技術構成としては以下のようになっています。

  • gulp
  • Browserify
  • Babel (Babelify)
  • Vue.js (0.12.10)
├── app # Railsのコード
│   ├── assets
│   │   ├── javascripts
├── package.json
├── gulpfile.coffee
├── frontend # 新フロントエンド環境
│   ├── javascripts
│   │   ├── src
│   │   └── build

frontend/ ディレクトリでビルドされたJSをSprocketsのアセットパイプラインに組み込んで、Railsから配信するという形をとっていました。当時のRailsプロジェクトとしては割とモダンな構成と言えるのではないでしょうか。 新しく作成されたfrontend/ ディレクトリにより、クラウドワークスの中でも重要な機能がいくつか書き換えられることとなりました。

しかしながら、ES2015やVue.jsを使用するもののみ frontend/ ディレクトリを使うというルールになっていたこと、また後述するクラウドワークス独自のRailsとのインテグレーション機構が作り込まれていたこともあり参入障壁が高くなってしまい、 frontend/ ディレクトリは導入に関わったエンジニア + 数名のみしか触れない環境になってしまいました。

独自のルーティング、Railsからのデータ渡し機構

特にエンジニアを遠ざける原因となってしまったのは Railsとの独自のインテグレーション機構です。Vue.jsを使用する画面はクラウドワークスの中でも10くらいだったのですが、全画面で全てのVue.jsのコンポーネントとルーティング機構(どの画面でどのコンポーネントをrenderするか)がbundleされたJSを読み込んでおり、またRailsからのデータの渡し方も独自の仕組みで行われていました。

//  疑似コード
import Base from './viewmodels/base';
import Dashboard from './viewmodels/dashboard/dashboard';

export default class Router {
  constructor() {
    this.routes = []
    this.route("/user-dashboard", Dashboard);

    this.route("*", Base);
  }

  route(path, vm) {
    this.routes.push(new Route(path, vm));
  }

  exec() {
    var pathName = location.pathname;
    var first_match_route = _.find(this.routes, (route) => { return route.isMatch(pathName); });
    if (first_match_route != undefined) {
      global.CWViewModel = new first_match_route.ViewModel();
    }
  }
}

Vue.jsのコンポーネントは以下のようになっており、RailsレンダリングするERBのbody配下を全てVue.jsのテンプレートとして扱っています。

// viewmodel/sample.vue
import Vue from 'vue';
import MessageThread from '../components/message/thread.vue';

export default Vue.extend({
  data: function() { return CW.getData(); },
  el: function() { return "body"; },
  components: {
    'cw-message-thread': MessageThread,
  },
});

ここで、 CW.getData() しているのが、独自のデータ渡し機構です。詳細は省きますが、以下のJSをERBに書くと、フロントエンド環境で定義されたモデルクラスに値を渡してインスタンスを生成するというような機構になっていました。

  <script>
    CW.assign('some_model', <%= raw @some_model.to_json()) %>);
  </script>

Rails側のテンプレートは以下のようになります。RailsのERBとVue.jsのテンプレートが混ざっており、読むのにかなり脳内メモリが必要になっています。

<input-text inline-template :ay-form-field="hoge.fugas[<%= field.index - 1 %>].piyo">
  <%= text_area_tag "some_textT_area",
    required: 'required',
    class: 'hoge',
    'v-model' => 'text',
    'rows' => '{{rows}}'
    ':rows' => 'rows'
  %>
  <cw-keywords-counter :keywords="hoge.keywords" :text="text"></cw-keywords-counter>
</input-text>

これらの機構はWebComponentsのようなものを志向して導入されたようなのですが、上記のコードを見てもわかるように以下のような問題があったと言えるでしょう。

  • Railsとフロントエンド環境との境目が曖昧でデータのフローがわかりにくい
  • ES2015、Vue.jsと合わせて、これらの独自機構を学ぶ必要があり不要に学習コストが高い

これによりVue.jsに苦手意識を持ってしまったエンジニアも多いです。在籍時はVue.jsは苦手と言っていたエンジニアが退職後にVue.jsいいじゃん!となっている事例を何度も見ました(ちなみに私もその一人です……)。またbody配下を全てVueのテンプレートとして扱ったせいか、あるChromeのバージョンから一時的に画面が真っ白になってしまうという現象が発生しており、chromeのみ画面ロード時に微妙にスクロールさせるという謎のハックが導入されたりしていました。また触る人間も少ないためライブラリのバージョンも古いままとなっており、Vue.jsは1.0のまま塩漬けにされているという状態でした。

エンジニアとしてフォローしておくと、2015年はまだWebpackerも存在しておらず、Vuex、vue-routerといった今ではデファクトとなったツール・ライブラリの登場も未だだったと記憶しています。そんな中、手探りでRailsとモダンフロントエンドの良い関係、Vue.jsでのアプリケーション開発にチャレンジしたことは称賛されるべきだろうと思います。

エンジニアの退職、そして無人化へ

2017年の冬から2018年の夏にかけてfrontend/ 環境を知るエンジニアが退職してしまい、とうとう frontend/ をメンテできるエンジニアはいなくなってしまいました。

私は2018年の春ごろから副業という形でクラウドワークスのMihalyという社内CSS / JSフレームワーク開発環境の整備・サービスへ適用するための技術的な支援を行っていましたが、作成したフレームワークを実サービスへnpmのパッケージとして配布するためには、既存の無人化した frontend/ をどうにかすることは避けられません。そこで社員時代には避けてきた frontend/ ディレクトリを何とかするという仕事を手が空いた時間を見つけては進めることにしたのでした。

f:id:suzan2go:20190527122344p:plain

フロントエンド環境をもう一度メンテ可能な状態にもっていくために

前述したとおり、誰も触れない状態ではあるものの、非常に重要な画面のインタラクションを担っているJSなので、そのまま捨てる事はできません。またメンテ可能な状態にするには少なくとも以下のようなことは実施する必要がありそうです。

  • gulp + browserifyをやめてWebpackに移行する
  • Vue1.0 => 2.0にアップグレードする
  • オレオレデータフローをやめる
  • オレオレルーティングをやめる
  • Linter / テストを導入する

これらを一気に変更するのはリスクも高く、QAコストも高くつきます。またfrontendディレクトリ配下で徐々に新しいライブラリに移行していくことも以下のように難しい状況に見えました。

  • Vue.js 1.0と2.0を混在させた状態でビルド環境を作ることは vue-template-compilerwebpack 等の依存関係により、不可能に見えた*1
  • 後述する通りVue.js 2.0非互換なAPIを使っている箇所が大量にあった

そこで frontend/ ディレクトリの中身を一気に改善するのではなく、別のビルド環境を作って少しずつ移行していく方針をとりました。

frontendディレクトリからの 漸進的 な脱却

まずは全画面で読まれていたfrontendディレクトリのJSを、必要な画面だけで読み込む形にし、独自のルーティング機構が不要な状態に変更しました。

f:id:suzan2go:20190527124548p:plain

またアプリルートから package.jsongulpfileといったものを frontend ディレクトリに移して frontend ディレクトリでビルドが完結するようにし、完全別のパッケージの管理とビルドを導入できる状態にもっていきました。これにより、画面ごとに別のビルド環境で生成されたJSを読み込ませる事が可能になり、安全にフロントエンド環境の移行が行える準備が整いました。

Webpackerの導入

そしてfrontend/ディレクトリとは別のビルド環境として選んだのはWebpackerです。

これを読んでいる人の中にはWebpackerではなく素のWebpackを使ったほうが良いのでは???と思ってる人も多いと思います。自分自身、過去に「webpackで作るSprockets無しのフロントエンド開発 - クラウドワークス エンジニアブログ」というブログを書いたり、プロダクションでSprocketsではなくWebpackを使ってフロントエンドビルド環境を作ったこともあるので、自分がフルタイムで関わるプロジェクトなら素のWebpackを選んだでしょう。しかしながらクラウドワークスにはフロントエンド専任のエンジニアがおらず、自分がいきなり1からビルド環境を作ってもまた無人化してしまうのではという懸念があったので、今回はWebpackerを選んだのでした。

Webpacker2系までは大量のconfigファイルがアプリ上に生成される形式だったため、かなりアップグレードが難しい状況であったのは間違いないですが*2、3系からはconfigファイルがアプリには殆ど入らない形式になっているため、Webpackのコンフィグをゴリゴリいじる必要がなく、Railsエンジニアが片手間に触るという環境なら良い妥協点なのではと考えています*3。Webpacker脱却記事も多いので、フロントエンド人材が育ったら引き剥がすことがそこまで難しくなさそうなのもポイントです。

Webpackerをフロントエンド環境の補助輪と考えて、将来存在が足かせになったタイミングでWebpackerのレイヤは剥がしてもらえばよいと考えています。

この変更により、ローカル開発環境では Sprocketsgulp(frontendディレクトリ)Webpacker と3つのフロントエンドビルド環境が一時的に生まれてしまったわけですが、幸いクラウドワークスの開発環境は docker-compose up で全てが立ち上がるように標準化されていたこともあり、他のエンジニアの開発環境に大きな迷惑はかけずに docker-compsoe.yml の更新をするだけで済んだのでした。

Webpackerの導入にあたっては、本番環境のNodeのバージョンアップ、デプロイツールと関連するGemのアップデートなどフロントエンドに完結しないタスクも多くありましたが、話の本筋からは離れるので割愛します。

導入後はVue.jsを使っている画面ごとに以下のような変更を行っていき、少しずつ移行を実施していきました。

- <%= javascript_include_tag bundle.js # 旧フロントエンド環境のJS %>
+ <%= javascript_pack_tag 'hogefuga.js' # Webpackerによりビルドした新JS %>

Vue.js 1.0から2.0へのアップグレード

Vue.jsの公式ドキュメントには以下のように、Vue.js 1.x 系からの移行に関するドキュメントがあるのでこちらを参考にしつつ、公式が vue-migration-helper という2.0非互換なAPIを使っている箇所を検出してくれるCLIを提供してくれていたので、それを元に少しずつ移行作業を進めていきました。 jp.vuejs.org

github.com

実際に vue-migration-helper をかけてみると大体150件ほど警告が出る状態でしたが、殆どが name="{{ name }}" to v-bind:name="name" のような警告で機械的に修正が可能なものでした。 以下のように1画面ずつプルリクを出してリリースしていき、何か問題があっても複数の画面に影響がでないよう進めていったので、大きな問題を出さずに移行を進めていくことが出来ました。

f:id:suzan2go:20190527133239p:plain

Railsから素直にデータをフロントエンド環境にわたす

前述したとおり、従来のフロントエンド環境はRailsからデータを渡すのに不必要に複雑な機構をもってしまっていました。しかしながらJSで扱うデータは全てAPIを作って対応するというのも、初期データしか必要のない場合には少し実装のオーバーヘッドが大きいように感じます。

そこで Webpackerにもドキュメントとして公開されており(webpacker/props.md at master · rails/webpacker · GitHub)、 react-rails などのライブラリが内部でやっているように、テンプレートにJSONを埋め込んでJSから読み出すという方法を取りました。

<%= content_tag :div,
  id: "hello-vue",
  data: {
    message: "Hello!",
    name: "David"
  }.to_json do %>
<% end %>
document.addEventListener('DOMContentLoaded', () => {
  // Get the properties BEFORE the app is instantiated
  const node = document.getElementById('hello-vue')
  const props = JSON.parse(node.getAttribute('data'))

  // Render component with props
  new Vue({
    render: h => h(App, { props })
  }).$mount('#hello-vue');
})

これがベストなやり方かと言われると微妙かもしれませんが、globalにデータをセットしてJSから読み出すよりも、「データをどこでセットしているか」、「データをどこから読み出しているか」が明確になったので以前よりはデータフローが追いやすくなったと思います。

Vue2.0へのアップグレードと合わせて、このデータフローについても上記のような変更を行いました。

Linter / Formatter 及びテストの導入

当たり前の話ではありますが、ライブラリを現時点で最新までもっていっても、追随できなければまたメンテナンスは難しくなってしまいます。またライブラリを上げたときに全画面で手動で確認しなければいけないとなると、アップグレードしていくのはかなり辛い作業になってしまうでしょう。というわけで普通の話ではありますが、アップグレードと合わせてLintとテストをするようにしました。

特に特殊なことはやっておらず、以下のライブラリを使用しています。

  • Jest + vue-test-utils
  • ESLint / Prettier

既存のコンポーネントは設計もあまりよくなく、テストが書きにくいものが多かったのですが、スナップショットテストがあるだけでも、ライブラリのアップデート・変更の心理的ハードルは下がるなーと実感しています。

そして現状

無事、frontendディレクトリは役目を終え、全てをWebpackerに載せることが完了しました。

f:id:suzan2go:20190527141125p:plain

また自分がプッシュしたわけでもなく、Vue.jsを使った施策を出すチームも現れてきています。もともとやっていた mihaly という社内CSSフレームワークについても、インタラクションは自分が素のJS(TypeScript)で書いていたのですが、Vue.jsからも使いたいという声が出てきており、 vue-mihaly 爆誕の機運が高まっています。

まだまだフロントエンドはCoffeeScriptが支配的ではありますが、継続的にフロントエンドを改善していく下地は出来てきたのかなと感じています。今後はCoffeeScriptの脱却、mihalyの導入などが進むといいな〜

おわりに

クラウドワークスではフロントエンドエンジニアを募集していますし、私への仕事の依頼もお待ちしております!(DMください)

twitter.com

www.wantedly.com

*1:SFCを使っていなければできるのかもしれない 心理的負担を抑えつつVue.jsを0.12→2.4にアップグレードした話 - Misoca開発者ブログ

*2:実際2系からアップグレードできず、素のWebpackを使う変更をしたこともあります

*3:実際3=>4のアップグレードはかなり楽に出来ました

RubyKaigi 2019にブースを出展してきました

SRE兼エンジニア採用をやっているsawadashotaです。 クラウドワークスは、4/18(木)から4/20(土)に福岡で開催されるRubyKaigi 2019にプラチナスポンサーとして協賛させていただき、ブースを出展してきました。

engineer.crowdworks.jp

昨年はロゴスポンサーとして協賛させていただいており、ブースを出展するのは今年が初でした。

f:id:shotasawada:20190510174734j:plain

初のブース出展ということで、当日の運営してみて、よかったところや苦労したところ、課題感を振り返ってみます。

良かったところ

ノベルティが好評だった

f:id:shotasawada:20190510174936j:plain

ノベルティはPCスタンド、クリーナー、クッキーを用意しました。

中でもPCスタンドはものすごい人気で、100個ほど用意してましたが、初日と二日目で50個ずつを両日とも午前中に配り終えてしまうほどでした。

一方でクリーナーとクッキーは余るくらいだったので、限定生産のPCスタンドとのバランスはよかったかなと思っています。

ノベルティの選定・発注については別途、担当したエンジニアが記事を書いてくれるはずなので乞うご期待!

参加者で協力してブースを運営できた

RubyKaigiに参加したのはエンジニアだけでしたが、交代しながらブースを運営できました。

ただし、見たいセッションが皆、同じだったりすることもあるので、事前にシフトを組んだり、可能であれば採用チームの方にお願いするのもいいかもしれません。

手作りハンコが意外と悪くなかった

RubyKaigiではブースへの集客を目的としてスタンプラリーを実施しています。

サインやシールでも大丈夫とのことなんですが、せっかくなのでスタンプを作ることにしました。

スタンプは業者に発注したものの、RubyKaigi直前に発注したため、初日には間に合わず、手作りハンコを作りました。

f:id:shotasawada:20190510184837j:plain

本当は「CrowdWorks」と彫りたかったんですが、難易度が高く「CW」に妥協しました。

しかし、いざ使ってみると、ユニーク性が高くいい感じに目立ってて、ブースに来てくださった方との話の種になったので、結果オーライでした。

f:id:shotasawada:20190510185714p:plain

課題に感じた点

場の設計がうまくできていなかった

なんとなくブースを構えて、無条件にノベルティを配る形になってしまい、来てくださった方とあまりお話できませんでした。

来年はブースでどんなコンテンツを提供して、来てくださった方とどんな会話をしたいのか、きちんと設計していきたいと思います。

たとえば、技術スタックなどを展示して、エンジニア同士の会話を発生させたり、サービスを知ってもらうために簡単な体験ができるようにしたりするなど、他社さんのブースを参考にしながら改善していきたいです。

国際化対応

RubyKaigiは国際的なカンファレンスなので、海外からも参加者がたくさん来ています。

しかし、ブースには日本語のコンテンツしかなかったり、全く異なる背景を持つ方に英語でサービスの説明をするのがうまくできてなかったので、配慮していきたいです。

ポスタースタンド

ポスタースタンドを用意してないものの、垂れ幕だけあったので、人通りの多い時間帯は人力で垂れ幕を立てていました。

f:id:shotasawada:20190510194409j:plain

他のブースを見ていても、ポスタースタンドがあると、一気にブースっぽくなったり、後ろにある荷物を隠せたりするので、必携です。

まとめ

はじめてのブース出展で至らぬこともありましたが、意外となんとかなりました。

また、最終日はブースを回る参加者も少なくなってきて、他のブース運営の方を情報交換できたりと、参加者だけでなくスポンサー間の交流もあって充実した3日間でした。

来年もブース出展したいと思っていますので、お楽しみに!

We Are Hiring!

クラウドワークスでは、一緒にRubyコミュニティを盛り上げていくエンジニアを募集しております!

www.wantedly.com

© 2016 CrowdWorks, Inc., All rights reserved.