CoffeeScript スタイルガイドの公開とその目的

こんにちは、クックパッド編集室の太田(@os0x)です。 普段は料理動画やクックパッドニュースなど、メディア寄りのサービスを担当しながら、社内のCoffeeScriptを中心としたウェブフロントエンドのコードレビューなどを行っています。 今回は、そのCoffeeScriptのレビューを円滑に行うためのコーディングスタイルについてお話したいと思います。

Style guides in Cookpad

クックパッドでは、github.com上でスタイルガイドを公開しているのをご存知でしょうか? cookpad/styleguide

これまで、Ruby / Objective-C / Java のコーディングスタイルが公開されていました。そして、本日 CoffeeScript のコーディングスタイルを追加しました。

さて、そもそもスタイルガイドとはなんでしょうか?コーディング規約とも言われたりしますが、スタイルガイドとコーディング規約はどう違うのでしょうか?

コーディング規約(ルール)とは

コーディング規約は、こう書かなければいけないというルールを定めたものです。それはルールなので、必ず従わなければいけません。 例えばJavaのコーディング規約はIDEの構文チェックツール*1とセットになっていて、その規約に従わなければわかりやすいエラーメッセージを表示してくれるといった具合です。 Javaような静的型付け言語では、言語の特性に加えてIDEの強力なサポートもあるので、ルールがあることでコードが書きやすくなる上に、レビュー時も細かいスペースについて指摘し合うような時間を節約することができます。

スタイルガイドとは

スタイルガイドは、ガイドという名前の通り、ルールほど強制力が強くないところがコーディング規約との大きな違いといえるでしょう。 こう書いたほうが読みやすいという部分を定めたものなので、ガイドに従っても読みやすくならない場合などはガイドに従わないということもあります。

RubyやJavaScript、CoffeeScriptのような動的型付けでインタプリタで実行される言語の場合、その動的さ故にIDEによるサポートは十分ではありません。 そういった環境で厳密なルールを決めて強制した場合、コードを書くことが窮屈になり、楽しくなくなってしまいます。 RubyやCoffeeScriptのような言語は書いていて楽しいというのが最大の長所と言っても過言ではないと思う*2のですが、そこに厳密なルールを適用してしまうと、その長所を潰してしまいかねません。 だからこそ、スタイルガイドという形がこれらの言語には最適なのです。

コードレビューとガイド

そもそも、なぜコーディング規約・スタイルガイドが必要なんでしょうか。 ガイドがあることで、

  • スペースの取り方などを統一されてコードがキレイになる
  • 議論になりそうな部分を定めておくことで、議論の時間を節約できる(文字列リテラルにシングルクォートとダブルクォートのどちらを使うかとか)
  • 書きやすさより、読みやすさを優先したコードにできる

といったメリットがあります。これらのメリットはコードのメンテナンス性を上げ、同時にレビューの効率をあげることに貢献します。

「書きやすさ」より「読みやすさ」

さて、スタイルガイドのメリットとして「書きやすさより、読みやすさを優先したコードにできる」という項目を挙げましたが、これについて少し掘り下げたいと思います。

コードの書き方というのは人それぞれですが、その書き方は、意識して書き方を変えていたりしない限りは、自分が書きやすい書き方で書いているはずです。 自分が書きやすい書き方が他の人、もしくは3日後の自分に取って読みやすい書き方とは限りません。 コードを書くのは一瞬(完成するかは別の話)ですが、一度書かれたコードはその瞬間から自分自身が繰り返し読むはずです。例えば、1行追加するだけの修正でも、前後の行はしっかり見てから追加しますよね。そういったことを繰り返し行なっているはずです。 さらにレビューなどで他人も読むのであれば、コードは書かれている時間より読まれている時間のほうが圧倒的に長くなります。

だからこそ、クックパッドのスタイルガイドは、書きやすさより、読みやすさを優先しています。 例えば、CoffeeScriptのガイドでは、「[MUST] メソッドの呼び出しの括弧は基本的に省略しない」という項目があります。 書きやすさだけを考えたら、括弧は省略したほうが書きやすいことはまず間違いないでしょう。しかし、読みやすさについて考えてみると、括弧がしっかり書いてあったほうが読み間違いをしにくく、レビューなどもしやすいと考えているので、括弧を省略しないというガイドを作りました。

最後に

スタイルガイドを定めておくと、レビューを円滑にすることができるので、より本質的な部分に集中しやすくなります。今回公開したCoffeeScript以外にも、Ruby / Objective-C / Javaのコーディングスタイルも公開していますので、この機会に目を通して頂ければ幸いです。

クックパッドでは一緒にレビューしあう仲間を募集しています。是非ご応募ください。

ソフトウェアエンジニア | クックパッド株式会社 採用情報

*1:CheckStyleやFindBugsなど

*2:あくまで個人的な意見です

7つのサンプルプログラムで学ぶRxJavaの挙動

会員事業部の山下(@tomorrowkey)です。
RxJavaが流行ってますね。最近Android版クックパッドでもRxJavaが導入されました。この記事は私がRxJavaを使うにあたって検証用のテストコードを書いたものをベースに、RxJavaの挙動をみなさんに紹介したいと思います。

目次

  • リスト操作でおさらいする基本的なRxJavaの使い方
    • Observable
    • Operator
    • Observer / Subscribe
  • 実行順序を確認するサンプルプログラム
    • 7つのサンプルプログラム

リスト操作でおさらいする基本的なRxJavaの使い方

RxJavaはAPIアクセスやイベントトリガーやリスト処理などを多岐にわたる処理に使うことができます。このエントリでは初学者に一番分かりやすいリストの処理を例に解説します。
これは1から10までの値を渡し、偶数だけにフィルタリングしたうえ、値を10倍にして、ログ出力するというプログラムです。

Observable.from(new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) // 1
  .filter(new Func1<Integer, Boolean>() { // 2
    @Override
    public Boolean call(Integer i) {
      return (i % 2) == 0;
    }
  })
  .map(new Func1<Integer, Integer>() {  // 2
    @Override
    public Integer call(Integer i) {
      return i * 10;
    }
  })
  .subscribe(new Observer<Integer>() {  // 3
    @Override
    public void onNext(Integer integer) {
      Log.d("Hoge", integer.toString());
    }

    @Override
    public void onCompleted() {
      Log.d("Hoge", "completed");
    }

    @Override
    public void onError(Throwable e) {
    }
  });

実行してみると以下のように出力されます。

20
40
60
80
100
completed

RxJavaの大まかな流れを説明すると以下のようになります。

  1. Observableを作る
  2. filterやmapなどのOperatorを使って値を加工する
  3. Observerを使ってObservableをsubscribeする

1つずつ解説します。

1.Observableを作る

データの元となるものをデータソースといいます。これはAPIのレスポンスだったり、ディスクに保存されているファイルだったり、単純にメモリ上の変数だったりします。 RxJavaではまずデータソースを提供するObservableを作る必要があります。
データソースを提供するObservableを作成する為のstaticメソッドがいくつか定義されています。今回は説明しませんが、データソースを提供するObservableは自作することができ、ファイルアクセスやAPIアクセスする場合は、自作する必要があります。
Observableを作るメソッドをいくつか紹介しましょう。

from

Observable.from(new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})

サンプルプログラムで使われているメソッドです。 配列を渡すことで、各要素をOperatorに渡します。 配列の他にListやIterableなどのOverloadがあります。

just

Observable.just(1, 5, 6)

10個までのオブジェクトをOperatorに渡します。 可変長引数ではないので、それ以上のオブジェクトを渡すことはできません。 配列やListではないオブジェクトをObservable化したい場合に使います。

range

Observable.range(1, 10)

startからcountまでのint値をOperatorに渡します。 サンプルプログラムではわざわざIntegerの配列を作っていましたが、これを使うことでより簡潔に書くことができます。

2.Operatorを使って値を加工する

Observableで作成されたデータを1つずつ受け取り、加工したり、フィルタリングしたり、他のObservableとマージしたりなどします。 いくつかのOperatorを紹介します。

filter

Observable.filter(new Func1<Ingeger, Boolean>() {
  @Override
  public Boolean call(Integer i) {
  return (i % 2) == 0;
  }
})

名前の通り値をフィルタリングするOperatorです。trueを返せばその値を採用し、falseを返せばその値を取り除きます。 このコードではリストの値が偶数だけになるようにフィルタリングしています。

map

Observable.map(new Func1<Integer, Integer>() {
  @Override
  public Integer call(Integer i) {
  return i * 10;
  }
})

受け取った値を違う値に変換するOperatorです。 このサンプルプログラムではIntegerの値を10倍にしています。 値の変換だけではなく、例えばIntegerからStringにしたりなど、違う型に変換することもできます。

3.Observerを使ってObservableをsubscribeする

ObserverではOperatorで加工した値を受け取ります。

onNext()は1つの値の処理が終わる度に実行されます。 onCompleted()ではすべての値の処理が終わったら実行されます。 onError()は一連の流れの中で例外が発生した時に呼ばれます。

Observable.subscribe(new Observer<Integer>() {
  @Override
  public void onNext(Integer integer) {
  Log.d("Hoge", integer.toString());
  }

  @Override
  public void onCompleted() {
  Log.d("Hoge", "completed");
  }

  @Override
  public void onError(Throwable e) {
  }
})

Observable.subscribe()を実行することで、Observableがデータソースの準備をしてOperatorにデータを渡します。 リストをデータソースとして指定した場合、すぐにデータを提供できるのですぐに実行されます。 サンプルプログラムを分解して、解説すると

Observable observable = Observable.from(new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
  .filter(new Func1<Integer, Boolean>() {
  @Override
  public Boolean call(Integer i) {
    return (i % 2) == 0;
  }
  }); // 1. まだobservableは実行されない

observable = observable.filter(new Func1<Integer, Boolean>() {
  @Override
  public Boolean call(Integer i) {
    return (i % 2) == 0;
  }
  }) // 2. まだobservableは実行されない

observable.subscribe(new Observer<Integer>() {
  @Override
  public void onNext(Integer integer) {
  }

  @Override
  public void onCompleted() {
  }

  @Override
  public void onError(Throwable e) {
  }
}); // 3. 実行される

1、2ではsubscribeするためのものを作っている段階なので実行されませんが、3で初めてsubscribeされるので一連の流れが実行されます。

サンプルプログラムをいくつか見よう

前節でRxJavaの基本的な動きはなんとなく分かったんじゃないかなと思います。
ここからは実行順序に関する7つのサンプルプログラムを提示します。変数の値がどのようになるか想像してみましょう。

ちなみにこれらのサンプルプログラムはこちらで公開しています。 https://github.com/tomorrowkey/RxAndroidTest

サンプルプログラム1

まずは簡単なサンプルプログラムです。 sbの内容はどうなるでしょうか

final StringBuilder sb = new StringBuilder();

Observable.just(1)
  .map(new Func1<Integer, Integer>() {
    @Override
    public Integer call(Integer i) {
      sb.append("1");
      return i;
    }
  })
  .subscribe(new Observer<Integer>() {
    @Override
    public void onNext(Integer integer) {
      sb.append("2");
    }

    @Override
    public void onCompleted() {
      sb.append("3");
    }

    @Override
    public void onError(Throwable e) {
      sb.append("4");
    }

  });
sb.append("5");

テストコード1

assertThat(sb.toString(), is("1235"));

前述の説明を理解できていれば簡単に分かったと思います。
Integerの値1をデータソースにリスト処理します。mapのOperatorが1度だけ実行され、ObserverのonNext()、onCompleted()の流れで実行されます。

サンプルプログラム2

今度は実行順序を確認するためにsleepを入れてみましょう。
sbの内容はどうなるでしょうか

final CountDownLatch latch = new CountDownLatch(1);
final StringBuilder sb = new StringBuilder();

Observable.just(1)
  .map(new Func1<Integer, Integer>() {
    @Override
    public Integer call(Integer i) {
      sleep(500);
      sb.append("1");
      return i;
    }
  })
  .subscribe(new Observer<Integer>() {
    @Override
    public void onNext(Integer integer) {
      sb.append("2");
    }

    @Override
    public void onCompleted() {
      sb.append("3");
      latch.countDown();
    }

    @Override
    public void onError(Throwable e) {
      sb.append("4");
    }
  });

sb.append("5");

latch.await(10, TimeUnit.SECONDS);

テストコード2

assertThat(sb.toString(), is("1235"));

特に何も指定をしなければ、subscribeを実行した時点で実行した時のスレッドを使って最後まで実行されるため、サンプルプログラム1と同じ挙動になります。

subscribeOn

RxJavaの一連の処理を実行するスレッドを指定したければ、subscribeOnを使います。

Observable.subscribeOn(Scheduler)

例えばAndroidのmainスレッドを使いたければ以下の様に指定します。

Observable.subscribeOn(AndroidSchedulers.mainThread());

mainではない別スレッドを使いたければ以下のように指定します。

Observable.subscribeOn(Schedulers.newThread());

リストの要素一つ一つに対してネットワーク処理を行いたい場合や、ディスクIOをしたい場合など、Operatorで重たい処理をしたい時には、スレッドを指定できるので便利ですね。

サンプルプログラム3

subscribeOnを使った際の実行順序を確認してみましょう。
sbの内容はどうなるでしょうか

final CountDownLatch latch = new CountDownLatch(1);
final StringBuilder sb = new StringBuilder();
Observable.just(1)
  .subscribeOn(Schedulers.newThread())
  .map(new Func1<Integer, Integer>() {
    @Override
    public Integer call(Integer i) {
      sleep(500);
      sb.append("1");
      return i;
    }
  })
  .subscribe(new Observer<Integer>() {
    @Override
    public void onNext(Integer integer) {
      sb.append("2");
    }

    @Override
    public void onCompleted() {
      sb.append("3");
      latch.countDown();
    }

    @Override
    public void onError(Throwable e) {
      sb.append("4");
    }
  });
sb.append("5");

latch.await(10, TimeUnit.SECONDS);

テストコード3

    assertThat(sb.toString(), is("5123"));

Operatorはmainではないスレッドで500msec待ってから実行されるので、先にmainスレッドが実行されました。

サンプルプログラム4

さらにどの部分がどのスレッドで実行されるか確認しましょう。
listの内容はどうなるでしょうか。

final CountDownLatch latch = new CountDownLatch(1);

final List<String> list = new ArrayList<>();
Observable.just(1)
  .subscribeOn(Schedulers.newThread())
  .map(new Func1<Integer, Integer>() {
    @Override
    public Integer call(Integer i) {
      list.add("1:" + Thread.currentThread().getName());
      return i;
    }
  })
  .subscribe(new Observer<Integer>() {
    @Override
    public void onNext(Integer integer) {
      list.add("2:" + Thread.currentThread().getName());
    }

    @Override
    public void onCompleted() {
      list.add("3:" + Thread.currentThread().getName());
      latch.countDown();
    }

    @Override
    public void onError(Throwable e) {
      list.add("4:" + Thread.currentThread().getName());
    }
  });

latch.await(10, TimeUnit.SECONDS);

テストコード4

assertThat(list.size(), is(3));
assertThat(list.get(0), is(matches("1:RxNewThreadScheduler-\\d")));
assertThat(list.get(1), is(matches("2:RxNewThreadScheduler-\\d")));
assertThat(list.get(2), is(matches("3:RxNewThreadScheduler-\\d")));

subscribeOnでのスレッド指定はObserverまで影響受けるので、OperatorだけではなくObserverのメソッドも別スレッドで実行されました。
AndroidではUIスレッド以外でViewの更新ができないので、このまま通信処理などのために利用することはできませんね。

ObserveOn

observeOnを使えばObserverが指定されたメソッドで実行されます。
subscribeOnで別スレッドを指定し、Operatorで重たい処理を実行して、Observerで処理した内容をUIに反映したいといった時にobserveOnを使います。

サンプルプログラム5

observeOnを使った時の実行順序とスレッドを確認しましょう。
listの内容はどうなるでしょうか

final CountDownLatch latch = new CountDownLatch(1);

final List<String> list = new ArrayList<>();
Observable.just(1)
  .subscribeOn(Schedulers.newThread())
  .map(new Func1<Integer, Integer>() {
    @Override
    public Integer call(Integer i) {
      list.add("1:" + Thread.currentThread().getName());
      return i;
    }
  })
  .observeOn(AndroidSchedulers.mainThread())
  .subscribe(new Observer<Integer>() {
    @Override
    public void onNext(Integer integer) {
      list.add("2:" + Thread.currentThread().getName());
    }

    @Override
    public void onCompleted() {
      list.add("3:" + Thread.currentThread().getName());
      latch.countDown();
    }

    @Override
    public void onError(Throwable e) {
      list.add("4:" + Thread.currentThread().getName());
    }
  });

latch.await(10, TimeUnit.SECONDS);

テストコード5

assertThat(list.size(), is(3));
assertThat(list.get(0), is(matches("1:RxNewThreadScheduler-\\d+")));
assertThat(list.get(1), is("2:main"));
assertThat(list.get(2), is("3:main"));

Operatorは別スレッドで実行され、Observerはmainスレッドで実行されるようになりました。 AsyncTaskのように見えてきませんか?だいぶRxJavaへ親しみがもてるようになってきたかと思います。

サンプルプログラム6

もうすこしsubscribeOnの挙動を見てみましょう。
2回subscribeOnを実行した場合にどうなるでしょうか。

final CountDownLatch latch = new CountDownLatch(1);

final List<String> list = new ArrayList<>();
Observable.just(1)
  .subscribeOn(AndroidSchedulers.mainThread())
  .map(new Func1<Integer, Integer>() {
    @Override
    public Integer call(Integer i) {
      list.add("1:" + Thread.currentThread().getName());
      return i;
    }
  })
  .subscribeOn(Schedulers.newThread())
  .map(new Func1<Integer, Integer>() {
    @Override
    public Integer call(Integer i) {
      list.add("2:" + Thread.currentThread().getName());
      return i;
    }
  })
  .observeOn(AndroidSchedulers.mainThread())
  .subscribe(new Observer<Integer>() {
    @Override
    public void onNext(Integer integer) {
      list.add("3:" + Thread.currentThread().getName());
    }

    @Override
    public void onCompleted() {
      list.add("4:" + Thread.currentThread().getName());
      latch.countDown();
    }

    @Override
    public void onError(Throwable e) {
      list.add("5:" + Thread.currentThread().getName());
    }
  });

latch.await(10, TimeUnit.SECONDS);

テストコード6

assertThat(list.size(), is(4));
assertThat(list.get(0), is("1:main"));
assertThat(list.get(1), is("2:main"));
assertThat(list.get(2), is("3:main"));
assertThat(list.get(3), is("4:main"));

subscribeOnはOperatorが実行されるスレッドを指定するもので、実行した時点でスレッドを変えるような効果はありません。 よって、先に実行されたmainスレッドの指定が採用され、すべてmainスレッドで実行されました。

サンプルプログラム7

さきほどのサンプルプログラムでだいたいsubscribeOnの挙動は分かったと思いますが、もう1つだけ確認してみましょう。 途中からsubscribeOnを指定した場合どうなるでしょうか。

final CountDownLatch latch = new CountDownLatch(1);

final List<String> list = new ArrayList<>();
Observable.just(1)
  .map(new Func1<Integer, Integer>() {
    @Override
    public Integer call(Integer i) {
      list.add("1:" + Thread.currentThread().getName());
      return i;
    }
  })
  .subscribeOn(Schedulers.newThread())
  .map(new Func1<Integer, Integer>() {
    @Override
    public Integer call(Integer i) {
      list.add("2:" + Thread.currentThread().getName());
      return i;
    }
  })
  .subscribe(new Observer<Integer>() {
    @Override
    public void onNext(Integer integer) {
      list.add("3:" + Thread.currentThread().getName());
    }

    @Override
    public void onCompleted() {
      list.add("4:" + Thread.currentThread().getName());
      latch.countDown();
    }

    @Override
    public void onError(Throwable e) {
      list.add("5:" + Thread.currentThread().getName());
    }
  });

latch.await(10, TimeUnit.SECONDS);

テストコード7

assertThat(list.size(), is(4));
assertThat(list.get(0), is(matches("1:RxNewThreadScheduler-\\d+")));
assertThat(list.get(1), is(matches("2:RxNewThreadScheduler-\\d+")));
assertThat(list.get(2), is(matches("3:RxNewThreadScheduler-\\d+")));
assertThat(list.get(3), is(matches("4:RxNewThreadScheduler-\\d+")));

途中でsubscribeOnを指定したとしても最初のOperatorからスレッドが変わります。
でも途中にsubscribeOnを書くと可読性が落ちるので最初に書くと分かりやすいでしょう。

最後に

リスト操作を通じてRxJavaの挙動を確認しました。
今回は実行順序を確認するために最低限の説明しかしませんでしたが、その他にRxJavaをAndroid向けに機能追加したRxAndroidがあったり、オリジナルのOperatorを提供する拡張ライブラリもあります。RxJavaを使いこなせるようになった!と言えるようになるためにはまだまだやることは多そうです。
RxJavaはJavaにはない独自の世界があり、いままでJavaやAndroidのコードを書いてきた人にとってはどこかとっつきにくいところがあるように思いますが、このエントリを通してすこしでも理解が深まればと思います。

クックパッドではRxJavaを始めその他最新ライブラリを駆使して、スピーディに高品質なモバイルアプリを作っていくモバイルエンジニアを募集しています!興味がある方はぜひご応募ください。
iOS/Android アプリエンジニア | クックパッド株式会社 採用情報

コードで行うMySQLのアカウント管理

インフラストラクチャー部の菅原(@sgwr_dts)です。

インフラストラクチャー部のメンバーはオペレーションのため強力な権限のMySQLアカウントを使用していますが、サービス開発をするエンジニアも業務のためにサービスのDBの参照・更新権限を持ったアカウントが必要になることがあります。

セキュリティやオペレーションミスのことを考えると、すべてのエンジニアのアカウントをスーパーユーザーにするわけにはいかないため、都度適切な権限を付与していますが、手動での作業は地味に手間がかかります。

そこでクックパッドではMySQLのアカウント情報をコード化し、リポジトリで管理するようにしています。

gratanによるコード化

MySQLのアカウント管理はgratanという自作のツールを使って行っています。 gratanを使うとMySQLのアカウントをRubyのDSLで記述することができるようになります。

require 'other/grantfile' # 他の権限定義ファイルを読み込む

user "scott", ["127.0.0.1", "%"] do
  on "*.*" do
    grant "USAGE"
  end

  # test DBへの権限付与は2014/10/08まで
  on "test.*", expired: '2014/10/08' do
    grant "SELECT"
    grant "INSERT"
  end

 # test2 DBのresipe_*テーブルに対して権限を付与
  on /^test2\.recipe_/ do
    grant "SELECT"
    grant "INSERT"
  end
end

上記のDSLをMySQLに適用すると、たとえば以下のようなログが出力されます。

$ bundle exec rake apply[db_foo]
[WARN] User `scott@%`: Object `test.*` has expired
[WARN] User `scott@127.0.0.1`: Object `test.*` has expired
REVOKE SELECT ON `test`.`*` FROM 'scott'@'%'
REVOKE INSERT ON `test`.`*` FROM 'scott'@'%'
REVOKE SELECT ON `test`.`*` FROM 'scott'@'127.0.0.1'
REVOKE INSERT ON `test`.`*` FROM 'scott'@'127.0.0.1'
GRANT SELECT ON `test2`.`recipe_photos` TO 'scott'@'%'
GRANT SELECT ON `test2`.`recipe_photos` TO 'scott'@'127.0.0.1'
FLUSH PRIVILEGES

gratanは冪等性を保証しているので、MySQLの権限がすでに定義ファイル通りである場合には、なにも変更を行いません。

$ bundle exec rake apply[db_foo]
No change

また、expiredを記述すると、指定した日付以降にDSLを適用した場合に、期限の切れた権限をREVOKEするようになります。このため、現在の運用では定期的にDSLの適用を行い、期限が切れた権限がDBに残らないようにしています。

このような定義ファイルを各エンジニアごとに作成し、以下のようなディレクトリ構成でGitに保存しています。

repo
├── Gemfile
├── Rakefile
├── db_bar
│   ├── Grantfile
│   ├── alice.grant
│   └── bob.grant
└── db_foo
    ├── Grantfile
    ├── scott.grant
    └── tiger.grant

権限付与のワークフロー

エンジニアへの権限の付与は次のようなワークフローで行われます。

  1. エンジニアがリポジトリをFork
  2. 必要な権限を追加した修正をPull Request
  3. Pull Requestをレビューして問題がなければマージ
  4. マージしたリポジトリのアカウント情報をMySQLに適用

何がうれしいのか

オペレーションのしやすさ

権限付与の作業はrakeタスクで自動化されているので作業者はrakeコマンドを実行するだけでよく、オペレーションミスを防ぐことができます。

アカウントの見通しの良さ

すべてのエンジニアのMySQLアカウントはテキストファイルとしてGitに保存されているため、誰が、どのDBに対して、どのような権限を持っているかがすぐに把握できます。そのため不必要なアカウントがずっと残ってしまうような問題を防ぐことができます。

履歴が残る

Gitで管理しているので、いつ誰にどのDBの権限を与えたかがすべて残ります。またどのような理由にで権限が必要になったかについても、Pull Requestの形でレビューとともに履歴が残ります。

おわりに

今のところMySQLへの適用作業はインフラエンジニアが手動でコマンドを実行しているのですが、Pull Requestがマージされたタイミングで自動的に適用するのもそれほど難しいことではないため、できれば完全に自動化したいと考えています。

インフラストラクチャー部では、刺身タンポポを撲滅できるエンジニアを募集しています。

インフラエンジニア | クックパッド株式会社 採用情報