Pull Request発行時にそのコミットIDでデプロイされた環境を自動構築してレビュー時/マージ前に確認しやすくする仕組み

インフラをアレしてる佐野です。Pull Request(以下、p-r)が発行されると、そのp-rのコミットIDでデプロイされた環境を自動構築する仕組みを作ったので、今日はそれについて。マージ直前の環境が立ち上がるのでレビューアはレビュー時にコードを追うだけでなく、ブラウザ/アプリの接続先をこの環境に向きかえることで実際のアプリケーションの動作も確認できるようになります。レビューが非常に捗ります。

  1. 動作
  2. 仕組み
  3. GitHubのWebhookについて
  4. 自動構築の処理
  5. Dockerの活用
  6. tmpfsの活用
  7. まとめ

1. 動作

こんな感じです。ここで、この通知するボットおよびこの仕組みを以下、シャイニング・ウィザードと呼ぶことにします。シャイニング・ウィザードというのは好きなプロレスの技でして最初はこの基盤の仮名だったのですが、チーム内で定着してしまったのでもうこれでいいや的な…。ちなみに、Amazon prime ビデオに有田と週刊プロレスとという、くりぃむしちゅーの有田さんがプロレスについてひたすら語るだけの番組があります。これが最高に面白いので、プロレス好きな人にはおすすめです。Amazon primeに契約していれば無料で見れます。

さて、まずは動作の様子です。p-rが発行されるとシャイニング・ウィザードがそれを検知してサーバを作り、IP、APIサーバのURL、APPサーバのURL…を発行しています。

f:id:hiroakis:20170224204927p:plain

p-rがクローズ(マージもしくは単にクローズ)されるとそのサーバを殺します。

f:id:hiroakis:20170220170003p:plain

冒頭でも述べた通り、レビュー、特に画面系のレビューが捗ります。データベースも独立した環境として構築するのでこのサーバでデータ操作をしても本番はもちろんステージング環境にも影響はないです。

2. 仕組み

この記事のために図を書き直すのが面倒なので、社内への説明資料をそのまま転用します。なお、この記事はトレタのAPIサーバ/ウェブサーバを対象としており、そのサーバはEC2を土台にnginx, Rails, Sidekiq, Aurora(MySQL)、Redis、memcachedが存在しています。以下、説明するフローはこの構成前提ものとなります。

f:id:hiroakis:20170221162153p:plain

  1. 開発者(図でいうと「おまえら」)がp-r発行します。
  2. シャイニング・ウィザード(武藤敬司選手の画像)がWebhookを受ける。事前にGitHubのリポジトリの設定でwebhookをシャイニング・ウィザードの動作するサーバに飛ばすようにしておく。
  3. シャイニング・ウィザードがEC2を立ち上げる。この際、user-dataにて下記の(4)(5)の処理を行うようにしておく。
  4. user-dataスクリプトでステージングのデータベースからmysqldumpを取得する。
  5. user-dataスクリプトで各種ミドルウェアのdockerコンテナを立ち上げる(と同時に(4)でダンプしたデータも読み込む)。
  6. シャイニング・ウィザードがansibleを実行してサーバの状態を最新にする。さらにp-rに対応するコミットIDでデプロイを行う。
  7. シャイニング・ウィザードが api-[リポジトリ名]-[プルリク番号].xxxx.com , app-[リポジトリ名]-[プルリク番号].xxxx.com などとしてDNSにレコードを登録する。
  8. 構築したサーバのIPやURLをslackに通知する。

p-rがマージ/クローズされた際の挙動は、単に対象のEC2をterminateして、DNSから対象のレコードを削除する動きになります。

3. GitHubのWebhook

これはご存知の方も多いと思われますが、GitHubにはwebhookの仕組みがあり、issueの変更、リポジトリの変更…などの挙動をhttp/httpsでリモートに通知することができます。本記事の仕組みはこのwebhookを利用しています。GitHubのwebhookにおいて本記事で重要な事柄は次のものになります。

  • X-GitHub-Eventヘッダ
  • POSTされてくるHTTPボディの action 要素
  • 同じくHTTPボディの pull_request.head.sha 要素

p-rのwebhookが飛ばしてくれるデータの詳細についてはこちらのドキュメントを参照してください。

X-GitHub-Eventヘッダ

このHTTPヘッダには「何が行われたか」が入っています。issueの登録/更新/削除なのか、リポジトリに紐づくチームの登録/更新/削除なのか…。p-rの場合、 pull_request という文字列が入ってきます。

action要素

actionにはp-r時にどんな操作が行われたかが記載されています。この記事で重要となる操作は下記の通り。

  • opened: 新規p-rが発行されたとき。
  • reopened: p-rの再開。つまり一度閉じたp-rが再びオープンされたとき。
  • synchronize: p-rが発行されたあとにそのブランチにpushが行われたとき。
  • closed: p-rが閉じられたとき。なお、今回の処理では識別は不要ですが、pull_request.merged 要素のtrue/falseで、マージされて閉じられたのかマージされずに閉じられたかの判別も可能です。

pull_request.head.sha

そのp-rのコミットIDが入っています。つまりこのコミットIDでデプロイすることで、p-rの状態が実現できます。

4. 自動構築の処理

Jenkinsでやってもいいかもしれません(Jenkins鯖を設えて、Webhookの宛先をそこにする)が、今回はGoでWebhookを受けてEC2構築〜デプロイまでを行うサーバを書きました。 実際のコードからいくらか簡略化したコードを下述します。 handler(w http.ResponseWriter, req *http.Request) はgoなhttpサーバのhttpリクエストハンドラです。webhookにて通知されるp-rの action に応じて処理内容を分岐させます。opened および reopened ならEC2の新規作成とデプロイ。 synchronize なら既存のp-rへのpushなので、webhookにて通知されたコミットID、つまり pull_request.head.sha を使ってデプロイを行います。 closed ならいわずもがなEC2の削除やDNSの削除などの後始末を行います。

func handler(w http.ResponseWriter, req *http.Request) {

    // タイムアウト。処理内容に応じて適切に。
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
    defer cancel()

    // WebHookの通知によって、新規作成、更新、削除それぞれの処理を行う。
    // それぞれgoroutineを起動してバックグラウンドで処理を行う。
    errCh := make(chan error)
    switch action {
    case "opened", "reopened":
        go func() {
            // 作業ディレクトリ掘る、EC2作成、DNS登録、Ansible実行、デプロイ
            errCh <- create(ctx) 
        }()
    case "synchronize":
        go func() {
            // 作業ディレクトリに移動してデプロイを行う。
            errCh <- update(ctx) 
        }()
    case "closed":
        go func() {
            // EC2削除、DNS削除、作業ディレクトリ削除を行う。
            errCh <- terminate(ctx)
        }()
    }

    // 結果を待つgoroutine
    go func() {
        select {
        case err := <-errCh:
            if err != nil {
                slack.Post("エラった")
            }
            slack.Post("でけた")
        }
    }()

    // 処理の結果は待たずにGitHubには200 OKを返しておく
    w.WriteHeader(200)
 }

create()の処理は泥臭い感じになります。create()の中でさらにgoroutineを起動してEC2サーバの起動とDNS登録を行いつつ、別のgoroutineが リポジトリ名-番号 ( repository.full_name-pull_request.number ) みたいな作業ディレクトリを掘ってそこにansibleのリポジトリとrailsのリポジトリをcloneする。そして、EC2を起動するgoroutineとgit cloneするgoroutineが正常終了したら、起動したEC2のパブリックIPでansibleのインベントリファイルを生成する。同様にcapistranoのdeploy.rbを、EC2のIPを使って server '{{ .IP }}', user: 'deploy', roles: %w(web app db) を書き換え、 pull_request.head.sha を使って set :branch, '{{ .Sha }}' を書き換える。そしてcap deploy実行。 update()は単にcapistranoのdeploy.rbのを対象のコミットID( pull_request.head.sha )で書き換えたもので置換して、cap deploy実行。 terminate()はEC2のterminateとDNSの更新。

5. Dockerの活用

トレタではMySQL -> Aurora, Memcached -> Elasticache といったように、基本的にはAWSマネージドを使っていますが、EC2上に何かミドルウェアを構築したいときにはDockerも活用しています。かつてはミドルウェアはrpm/debを煮込んでおいてyum/aptする、またはconfigure && make && make installするような世の中でしたが、今はdockerイメージを使うという選択肢もあります。

Docker公式イメージ

ここで使うDockerイメージは公式イメージを使います。自分で1からDockerfileを書いてもいいかもしれませんが、この手のインスタント環境では基本的には公式イメージで十分です。MySQL, Redis, memcached, PostgreSQL…など著名なミドルウェアのものは大抵公式イメージが提供されていて、docker run時にMYSQL_ROOT_PASSWORDなどの環境変数をセットしたり、ディレクトリマウントを行うことで比較的簡単にコンフィグレーション可能です。例えば、MySQLの公式イメージを使うときのTipsについて紹介します。

  • 自分で作ったmy.cnfを使いたい

文字コードの指定、バッファプールなどの設定などなど、MySQLのコンフィグでケアしたいものをどうやって公式イメージに設定するのか?これはコンテナ側の /etc/mysql/conf.d にmy.cnfを置けば良いです。例えばホスト側に /mem/docker/mysql/conf/my.cnf としてmy.cnfを置いた場合、 -v /mem/docker/mysql/conf/:/etc/mysql/conf.d としてdocker runすることで起動時にmy.cnfを読み込んでくれます。

  • DBに初期データを登録したい

単純にdocker runしたあとに mysql -uroot dbname < mysqldump.sql としてもいいかもしれませんが、 *.sql/docker-entrypoint-initdb.d に置いた上でrun する( -v /mem/docker/mysql/dump/:/docker-entrypoint-initdb.d )と、MySQLコンテナ起動時にデータが流し込まれるようになっています。

シャイニング・ウィザードで使うMySQLは次のdocker runで構築できます。aptリポジトリをセットしてapt-get installしてmy.cnfを置き換えて再起動して、mysqldumpを流し込んで…とシェルスクリプトやansibleでやるよりも簡単です。

docker run --name mysqld -p 3306:3306 \
    -e MYSQL_ROOT_PASSWORD=secret \ 
    -e MYSQL_DATABASE=dbdbdbdb \ 
    -e MYSQL_USER=useruseruser \
    -e MYSQL_PASSWORD=passpasspass \
    -v /mem/docker/mysql/data:/var/lib/mysql \
    -v /mem/docker/mysql/conf/:/etc/mysql/conf.d \
    -v /mem/docker/mysql/dump/:/docker-entrypoint-initdb.d \
    -d mysql:5.6

ここで、 /mem というディレクトリ名で始まっているのは、 /mem を 下述するtmpfsとしてマウントして、mysqldump取得/インポート時の高速化を図るためです。そのためコンテナ側のMySQLデータディレクトリである /var/lib/mysql-v /mem/docker/mysql/data:/var/lib/mysql としてtmpfsである /mem 配下と共有するようにします。

6. tmpfsの活用

若者が知らなかったのでまずは簡単に説明します。tmpfsというのは簡単にいうと、メモリ上にファイルシステムを作るような仕組みです。例えば、 /mem を4Gの容量を持つtmpfsなディレクトリとしてマウントするには下記のようにします。通常のマウント同様、fstabに書いてやれば良いです。 mount -t tmpfs xxxx みたいなコマンド一発でも良いです。

mkdir /mem
echo '/dev/shm /mem tmpfs size=4096m 0 0' >> /etc/fstab
mount -a

これで /mem はメモリ上に展開されたファイルシステムとして振る舞います。さて、上記のようなコマンドをuser-dataに仕込み、 /mem 上でmysqldumpの取得/データ書き込みなどWrite IOがキツイ作業をすればその部分はかなり高速化されます。ステージングDBといえどデータ容量が数ギガあり、export/importに多少時間を食うので、ここは高速化したいポイントでした。ただし、当然、メモリ上にデータが置かれるので、OS再起動したらデータは全部消えます。シャイニング・ウィザードが用意するサーバはそもそも一時的なものなのでデータの保全性については揮発的で良いと考えています。

※ この部分を、日次で mysqldump && docker build && docker push などとしてダンプデータ入りのイメージを作ってしまい、user-dataでは単にdocker pullすれば良いだけ、にしてしまえばもっと早くなるんですがねw

tmpfsのような仕組みは、Writeがきつい、かつ、消えても良いデータの置き場、つまり今回のようなケースで役にたったりします。他にも、例えばtar.gzを生成してscpするバッチがあるとしてそのtar.gzを書き出すディレクトリとか、画像キャッシュサーバがあるとしてそのキャッシュの書き込み場所としてとか。

7. まとめ

f:id:hiroakis:20170224203845p:plain

チームメンバーに欲されている基盤を作るのはインフラ冥利に尽きる。

おわり

トレタのiPadアプリをSwift 3 対応しました

iOSエンジニアの高(@y_koh)です。

この度トレタではiPadアプリのSwift 3対応を行いました。どんな感じで進めたのかと、ハマったところなど共有できればなと思います。

対応自体は去年末には終えていましたが、年が明けて1/10にリリースしました。 年末は飲食店さま繁忙期のため、トレタではこの時期のアプリアップデートは控えています。例年このタイミングでリファクタリングやKaizenタスクなどを行っています。今回はSwift 3対応をメインに行いました。

先日サーバサイドもRailsを4.2にバージョンアップしています。言語やフレームワークのバージョンアップは機能改善に直接つながるものではないので後回しにしがちですが、将来的に負債になってしまうだけなので出来る限り時間を作って適宜アップデートするようにしています。

今回のSwift 3対応については、昨年のpotatotips#35でも発表させていただきました。 speakerdeck.com

概要

コード行数

9万行ほど

Swift使用率

Screen Shot 2016-11-26 at 12.48.04.png (21.8 kB)

トレタのベースはObjective-Cです。Swiftの占める割合は全体の2割ほどですが、コアなUI部分からSwift化しているので割りと複雑なところが多いです。基本的なスタンスとして新規追加もしくはリファクタリングを行うタイミングでSwiftを使用するようにしています。

その結果APIやモデル周りがまだObjective-Cのままなので、今後はこちらを設計見直しと合わせて対応していければと思っています。

期間

2〜3週間ほどかかりました。

進め方

こんな流れで進めていきました。

  1. 作業ブランチを切る
  2. ライブラリをアップデートする
  3. XcodeでSwift 3にConvertする
  4. コンパイルエラーをひたすら潰す
  5. 動作確認

作業ブランチを切る

iPadアプリはエンジニア3名体制で開発しているので他の開発と並行して作業することになります。feature/swift3ブランチで作業して、developに加わった変更を随時取り込んでいくことになります。

当然ながらdevelopから離れれば離れるほど大変になるので、迅速な対応が求められます。

ライブラリをアップデートする

ライブラリ自体がSwift 3対応している必要があるので対応状況を調査するところから始めます。 READMEにしっかり書いてあるものもあれば、何も書かれておらずブランチのみ用意されているものもあります。最悪メンテされていない場合は他のライブラリに乗り換える必要も出てきます。

時期的にもほとんどのライブラリは対応されていると思います。もし現状対応されていなければ乗り換えを検討されたほうが良いかもしれません。

またSwiftライブラリだけでなく、Objective-Cライブラリもnullabilityアノテーションに対応されているケースもあるので確認してみると良いと思います。

XcodeでSwift 3にConvertする

convertしようとするとこんな感じアラートが出て来てconvert出来ずにしばらく悩みました。

f:id:y_koh:20170126154245p:plain

http://stackoverflow.com/questions/39492974/tests-stop-working-under-xcode-8-test-host-error

こちらにあるように、テストターゲットのTesting > Target ApplicationNoneにすることで対応できました。

コンパイルエラーをひたすら潰す

ここからはコンパイルエラーをひたすら潰していきます。エラー数が多いとコンパイラが途中で諦めてしまうため、エラー数の総量を知ることが出来ません。よって、エラーを解消して残エラー数が減っていくとあるところでガッツリ増える、ということを繰り返してくことになります。

これに関してはどうすることも出来ないので諦めない心で頑張るしか無いです。コツとしては、まず全体を知るために、エラー解消が難しいところはちゃんとメモに残した上で、コメントアウトしてでも一旦通すのが良いと思います。

主要ライブラリ対応

SwiftBond

Migration from v4.x to v5.0を参考にMigrationします。

observeNewがしれっと無くなっているのでskipで代用します。

hoge.skip(first: 1).observeNext

また、bnd_notificationの場合は定義時にはクロージャは走らなくなっているのでskipする必要はありません。これに気づかずなぜか一回目だけ挙動がおかしいというのでハマりました。。

ライブラリ以外での変更

DispatchOnce

Swift 3 からはdispatch_onceに相当するものが提供されなくなりました。 こちらはlazy initialized globalsもしくはstaticプロパティで代用できます。

こちらの記事に詳しくまとめられていたので参照してみると良いと思います。 Swift3のGCD周りのまとめ - Qiita

Date

Swift 3からNSDateのSiwft実装Dateが提供されるようになりました。これにより、MTDatesというObjective-Cライブラリを使用していたのですが、NSDateのカテゴリ実装のためDateでは使えません。よってこの様にキャストする必要があります。

この時はまずビルドを通すことを目標にしていたので、地道に書き換えてましたが、後にブリッジするためのDate Extensionを作ってます。

(reservation.startAt as NSDate).tr_startOfToday())

CGFloat の剰余

これは少し驚いた変更でした。

y % heightByHour
↓
y.truncatingRemainder(dividingBy: heightByHour)

truncatingRemainder(dividingBy:) - FloatingPoint | Apple Developer Documentation

Int型の場合はいままでどおり%が使えます。

@escaping

Swift 3 の @escaping とは何か - Qiita

@noescapeがdeprecatedになり、逆に@escapingが必須になりました。

@escapingの有り無しで別メソッド扱いになるみたいで、interfaceに@escapingを付け忘れたときに呼べないということがありました。

removeRange

Screen Shot 2016-11-26 at 13.03.49.png (48.5 kB)

エラー内容がわかりにくくてちょっと悩みました。

courseLabels.removeRange(CountableRange<Int>(courses.count ..< courseLabels.count))
↓
courseLabels.removeSubrange(CountableRange<Int>(courses.count..<courseLabels.count))

Swift 3のRange徹底解説 - Qiita

iOS 10対応

ユーザーデータアクセス

写真選択時にエラーになりました。

This app has crashed because it attempted to access privacy-sensitive data without a usage description.  The app's Info.plist must contain an NSPhotoLibraryUsageDescription key with a string value explaining to the user how the app uses this data.

iOS 10 からInfo.plistにユーザーデータへアクセスする目的の記述が必須になっているのでこちらを対応しましょう。

[iOS 10] 各種ユーザーデータへアクセスする目的を記述することが必須になるようです | Developers.IO

その他

テストが動かない

Could not determine bundle identifier for xxxTest TEST_HOST: some path that does not exist

こんなエラーが出てきましたが、テストターゲットのHostApplicationを切り替えてみたら動きました。

IUOが無くなった影響で、文字列内式展開でOptionalが出力されてしまった

ラベルにそのまま"Optional(y_koh)“のように表示されてしまいました。これはUnwrapすることで対応しました。

var user = User() // modelはObjective-Cでnullabilityアノテーション指定なし
var label = UILabel()
label.name = user.name

var user = User() // modelはObjective-Cでnullabilityアノテーション指定なし
var label = UILabel()
if let user = user {
    label.name = user.name
}

Swift 3 対応時にハマったString Interpolate - Qiita

iOS 9でクラッシュ

Swift 3というかSDKの違いかもしれません。

let cell: TableLayoutListCell = tableView.dequeueReusableCell(withIdentifier: "ReservationCell", for: indexPath) as! TableLayoutListCell

上記を実行すると、

  • iOS 9 layoutSubviewsが呼ばれる
  • iOS 10 layoutSubviewsが呼ばれない

となって挙動が変わっています。今回は先にlayoutSubviewsが呼ばれることを想定していないコードになっていたのでUnwrap時にnilで落ちていました。

まとめ

Swift 3対応はアプリの規模にもよりますが、かなり根気のいる作業となります。これから対応される方は最初から完璧にやろうとせずにまずはコメントアウトしながらでもビルドを通すことを目標にすると良いと思います。

少しでもこれからSwift 3対応される方の助けになれば幸いです。

トレタのRailsを4.2にアップグレードしました

サーバサイドエンジニアの中村です。 今回はトレタのAPIに使っているRailsのバージョンを4.1.12から4.2.7.1にアップグレードしたので、その手順について紹介いたします。

トレタのAPIでrake statsを実行した結果は以下の通りです。同じ規模感のサービスのRailsをアップグレードするときに役立てていただけたら幸いです。

+----------------------+-------+-------+---------+---------+-----+-------+
| Name                 | Lines |   LOC | Classes | Methods | M/C | LOC/M |
+----------------------+-------+-------+---------+---------+-----+-------+
| Controllers          | 19554 | 16806 |     165 |     831 |   5 |    18 |
| Helpers              |   267 |   244 |       0 |      24 |   0 |     8 |
| Models               | 10685 |  6564 |     161 |     435 |   2 |    13 |
| Mailers              |   345 |   295 |      10 |      17 |   1 |    15 |
| Javascripts          | 14813 | 10032 |      16 |    1270 |  79 |     5 |
| Libraries            |  1316 |   992 |      24 |      89 |   3 |     9 |
| Concern specs        |   478 |   396 |       0 |       3 |   0 |   130 |
| Controller specs     | 23935 | 21178 |       1 |       3 |   3 |  7057 |
| Helper specs         |   147 |   118 |       0 |       0 |   0 |     0 |
| Lib specs            |   416 |   375 |       0 |       0 |   0 |     0 |
| Mailer specs         |  1958 |  1678 |       0 |       1 |   0 |  1676 |
| Model specs          | 11251 |  8762 |       0 |       2 |   0 |  4379 |
| Request specs        |    91 |    66 |       0 |       0 |   0 |     0 |
| View specs           |   679 |   574 |       0 |       0 |   0 |     0 |
| Worker specs         |  2906 |  2530 |       1 |       3 |   3 |   841 |
+----------------------+-------+-------+---------+---------+-----+-------+
| Total                | 88841 | 70610 |     378 |    2678 |   7 |    24 |
+----------------------+-------+-------+---------+---------+-----+-------+
  Code LOC: 34933     Test LOC: 35677     Code to Test Ratio: 1:1.0

サーバサイドチームのルールとして、APIの機能開発を行うときは必ずRSpecでのテストを書くようにしているので、テストが存在しないclassはほぼありません。現在、テストケースの数は約3000ケースあります。

Railsアップグレードの手順

手順は以下のように行いました。

  1. railsのバージョンをアップグレードする
  2. その他に依存するgemをbundle updateでアップグレードする
  3. rake rails:updateを実行する
  4. テストを全て通す
  5. staging環境にデプロイし、QAテストを行う
  6. 本番環境にデプロイ

非互換な変更や修正するべき部分の確認はRailsアップグレードガイドを参考にしました。

gemをbundle updateでアップグレードする

rails本体のバージョンをアップグレード後、テストを実行します。 この時点で1/3ぐらいのテストが落ちていたので、落ちたテストのstacktraceを確認して、関連しそうなgemをbundle updateで1つ1つアップグレードします。

ここで陥った問題として、csso-railsのバージョンを上げすぎてしまい、therubyracerで読み込めないJavaScript Codeが吐かれてしまいrake assets:precompileが失敗してしまいました。

この問題はcsso-railsのバージョンをダウングレードして回避するという方法を取りました。他の方法としては、therubyracermini_racerやNode.jsに置き換えると方法が考えられます。

関連するgemを全てアップグレードし終えたら、落ちるテストが10件ほどになったので、残りは1つ1つ地道に修正しました。この時点で、全てのテストが通りました。

QAテスト

staging環境にRailsアップグレード用ブランチをデプロイし、QAテストを行います。 テスト期間を2週間設けて、QAエンジニアとアプリ開発チームの協力の元、念入りにQAテストを行いました。

本番環境にデプロイ

通常のAPIのデプロイは週1回、平日昼間に行っていますが、今回のアップグレードはトレタを利用していただいている店舗さまへの影響を最小限にするために、深夜にデプロイを行いました。

EC2インスタンスの数を通常の倍に増やし、半数はアップグレードブランチのデプロイがされないようにしておき、万が一のためにすぐ切り戻せるようにしておきます。

デプロイ時、一部のテストが実行時間依存になっていることが原因でテストが落ちたことや、EC2インスタンスのネットワークが不調でデプロイ失敗など波乱がありましたが、無事デプロイ完了しました。

半日ほど様子を見て、本番で問題が起きていないことを確認後、古いコードが乗っているEC2インスタンスを削除し、全てのサーバーが4.2系のコードに切り替わりアップグレード作業が完了しました。

特にお客様からのお問い合わせも無く、安心してお店でトレタを使っていただいています。

まとめ

トレタのAPIのRailsのバージョンを1年半ぶりにアップグレードしました。 トレタのエンジニアはまだ人数も少ないこともありますが、基本的に機能開発をメインとするエンジニアしかおらず、リファクタリングや開発基盤構築をメインの職責とするエンジニアはいません。

しかし、継続的にコードを改善していく文化が根付いており、日頃から小さい改善を積み重ねていくことで、コードが大きな負債にならないように日々努力しています。今回のアップグレード作業でもほぼ問題なく、スムーズにアップグレードすることができました。

トレタでは機能開発が大好きで、継続的なコードの改善をやりたいというサーバサイドエンジニアを募集しています。トレタに興味を持たれた方はぜひこちらからご応募お願いします!

© Toreta, Inc.

Powered by Hatena Blog