📱MisocaアプリのiPhone X対応

mzpです。こんにちは。

MisocaのiPhoneアプリiPhone Xに対応しました。

🎨 デザイン変更

単純に最新のXcodeでビルドしなおしてみたが、iPhone Xのホームインジケータとボタンが被ってしまった。

f:id:mzp:20171116135049p:plain:w200

こういった画面はデザインを変更し、ボタンの下に余白を設けた。

f:id:mzp:20171116135803p:plain:w250

同様の位置にボタンがある画面がいくつかあったので、それらの画面も修正した。

📝更新通知画面

今回のバージョンから以下のような更新内容を通知する画面を導入した。

f:id:mzp:20171116140943p:plain:w250

実機で動かしてみたら、想像以上に格好よくて楽しかった。この写真のときは不具合修正のところに虫のピクトグラムを表示していたが、虫が気持ち悪いと言われたのでリリース版では魔法の杖に変えた。

f:id:mzp:20171115110809j:plain:w400

この画面を表示するかどうかの判断は以下のようにしている。前回起動したときとバージョンが異なっている場合は、この画面を出すようにした。

enum UpdateType {
    case initial
    case update
    case normal
}

class UpdateInfo {
    private let key = "latest_version"

    func requestUpdateType(f: (UpdateType) -> Void) {
        if !Auth.isLoggedIn() {
            // 未ログイン状態なら初期画面を表示する
            UserDefaults.standard.set(currentVersion, forKey: key)
            f(.initial)
            return
        }

        if currentVersion == previosVersion {
            // 前回起動したときと同じバージョンなら通常処理をする
            f(.normal)
        } else {
            // 前回起動したときと違うバージョンなら更新通知画面を出す
            UserDefaults.standard.set(currentVersion, forKey: key)
            f(.update)
        }
    }

    private lazy var currentVersion: String? = {
        guard let shortVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else {
            return nil
        }
        guard let version = Bundle.main.infoDictionary?["CFBundleVersion"] as? String else {
            return nil
        }
        return "\(shortVersion)-\(version)"
    }()

    private lazy var previosVersion: String = {
        return UserDefaults.standard.string(forKey: key) ?? ""
    }()
}

👈文書ボタンの長押し

文書作成ボタンやタブバーを長押しすると、作成する文書の種類を選べるようにした。

f:id:mzp:20171116151905j:plain:w250

これは UILongPressGestureRecognizer で実現している。

let view = navigationItem.rightBarButtonItem?.value(forKey: "view") as? UIView
view?.addGestureRecognizer(
  UILongPressGestureRecognizer(target: self, 
                               action: #selector(DocumentsViewController.addLongpressAction(_:))))

📢採用

MisocaではiPhoneアプリ開発に携わりたいエンジニアを募集中です。

特定のブランチをもとに本番同様の動作確認ができる「レビュー環境」の話

こんにちは。tkykです。

みなさん、コードレビューしていますか?今日はMisocaのレビュープロセスで用いられている、とっても便利な「レビュー環境」について紹介します。

Misocaのレビュー体制とその課題

MisocaではPull Request(以下、PR)ベースの開発体制をとっており、必ず他のエンジニアによるPRのレビューを経てから、masterへマージすることになっています。

レビュー時に動作確認をするには、エンジニア各自がローカル環境にブランチをチェックアウトして行うのですが、時にはそれだけでは不都合なケースもあります。

  • 非エンジニアにも動作確認をしてほしい
  • 動作確認をするための条件を整えたい
    • 最終的にはマージされないコードを一時的に追加したい
    • 依存するライブラリのバージョンを変更したい
    • RAILS_ENV=production でビルド・実行したい
    • などなど

このようなケースに対応するために、「レビュー環境」という仕組みを作りました*1

レビュー環境とは

レビュー環境とは、特定のブランチに基づいて構築された、独立したアプリケーションの実行環境です。専用のURLが割り当てられ、本番環境と同様に動作しますが、サーバリソースは独自に確保されており、自由な操作によるテストが可能です。

レビュー環境の使い方はとても簡単です。例えば今、 awesome-feature ブランチで新機能の開発を行っているとしましょう。このブランチを元にレビュー環境を構築するには、 review/[任意の名前] という名前のブランチをリポジトリにpushします。

git push origin awesome-feature:review/awesome

構築は自動で行われ、完了するとslackにそのレビュー環境専用に割り当てられたURLが通知されます。このURLにはGoogle認証によるアクセス制限がかかっていますが、Misocaの開発メンバーなら誰もが自由にアクセスすることができます。

(当初はPRが作られるたびに自動で環境を構築する方式も検討しましたが、すべてのPRが専用の環境を必要とするわけではなく、リソースの無駄が大きくなるので、ブランチ名で明示する方式になりました)。

仕組み

レビュー環境の正体はDockerコンテナです。コンテナの管理はAWS ECS(Elastic Container Service)で行っています。

全体の構造は次の通りです。GitHub Webhookを起点にAWS CodeBuildでDockerイメージの構築を行い、AWS Lambdaでコンテナの起動やslackへの通知を行います。

(prprについては過去の記事をご覧ください)

f:id:tkykmw:20171107151244p:plain

今回はCodeBuildの設定と、ECSを管理するLambdaの処理内容について、少し詳しく説明します。

CodeBuildによるDockerイメージの構築

CodeBuildではDockerイメージの構築と、ECR(Amazon EC2 Container Registry)への登録を行います。一連の処理の流れは、公式ドキュメントのサンプルとほぼ同じです。

# appspec.yml

phases:
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - $(aws ecr get-login --region $AWS_DEFAULT_REGION)
  build:
    commands:
      - echo Building the Docker image...
      - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG -f config/docker/Dockerfile.review .
      - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
  post_build:
    commands:
      - echo Pushing the Docker image...
      - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
      - curl -X POST -d "{\"repo_name\":\"$IMAGE_REPO_NAME\",\"image_tag\":\"$IMAGE_TAG\"}" https://xxxxxxxx.execute-api.$AWS_DEFAULT_REGION.amazonaws.com/build-review-container

イメージのタグとして、レビュー環境の名前(ブランチ名の review/[この部分])を割り当てます。ファイル中では $IMAGE_TAG という変数の部分です。

ECRへのイメージ登録が完了したら、後続のLambdaを実行するために、API Gatewayに対するHTTP POSTリクエストを発行します。このとき、レビュー環境の名前をパラメータとして渡します。

LambdaによるECS管理

ECSでは、コンテナの定義情報(タスク定義)と実行情報(サービス)を分けて管理します。実際に起動されるコンテナは「タスク」として管理されます。

レビュー環境を構築するためのLambdaの処理は、次の通りです。

  1. タスク定義を作成・更新する
  2. サービスを作成・更新する
  3. (必要な場合)既存のタスクを終了する

順にコードを見ていきます。Lambdaのランタイムはnode.js 6.10を使用し、エラー処理は省いています。

タスク定義

タスク定義は単純なJavaScriptオブジェクトとして作成し、registerTaskDefinition で新規作成または更新します。

/*
 * タスク定義には、
 * - Dockerイメージのパス
 * - ポートマッピングの設定
 * - 環境変数の設定
 * - ログ出力の設定
 * - etc.
 * が含まれる
 */
const taskDefinition = (repository, tag, hostname, port) => {
  return {
    containerDefinitions: [ {
      family: taskName(tag),
      image: `000000000.dkr.ecr.ap-northeast-1.amazonaws.com/${repository}:${tag}`,
      portMappings: [ { hostPort: port, containerPort: 3000, protocol: 'tcp' } ],
      entryPoint: [ 'bin/launch' ],
      name: tag,
      environment: [
        { 'name': 'RAILS_ENV', 'value': 'production' },
        { 'name': 'APPLICATION_HOST_WITH_PORT', 'value': hostname },
        { 'name': 'MYSQL_DATABASE_PRODUCTION', 'value': `misoca_review_${tag}` }
      ],
      logConfiguration: {
        logDriver: 'awslogs',
        options: { 'awslogs-group': 'ECS', 'awslogs-region': 'ap-northeast-1', 'awslogs-stream-prefix': 'misoca-review' }
      },
      memoryReservation: 256
    } ],
    networkMode: 'bridge',
    placementConstraints: [],
    volumes: [],
    essential: true,
    volumesFrom: []
  };
};

/*
 * タスク定義の更新 or 新規作成
 * `event` はCodeBuildからLambdaに渡されたイベントオブジェクト
 * ホスト名とポート番号はタグ名をもとに生成する前提
 */
ecs.registerTaskDefinition(
  taskDefinition(
    event.repo_name,
    event.image_tag,
    hostname(event.image_tag),
    port(event.image_tag)
  ), (err, data) => {
  updateOrCreateService(event.image_tag);
});

タスク定義の内容に変更がない場合、毎回更新する必要はないのですが、処理を単純化するために常に registerTaskDefinition を実行するようにしました。

サービス

タスク定義の作成(更新)に成功したら、その定義を参照するサービスを作成(更新)します。レビュー環境一つにつき、一つのタスク(=コンテナ)を起動したいので、desiredCountは1に設定します。

const updateOrCreateService = (tag) => {
  ecs.describeServices({ services: [serviceName(tag)], cluster: CLUSTER }, (err, data) => {
    // 削除されたサービスは 'INACTIVE' ステータスとして見える
    if(data.services.length > 0 && data.services[0].status !== 'INACTIVE') {
      ecs.updateService({
        cluster: CLUSTER,
        service: serviceName(tag),
        taskDefinition: taskName(tag),
        desiredCount: 1
      }, (err, data) => {
        stopRunningTask(tag);
      });
    } else {
      ecs.createService({
        cluster: CLUSTER,
        serviceName: serviceName(tag),
        taskDefinition: taskName(tag),
        desiredCount: 1
      });
    }
  });
};

サービスが作成されると、タスクの起動はECSが自動で行ってくれます。しかしすでにタスクが起動していた場合、そのままでは新たなイメージがロードされないので、stopTask で起動済みタスクを終了します。するとECSが新たなイメージとタスク定義に基づいて、タスクを再起動してくれます。

/*
 * 常に1件のみ、実行中であることを想定する
 */
const stopRunningTask = (tag) => {
  ecs.listTasks({
    maxResults: 1,
    cluster: CLUSTER,
    desiredStatus: 'RUNNING',
    serviceName: serviceName(tag)
  }, (err, data) => {
    ecs.stopTask({
      task: data.taskArns[0],
      cluster: CLUSTER,
      reason: 'Review deploy'
    });
  });
};

まとめ

いかがでしたでしょうか。

Dockerコンテナによる軽量な実装と、AWS APIを活用した構築の自動化によって、細かな目的ごとに、本番同様に動く環境を作れるようになりました。今ではレビュー目的に限らず、エンジニア各自が自由な実験を行うためにも用いられ、開発生産性の向上に寄与しています。

採用

Misocaでは日々の開発生産性の向上に取り組みたいエンジニアも募集中です!

*1:この名前と機能は、Heroku Review Appsにインスパイアされたものです

開発合宿で野尻湖に行きました

こんにちは。Misocaの「合宿神(がっしゅくしん)」、もといコロチャン(@corocn) です。

f:id:monoooki:20171025154753j:plain

まあ、いきなり「合宿神」って言われても、「合宿神って、なに?」ってなると思いますので「合宿神」について説明します。

f:id:monoooki:20171025154822j:plain

合宿の、神です。

参考: 東京都内のおすすめ銭湯&交互浴を銭湯神ヨッピーが伝授! | SPOT
(※銭湯神に許可を頂いております)

今回はそんな合宿神コロチャンが、 先日、長野のLAMPで2泊3日の開発合宿を行いましたので、その報告を書きたいと思います。

野尻湖の近くでしたが、天候が悪めで、寒かったです。
そういえばRubyKaigi2017も台風が来てましたね。私のせいでは、ありません。

f:id:monoooki:20171025154843j:plain

目標と成果

今回の合宿では、3つのグループに分かれて、それぞれのテーマに取組みました。 我々のグループですが、数年後の軽減税率制度の話題もあり、税計算を学びたいという機運が高まってきているため、

「「「源泉徴収税を計算できるライブラリを作って公開するぞ!!」」」

という目標で取り組み、無事公開しました。

f:id:corocn:20171017174103p:plain

1日目

名古屋から特急しなの&しなの鉄道に乗って移動しましたが、合計3時間以上かかりました。長野の北部なのでなかなか遠いですね。

f:id:monoooki:20171025154940j:plain

LAMPに到着。

f:id:monoooki:20171025155004j:plain

荷物だけ置いて、会議室に移動します。

f:id:corocn:20171017181520j:plain

会議室は木をベースにした作りで、合宿ッ!って感じですね。
ちなみに、宿泊所と会議室は車で数分ほど離れていますので、検討されている方はご注意を。

開発

さっそく、3班に分かれて開発。

f:id:monoooki:20171025155028j:plain

源泉徴収といっても色々な種類があり、全部は合宿期間中に収まらないため、「報酬・料金」の計算ロジックを組むことに焦点をあてました(これは事前に決めてました)。

まずは国税庁のホームページを見て、皆でロジックを確認していきます。 資料がわかりにくかったので友人の税理士に聞いてみたところ、「これを見るといいよ!」と言われ、次のようなURLをもらいました。ありがとうございます。

「この金額の時はいくらになる」というような例が載っていたので、それをテストケースとしてTDD的に実装を進めることにしました。

開発環境

税の気持ちが分かってきたので、アーキテクチャを決めました。 Misocaでは普段使ってないTypeScriptで書こうぜ!! という提案があり、いいねいいね! となったので、採用されました。

TypeScriptはwebpackやbabelを入れる必要がないので、スッキリしています。 テストはavaとCircleCIで回しました。手軽で良いですね。

夕食

雨に打たれながら、炭をバーベキューを食べました。

f:id:monoooki:20171025155157j:plain

焼きすぎてしまいましたね。

食後

寝室がドミトリー形式なので作業できず、宿泊所のロビーで静かにコーディング。

一人1種類の計算ロジックを組んで、この日は終了です。

2日目

朝食

LAMPの朝食はモーニングビュッフェ形式になっており、パンを食べました。

米が食べたいとダダをこねましたが、有料みたいなのでやめました。

開発

国税庁の分類分けを参考に、イレギュラーケースを実装していきました。
Ⅳ 税額の求め方(平成29年分)(※PDF)

馬主やプロボクサーなど、職種によって計算方法が異なります。面白いですね。 どうしようもなくなって、banushiproBoxer というメソッドを実装することにしました。

昼食

大雨と寒すぎて散策する元気がなく、近所の喫茶店でそばを食べました。

f:id:monoooki:20171025155414j:plain

LAMPの方にストーブを出してもらいました。

f:id:monoooki:20171025155443j:plain 文明の利器に感動するワレワレ

開発

昼食後もワイワイ開発をしました。

f:id:monoooki:20171025155546j:plain

f:id:monoooki:20171025155559j:plain

夕方

夕方ごろになったらだいぶ晴れてきました。

f:id:renya-mizuno:20171024151906j:plain

f:id:renya-mizuno:20171024151949j:plain

それっぽい景色になって満足です。

f:id:renya-mizuno:20171024151924j:plain

夕食

LAMPの中で宴会。 「ナラタケ」というとても貴重なキノコをだしてもらいました。とても貴重な味がしました。
それはそれとしてポテトが美味しかったです。

食後

夜は昼と打って変わってガンガン晴れてました。 星が綺麗でした。

f:id:corocn:20170928215844j:plain

f:id:renya-mizuno:20171024152340j:plain

3日目

朝食は2日目と同じだったので割愛です。

晴れた

祈ったらめっちゃ晴天になりました。 僕達が求めていた野尻湖はこれだ!

f:id:corocn:20171017181716j:plain

開発

npm publish して、デモページを作ったり、発表資料を整えたりしました。

昼食

LAMPではランチにハンバーガーを提供しています。

lamp-guesthouse.com

6種類あって、僕は "始まりのLAMPバーガー" を選びました。
めっちゃ天気が良いお外でLAMPバーガーを食べました。美味しかった!

f:id:monoooki:20171025155654j:plain

発表会

名古屋オフィスと繋いで、発表会を行いました!!

f:id:monoooki:20171025155748j:plain

f:id:monoooki:20171025155756j:plain

まとめ

  • 思いのほか寒く、9月の長野は防寒具が必須ですね。
  • 馬主やプロボクサーの方に税金計算についてお伺いしたいです。
  • あなたのプルリク待ってます!

採用

Misocaでは二代目合宿の神を募集しています!!

Misocaではワイワイコードを書くのが大好きなエンジニアを絶賛募集中です!!

🍣キーキャップ

こんにちはmzpです。

先日開催されたRubyKaigi 2017に参加し、ノベルティとしてMisocaロゴ、Rubyロゴ、寿司の3種類のキーキャップを配りました。

今回は、このキーキャップを作ったときの話を紹介したいと思います。特に寿司の話をします。

💸要約: 注文した会社とかかった費用

キーキャップの作成はWASD KeybordsのCustom Art Cherry MX Keycapsで作りました。

計450個を発注し、約560ドルかかりました。数があったおかげか、サイトに掲載されている金額よりも安くなりました。

👣作った経緯

デザイン決定

ノベルティとしてキーキャップを作ることにしたので、デザイン投票を行ないました。その結果、候補になかった寿司に投票が入りました。

f:id:mzp:20171018170001p:plain

「なぜ寿司??」という疑問が社内の各所からでてきて、混乱が生じました。

f:id:mzp:20171018170329p:plain

f:id:mzp:20171018170953p:plain

WASD Keyboardsへの発注

WASD Keybordsの大口注文要のフォームから見積りを行ない、予算を確定させた後、発注しました。この際、予算の都合で寿司デザインがなしになりそうになりましたが、協議の末、注文することにしました。

f:id:mzp:20171018170352p:plain

寿司画像はいらすと屋の中トロマグロを合成して作りました。せっかくなので貼っておきます。

f:id:mzp:20171018170715p:plain:medium

RubyKaigiまであまり時間がなかったので間に合うか不安でしたが、かなりすばやく製造・配送が完了しました。 詳細なタイムテーブルは以下の通りです。

  • 9/6 注文
  • 9/8 製造完了・発送
  • 9/11 日本到着
  • 9/12 UPSに依頼して、会場に直接搬入してもらうよう依頼する
  • 9/17 RubyKaigi会場へ搬入

RubyKaigi当日

RubyKaigi当日にブースで配布しました。 かなり盛況で、2日目で在庫が尽きそうになりヒヤヒヤしました。

f:id:mzp:20171019113404j:plain

f:id:mzp:20171019113407j:plain

⭐️ふりかえり

よかった

  • 在庫がつきるほどの人気があってよかった。
  • 使い方の説明のためにキーボードを持っていったのはよかった。

よくなかった

  • チェリー軸のキーボードにしか刺さらないので、HHKB用のアダプタがあったほうがよかったかもしれない。
  • 黒(キーキャップの色)で赤(寿司やRubyの色)は発色があまりよくなかった。

📢採用

Misocaではノベルティ作りに興味のあるエンジニアの方の応募もお待ちしております。

RubyKaigi 2017 にみんなで参加して、Misoca ブースを初出展しました

こんにちは、松江オフィス勤務の @hidakatsuya です。急に寒くなってきて通勤が辛い季節になってきましたが、Misoca はリモートワークができるので体調や気分や気候に合わせて自由に選択できるので助かります。

さて、少し間が空いてしまいましたが、今回は 9/18-20 に広島で開催された RubyKaigi 2017 のお話です。

Gold Sponsor

Misoca は RubyKaigi 2017 に Gold Sponsor として協賛しました。今回は代表の豊吉をはじめ、総勢14名で参加してきました。

Image uploaded from iOS.jpg (5.7 MB)

なお、昨年の RubyKaigi 2016 は Drinkup Sponsor として協賛させてもらいました。

tech.misoca.jp

勉強会参加補助を使ってみんなで参加

Misoca には勉強会への参加補助制度があります。RubyKaigi においては業務扱いで参加できますし、参加費や交通費は全て会社負担です。そのおかげで積極的に RubyKaigi に参加できますし、その開催地(今回は広島)を楽しむこともできて最高ですね。来年は仙台だそうですよ。楽しみですね。

お好み焼きです。 Image uploaded from iOS (3).jpg (7.9 MB)

ブースの様子

さて、今回初めて RubyKaigi で Misoca のブースを出展しました。

f:id:hidakatsuya:20170918112522j:plain

キーキャップのノベルティが大好評ということもあり、本当にたくさんの方にお越しいただきました。お越しいただいた皆さん、ありがとうございました!

f:id:hidakatsuya:20170918112611j:plain (一番人気はやはり「Ruby」。キーキャップの他にキーシールも配布)

2日目からは、Misoca のリモートワーク風景を見ていただこうと、zoom の端末をブースに置いて実際の作業風景を紹介しました。こちらもたくさんの方に足を止めていただき、Misoca の文化とも言えるリモートワークを皆さんに知っていただけたと思います。

f:id:hidakatsuya:20171018023111j:plain

Misoca のリモートワークについては過去のエントリーもご覧ください。

ふりかえり

Misoca では KPT を使って頻繁にふりかえりを行っています。今回の RubyKaigi への参加も例に漏れず、終了後参加者で集まってワイワイとふりかえりました。その中で挙がった意見を少し紹介してみます。

Keep(良かったこと)

  • 寿司キーキャップ、好評で良かった
  • はじめてのRubyKaigiで楽しかった(by 非エンジニア)
  • 知り合い増えた
  • ブースでのオフィスの中継に足を止めてくれた人がいて嬉しかった

Problem(悪かったこと)

  • キーキャップ足りなかった
  • 英語がんばりたい
  • Misocaの紹介ぐらいは英語でできるようにしたい
  • もう少し早めにブースの準備(企画とかノベルティとか)すれば良かったね

Try(やりたいこと)

  • 参加だけじゃなく LT でもいいから発表したいね
  • ブログ書こう

ということで、来年も今年以上に RubyKaigi を盛り上げていきたいと思っています。その際は、今回のふりかえりを踏まえて、もう少し早めにみんなで楽しく考えていきたいと思います。

最後に

開発メンバーは毎日楽しく Ruby を書いています。そして、その Ruby の国内最大のイベントである RubyKaigi は Misoca とメンバーにとって大切で楽しみなイベントの一つです。Rubyコミュニティへの感謝と僅かながらの貢献として、今後もなんらかの形で RubyKaigi を盛り上げていければと思っています。

心理的負担を抑えつつVue.jsを0.12→2.4にアップグレードした話

こんにちは、@mugi_uno です。

RubyKaigi盛り上がりましたね〜!

そして広島は美味しいものがたくさんでした。次回の仙台も楽しみですね!


さて、みなさんはフロントエンドのフレームワークには何を利用していますか?

Misocaでは一部機能でReact/ReduxによるSPA構成を採用しています。 めろたん(@renyamizuno_)の書いた過去のエントリーにも登場していますね。

tech.misoca.jp

しかし、Misocaで利用しているのはReactだけではありません。

jQuery

React/ReduxはSPAなどでパワーを発揮しますが、逆に気軽に取り回しにくいため、ちょっとしたコンポーネントを作りたいケースなどでは、さくっと書けるjQueryを利用することもまだまだ多いです。

しかし、DOM操作が柔軟すぎるため影響範囲が読めなかったり、ビューと状態の管理が複雑になったりという問題を抱えやすいです。

そこで…

Vue.js

f:id:mugi1:20170926111531p:plain

実は、すでに部分的にVue.jsが採用されていました。 Vue.jsには以下のような特徴があります。

  • Rails側のViewをベースにディレクティブ/コンポーネントを埋め込める。
  • 単一ファイルコンポーネント内にtemplate/script/styleを閉じ込めることができる。
  • 日本語のドキュメントが充実している。
  • Reactと比較すると、フロントエンドが苦手な人でもわりと理解しやすい(と思う)

また、個人的な印象ですが、同一画面に小さいコンポーネントが複数点在する場合などにおいては、Reactよりも書きやすいように感じます。

ということで、すでに使えることですし、どんどん使っていきましょう!!

…と言いたいところですが

f:id:mugi1:20170926114710p:plain

衝撃のバージョン0.12.16。リリースされたのはなんと、2015年9月です。

単一ファイルコンポーネントをはじめとする、様々な魅力的な機能が使えないことがわかりました。

古いものを利用し続けるとエンジニアのモチベーションが下がりますし、情報を追うのも時間とともに困難になっていきます。

これは良くない!

アップグレードだ!!

というわけで、今回はMisocaで利用しているVue.jsを 0.12→2.4.2にアップグレードした際のお話です。

アップグレードの流れ

心理的負担を抑えるための下準備をする

0.12から2.4.2という大ジャンプのため、 尋常ではない数のBreaking Changesが存在します。

マイグレーションガイドも古すぎて存在せず、地道に差分を見ていかねばなりません。

リスクを抑えるためには少しずつ置き換えをしていきたいところですが、 すでにVue.jsはnpmで管理しているため、単純にアップグレードすると、すべてのVue.js利用箇所が一気に2.4に変更されます。

影響範囲が広すぎて心理的な負担も大きく、レビューする側も大変です。PRのマージボタンを命懸けでクリックすることになりますね。

そこでまずは、小さい範囲で少しずつアップグレード可能となるような環境を整えました。

1. ローカルパッケージをつくる

まず、サブディレクトリを作成し、0.12のVue.jsのみをdependenciesに保持するpackage.jsonと、エントリーファイルを作成します。

node_legacy_dependencies/vue-legacy/package.json
node_legacy_dependencies/vue-legacy/index.js

package.jsonの内容(dependencies以外は重要ではないのでデフォルトのままです)

{
  "name": "vue-legacy",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "vue": "0.12.16"
  }
}

index.js

module.exports = require('vue');

※以下を参考にさせていただきました。
http://efcl.info/2016/05/02/npm-package-mixed-multiple-versions-demo/

2. プロダクトで利用するpackage.jsonの依存を更新

Vueのバージョンを2.4.2へ更新し、さらに上記で作成したローカルパッケージを、vue-legacyでimportできるように依存に追加します。

(package.jsonから抜粋)

"vue": "^2.4.2",
"vue-legacy": "file:node_legacy_dependencies/vue-legacy",

3. 既存のすべてのVue利用箇所のrequire/importの向き先をvue-legacyに変更する。

以下のように変更します。

import Vue from 'vue-legacy';

→結果どうなるか?

import時の指定により、バージョンを任意に切り替えることができるようになりました。

import LegacyVue from 'vue-legacy'; // 0.12.16
import Vue from 'vue'; // 2.4.2

これで、アップグレードを行いたい利用箇所のみ、vue-legacyvueと書き換えることで、影響を最小限に抑えながら変更していくことができます。

置き換え対象がどれだけ残っているかも、vue-legacygrepするだけで確認できるので便利ですね。

アップグレード作業

これで小さくアップグレードを進めるための準備が整いました。実際に更新をしていきましょう!

CoffeeScript→ES6への変換

変更前のVue.jsを利用しているコードは、ほぼ全てがCoffeeScriptで記述されていました。

Misocaでは新規にjsコードを作成する際は基本的にはES6を利用しており、アップグレードに伴い大幅な変更も予想されますので、このタイミングでCoffeeScriptからES6への変換も行うことにしました。

手で書き換えるのはツラいものがあるので、decaffeinateを利用しましたが、特に問題もなく変換することができ、とても助けられました。

github.com

プロダクトによってはコーディング規約に揃えるのが大変だったりするかもしれませんが、ひとまずES6にしたい!というケースでは、一度decaffeinateで変換してから細かい修正をしていくと、安全に進められるかもしれません。

変更点を確認しながらがんばって2.4に置き換える

あとは地道にVue2.4で動くように書き換えていきます!

  • 短縮記法の利用 (v-bind:hoge='abc':hoge='abc')
  • 単一ファイルコンポーネントへの書き換え
  • フック関数変更に伴うカスタムディレクティブの書き換え
  • vm.$setをはじめとする、非推奨となったAPIを全て書き換え
  • クラス/スタイルのバインディングの書き換え
  • などなど…

多すぎて載せきれませんが、最終的にはかなりの量の書き換えを行いました。

作業中は、Vue1→Vue2のマイグレーションガイドを常に見ていたような気がします。

また、移行ヘルパーも適宜実行して機械的なチェックも行いました。

ファイル単位でPR&レビューを繰り返す

下準備のおかげで、影響範囲を抑えつつアップグレードしていくことが可能になっているので、上記変更をファイル単位で細かく繰り返していきます。

(大量のPR)

f:id:mugi1:20170926140546p:plain

作業中に軽微なバグを見つけることもありましたが、影響範囲が閉じられているので、Bugfixも小さいPRとして適用し、柔軟に対応していきました。

そして…

全ファイルから vue-legacy が消え去ったのを確認し、無事に移行完了となりました。

最後の作業として、下準備で作成したローカルパッケージを削除して終了です。

f:id:mugi1:20170925113819p:plain

f:id:mugi1:20170925114934p:plain

これで心置き無くVue.jsを使っていけるようになりました。

まとめ

こういった作業をする際は往々にして影響範囲が広く、リスクも高いことが多いです。今回は、小さく進める方法を最初に検討して整えることで、不安な気持ちを抑えながら作業していくことができました。

レビューする側の負荷軽減にも繋がるので、今後も心がけていきたいですね!


採用

Misocaでは最新フレームワークを使っていきたいエンジニアを募集しています!

アジャイル開発についての社内勉強会

Misoca開発者の黒曜です。

最近は技術書典3の原稿を書いていますが、進捗がダメすぎて間に合うかドキドキしています。

10/22(日)にアキバ・スクエアで出展しますので、ぜひ遊びに来てください。

新刊、間に合うといいな…

🎓 アジャイル開発勉強会

Misocaではスクラム開発を取り入れていますが、最近新メンバーも増えてきたため、知識の共有や基礎の見直しを目的にスクラム開発についての勉強会を行いました。

扱った内容は「スクラム開発とは何か」と「アジャイルな開発での見積りはどういったものか」の2本立てで、前者はスクラムガイド2016を、後者は アジャイルサムライアジャイルな見積りと計画づくりを参考に、内容を抜粋して紹介するためのスライドを作って話す形にしました。

アジャイルサムライ−達人開発者への道−

アジャイルサムライ−達人開発者への道−

アジャイルな見積りと計画づくり ~価値あるソフトウェアを育てる概念と技法~

アジャイルな見積りと計画づくり ~価値あるソフトウェアを育てる概念と技法~

📝 スライド資料

以下が勉強会に使用したスライドです。

これらのスライドはesa.ioのプレゼンテーションモードを利用しています。

大きい画像や長い文章だと少し崩れてしまいますが、Markdownでまとめた内容をそのままスライドにできるので手早く準備することができました。

💬 開催感想

勉強会ではスライドの内容を紹介した後に質問や議論の時間を設けたのですが、最近入ったメンバーだけではなく以前からいるメンバーも交え、普段の業務で気になっていることなどを積極的にディスカッションすることができました。

特にストーリーポイントやベロシティなど「なんとなくわかったつもりで使っているツールの意味や意義を理解できた」という感想が印象に残っています。

勉強会を実施して良かったとともに、プロセスの流れに乗ってもらうだけではなく「何故そうしているのか」の部分もしっかりと伝えていかないといけないな、という反省も得られ、よい経験になりました。

今後もアジャイルの話に限らず、社内勉強会を開いていければと思います。

📢 宣伝

💎 RubyKaigi

さて、来週はRubyKaigiですね。

MisocaはRubyKaigiスポンサーとしてブースを出展します。

面白いノベルティもご用意していますので、ぜひブースに遊びに来てください!

🙋 採用

Misocaではスクラム開発に興味のあるエンジニアを募集しています。