Ruby on Railsでバッチ書く時どうするか問題

別に問題じゃなさそうなことに問題って名前をつけたのは公式で用意してくれているバッチインターフェースが二つあって、いつも迷っちゃうからだ。

$ rails foo:bar:baz

のようにコマンドを作れるのがrake taskです。
これは普通にrails db:migraterails g scaffold Fooのようにいろいろなところで開発者が使うからおなじみのコマンドです。

もう一つのインターフェースが、rails runnerと呼ばれるやつで、こちらは引数に自由にruby(しかもrailsの資産をつかった)のコードを書くことができる。

普通にバッチを書くならどっちでも問題ない

基本的にどっちを使っても問題はありません。しかしrails runnerを作るときはインターフェースを自分で考える必要があります。以前言及した記事としては app/batches 配下にバッチフレームワークのようなものを作り、 rails runner FooBatch.run のように書くものでした。

自前のバッチフレームワークのようなものが必要なのはひょっとしたら過去の時代の話かもしれません。例えばkuroko2やjenkins、rundeckのようなcron replacementと呼ばれるツール群を使えば、共通処理で書いていた排他制御やロギングなどをそちらに任せることができます。そしてkuroko2やjenkinsの先ではrailsの入ったdockerを実行させるようなことも簡単にできます。
また、dockerの話を続けるとk8sのcronojobを使えばGKEやAKSのサポートの上で、kuroko2で得ていた問題を吸収することができます。

rails runnerrake task のどちらを使おうとも、結局バッチを実行するツールが実行するインターフェースはdocker runのようなものだったりするわけです。

そもそもバッチっていつ書く?というもう一つの問題

そもそもrailsをつかっているということは、おそらくはWebサービスのバッチを動かすということだと思います。
基本的にバッチ処理にしなくてもバックグラウンドジョブを利用するようにした方が、エンドユーザーにとっては処理完了のタイムラグも短くsidekiqやrescueのようなワーカーを使えばリトライも自動的に行ってくれるというメリットがあります。

それでもバッチ処理はある時点で行う集計を伴う処理には必要なものであると言えます。

思考停止でバッチにやらせない判断をする

僕は新規のサービスに携わることが多いですが、ほとんどバッチを使う機会がなくなってきている印象にあります。集計は基本的にredashのようなツールを用いるため、railsが関与することは減らすことができています。

一方ジョブキューの処理はちょっとしたデータの更新や重い処理を行うのにも非常によく使うようになってきています。
例えばトランザクションに影響されないAPIコールなども可能な限りバックグラウンドジョブで行うようにしてretryの判断をジョブキューで行わせるようにしています。

これは月次や日次のバッチに書き直そうとなるときもあるのですが、一旦ジョブキューで行わせるように検討してから、時間がかかりすぎるからバッチにしましょうと後で移行することも増えてきました。

そこで浮上する第三のバッチインターフェース

これはバッチように作られたインターフェースではないのですが、activejobでバッチ処理をキックする運用をやって意外とうまくいくじゃんという成功体験がありましたのでシェアします。

namecpace :es do
  task index_rebuild: enviroment do
    ElasticSearch::IndexRebuildJob.perform_now
  end
end
class Elasticsearch::IndexRebuildJob < BatchJob
  def perform(foo)
  end
end

こんな感じでindex rebuildを最初は管理画面から実行して行っていたのですが、時間がかかるようになりワーカーサーバーとは別のコンテナで実行したいという要望がでてきました。
そこでrake taskから同期的にActiveJobを呼び出すという荒技をやってしまうと意外とバッチインターフェースとしてしっくりきました。

上記では BatchJob を継承していますが、rake taskでは難しかった処理の共通化をフックメソッド before_perform などを使って制御することも可能になります。

まとめ

ここ最近バッチ処理どうしようかなぁという人をちょいちょい見かけたので、だらだらと書いてみました。

hubコマンドを窓から捨てて、ghへ移行した話

GitHub が新たに開発した GitHub用のCLIツールがめっさ便利なので、勢いあまって記事を書いたのですが、塩漬けにしていたので、とりあえず公開。
もう速報性はなくなってしまったけれど、便利に使っています。便利。

cli/cli: GitHub’s official command line tool
これね。

hubコマンドクッソ便利(でした)

$ git push origin feature-bransh

したあとは、そのまま

$ hub pull-request -o

とするのが手癖として根付いています。

hubコマンドに対する不満

手癖になってはいるのけれど、解決できていない問題がありました。
それはPull Requestをdraftで生成できないことです。

ですので、DraftのPull Requestを作りたい場合は $ hub browse を使ってブランチを表示。その後、ブラウザ上でPull Requestを作成していました。

$ gh pr create -w という革命

$ gh pr create -w

これを使えば今までやりたかったPull Requestの作成が簡単になるだけではなく、全てをdraftで作ってから細かい点をブラウザ上で修正するというワークフローがやりやすくなりました。

見た目を伴う修正の場合は、スクリーンショットをPull Requestのdescriptionの項目に貼ったりしますので、この辺りも便利です。

$ gh pr create -df を使えば完全に作業途中のpull requestを一時保存するのにも使えます。

gh コマンド便利

hubコマンドは手癖になってしまっているので、とりあえず今から $ brew uninstall hub を実行します!!!

コダテルというコワーキングスペースでリモートワークをしてきました

オンラインコミュニティnoraneeresのメンバーと一緒に、愛媛県八幡浜市に行ってリモートワークをしてきました。

リモートワークで場所を変えるというテクニック

リモートワークについて書かれた『強いチームはオフィスを捨てる』(原題:REMOTE)の本の中で、いくつかリモートワークの落とし穴にはまらないようにするアイデアが書かれています。その中の一つを紹介します。

全く問題ない人もいるけれど、人によっては、ひとりきりだと仕事に集中できなくなることがある。そういう人のためにシンプルな解決策を教えよう。
ノートパソコンを持って、Wi-Fiのあるカフェに行けばいいのだ。

私は、シェアオフィスを借りて作業をしているのですが、「何か集中できないなぁ」というときには、家に帰ったりカフェに行ったりします。
シェアオフィスで集中できなったのに、カフェだと集中できたり、家だと集中できたりするので、不思議なものです。

その延長線で、愛媛まで行ってみた

さて、今回はその延長線で愛媛まで行ってみました。
(まぁ集中力に悩みを抱えていたわけではないのですが、いい機会だったので)

コダテル

コダテル さんはなんと宿泊機能がついたコワーキングスペースです。
東京から飛行機と車でちょっと足を伸ばせば、そこで作業ができて、お昼に東京より新鮮な魚が食べれて、夜に温泉が入れて、めっちゃ美味しい居酒屋で飲めます。

コワーキングスペースといっても、東京のIT企業系の人とかがバシバシ働いているような感じではなくて、ふらーっと地元の高校生がやってきたりする緩さが良かったです。

↑ は僕がいた時ではないけれど、コダテルの浜田さんのツイート。なるほどカオスとも呼べるか(笑)

あと、コダテルの会員さんの人たちに地元の魅力を語っていただく会もしてもらい、地元を盛り上げていく人たちの熱さを感じました。

愛媛での集中力について

コダテルさんが快適だったので、めっちゃ普通に仕事はできました。
オンラインMTGもビデオ通話していたのですが、wifiも問題なくつながりました。
コダテルさんめっちゃ働きやすかったです。

じゃぁ、成果がめっちゃ上がったか?というと、ぶっちゃけそうではなかったです。
この写真のとき障害発生してて、あたふたしてましたし。。。

東京に帰ってきてからの集中力が上がったきがする

ただ、愛媛から帰ってきてから、仕事への集中力が上がった気がします。
やはり旅行のようなものを体験すると、何かにスイッチが入るんでしょうね。
(本当に佐田岬の景色は最高でした)

毎日は難しいかもしれませんが、1年に一度くらいは遠く離れた場所でリモートワークをするのは効果的だなぁという感想を得ました。

多拠点生活をする人たちはこういう感覚を求めているんでしょうね!わかった気がする。

開発合宿とかも、合宿中じゃなくて帰ってきてからに効果があるのかも。

泊まれるコワーキングええやん!

コダテルさんのように泊まれるコワーキングスペースは次回やるときも選択肢に入りそうですね!

合わせて読みたい

エンジニア4人で徒党を組んでフリーランス社員旅行 in 愛媛・八幡浜をしてきた話。
フリーランスエンジニアだって、開発合宿とか社員旅行とかいきたい #愛媛編
フリーランス合同社員旅行vol1 in愛媛を開催した話

一緒に行った人たちの記事。完全に旅行って言ってて、仕事してなくて草。リモートワークしに行ったのは僕だけだったw

泊まれるコワーキングスペースに東京からエンジニアのみなさんが遊びに来てくれた!
コダテルの浜田さんの記事。とてもいい人でした!いい人オーラが滲み出てるタイプ!

エージェント完全脱出マニュアルというタイトルでLTしてきました(しがないラジオmeetup 4)

はい。 #しがないラジオmeetup 4 - connpass に参加してまいりました。
(そういえばpodcastの話はあんまりしなかったな〜)

しがないラジオmeetupとは

『しがないラジオ』というpodcastを一回でも聞いたことがある人が参加できるmeetupです。podcast好き率高めですが、エンジニアとかそういう縛りは特にありません。
今回は公募枠LT5枠とスポンサーLT1枠、パーソナリティのお二人のLT2枠と、懇親会という内容でした。

エージェント完全脱出マニュアルというタイトルでLTしてきました

見事、公募枠のLTに当選し登壇してきました。
最近、登壇とかやってなかったし、LTにしては10分と割と枠があったので、緊張感もあり楽しめました。

なんだか、見境のなくあやかりまくったスライドが気がついたらできてました。ごめんなさいmm

10分のLTにスライドは40枚。
緊張と早口で大阪弁全開でまくしたて立ったぜ。

なんでこのテーマか???

よくどうやって仕事を受注してるんですか?ということを聞かれるので、自分でもよくわからんなぁと思い話してきました。

twadaさんとかkakutaniさんとかa_matsudaさんとかmizchさんとか、業界の中でプレゼンスを持っている方には足元に及ばない僕が、どうやってお仕事を受注してるかという話で。

エージェントでフリーランスになる話はごまんと存在する

それもそのはず、エージェントが大量にアフィリエイトをやってますから。。
また、最近こういう書籍も出版されて、なかなか的を射ているので、私がとやかくいう必要はないでしょう。
エージェントで仕事をするための、まとまった話は以下の本に書いてあります。


エンジニアがフリーランスで年収1000万円になるための稼ぎ方

それより先の話をしているのはこの本です。


ITエンジニアが独立して成功する本~年収2000万円を稼ぐ! 単行本 – 2005/1/21

この本はちょっと。ファーストステップにはちょっと歩幅が長い本かもしれないとおもいました。

しがないmeetupで話すべき内容だったか謎 (笑

というわけで、普通のエンジニアがエージェントを経由せずに仕事を受けるにはどうしたいいか?という内容が必要なのじゃないか?と考えたのですが。。。

なぜかフリーランスエンジニア向けの内容を考えていないんだけど、よく考えたらSIer脱出とは似ているけど、関係ない話だった。

なんで、この話をしようと思ったんだっけ?と考えいたのですが、、、

多分VOYAGE GROUPでこの話をすることが自分の中での何かの記号になったんだと思う

僕にとってはエージェント経由で常駐していたVOYAGE GROUP(以下VG)で、自分で仕事を取れるようになり、この話をすることに意味があったので、直感でこの話にしてしまったのでしょう。

今の僕にとっては、VG時代の繋がりは大事で、今もお仕事で付き合いがあったりする。VGの人は優秀なので、転職先や独立先で自分に仕事のお誘いをいつもくれる本当にありがたいことだ。

VGも新陳代謝で人が入れ替わっていくけれど、ajitoには変わらないメンバー(退職済み含む(爆))がいる。
今日はajitofmの舞台ということもあって、しがないリスナーにも興味のある方々がいたので、ajitoをみたいと言っていた人々と一緒に見学してきました。

綺麗になったVOYAGE GROUP楽しそうでした。

さらば!fat controller - scaffoldで生成されたようなコントローラーを意識しよう!

流しのRails業でチームの中で共有したtipsをちょっとずつブログにも書いていくぞという。気持ち。
ちょっと初心者向けの話題のように思うかもしれないけれど、私もなんども苦しんだことなので。

$ rails g scaffold Book name:string price:integer

scaffoldでこのように生成したとしよう。
そうすると、BooksController#create は以下のように生成される。

# POST /books
# POST /books.json
def create
  @book = Book.new(book_params)

  respond_to do |format|
    if @book.save
      format.html { redirect_to @book, notice: 'Book was successfully created.' }
      format.json { render :show, status: :created, location: @book }
    else
      format.html { render :new }
      format.json { render json: @book.errors, status: :unprocessable_entity }
    end
  end
end

もう少し簡易化してみましょう。

# POST /books
def create
  @book = Book.new(book_params)

   if @book.save
     redirect_to @book, notice: 'Book was successfully created.'
   else
    render :new
  end
end

respond_toを排除し、HTMLしか返さないようにしてみました。
何の変哲も無いcreateアクションに見えるでしょうが、これがrailsのcreateの基本形です。全てのPOSTをこの形で書けるようにすべきです。

例えば、Serviceクラスを使う時はこうなっていると綺麗です。

def create
  if BookRegisterService.call(book_params)
    redirect_to :show, notice: 'Book was successfully created.'
  else
    render :new
  end
end

save modelのインターフェースを委譲されたmodelでデコレーターパターンを用いて実装すると、もっと元の形に近いままです。

def create
  @book = Manga.new(Book.new(book_params))
  if @book.save
    redirect_to @book, notice: 'Book was successfully created.'
  else
    render :new
  end
end

MangaはBookクラスから委譲されたインターフェースを持っています。

class Manga
  delegate_missing_to :@book
  def initialize(book)
    @book = book
  end
  def save
    # Manga固有のロジックをここに書く
    @book.save
  end
end

Formオブジェクトも似たような形にできます。

def create
  @book = BookForm.new(book_params)
  if @book.save
    redirect_to @book, notice: 'Book was successfully created.'
  else
    render :new
  end
end
class BookForm
  include ActiveModel::Model
  attr_accessor :name, :price
  def save
    if valid?
      # フォームが正しかった時の処理
      true
    else
      false
    end
  end
end

とりあえず、今日はここまでにしておきます。