segment.ioをまじめに導入してみてわかったこと

segment.com

シリコンバレーベンチャーな、アプリに関わるASPサービスを一括で入れられるよ!というサービス。 前はドメイン名そのままでsegment.ioって名前だったんだけど、今はsegment.comになっている。 iOSアプリにこれを導入してみてわかったこと。

①チーム全員が英語読めないとついてけない。

ほぼ全部英語のサービスなので、チームの英語脳が試される・・・

②一括で入れても結局定番のものしかつかわない。

①もあって、1個1個見ていくだけでも疲れる。

③各サービス本気で使おうと思うと有料になる

当たり前っちゃ当たり前なんだけど、入ってる各サービスを無料版で使ったあと有料にしてまで使おうと思うとハードル高い。 あと、各サービスネイティブのSDK(各サービスが自分で作ってるSDK)を使って細かく設定すれば無料でもそれなりに使うことができるんだろうけど、中間にsegment.ioのSDKが挟まっていて、細かい設定ができなかった結果、すぐ無料リクエスト数の上限に達したりして、サービスの良さが判別できない、という流れもあった。

apple審査でよくわからないダメ出しされると、ビビってサードパーティ周りで犯人探しが始まり、よくわからないものがいっぱい入っているsegment.ioのSDKが真っ先に切られる

6月くらいにあったブースト標的のリジェクトの時に、ビビった僕はグレーなSDKはすべて切ったのでした・・・。 →結局犯人ではなかった。

SDKの導入はすげー簡単でよかった

ドキュメントも充実していて、かなりスムーズにはいった。

⑥目的を決めてなくて手段だけいっぱいある感じ

いろいろサービス見るだけならいいけど、解決したいことを決めてsemgentを導入したわけではないので、ソリューションだけたくさん提示されてお腹いっぱい

⑦やたらと深夜にメルマガや1日のスタッツが送られてくる

時差ボケ

Rails: herokuでstagingからproductionにデータコピーする方法

herokuは起動に30秒かかってもOKならdyno=1で無料なので、staging環境が気軽に作れる。

qiita.com

stagingで確認したデータをそのままproductionに移したい。 こんなかんじでできる。

 def self.copy_to_production(object)

    ...   
    #DBを本番に切り替え
    object.class.establish_connection :adapter => 'postgresql',
      :encoding => 'unicode',
      :database => ENV["PRODUCTION_DB_NAME"],
      :pool => '5',
      :username => ENV["PRODUCTION_DB_USERNAME"],
      :password => ENV["PRODUCTION_DB_PASSWORD"],
      :host => ENV["PRODUCTION_DB_HOST"],
      :port => ENV["PRODUCTION_DB_PORT"]
    ...

    #本番にデータを追加
    attrs = object.attributes
    attrs.delete("id")
    attrs.delete_if{|item| item.include?("_id")} #外部キーは環境で異なるので削除
    new_object = object.class.create(attrs)
    new_object.save
  end

objectはActiveRecord::Baseのサブクラス(RailsのModel)のインスタンス

establish_connectionで別DB(productionのDB)に繋ぎ直して、そっちでcreateするとそっちにcommitされる。

connectionの設定情報はherokuのherokupostgresの管理画面から確認できる。

f:id:yuma_nishizaki:20150723235237p:plain f:id:yuma_nishizaki:20150723235227p:plain

connectionを元に戻す

で、これだけだとproductionのDBに繋げたまんまで、stagingに戻ってこれない。 (正確には、コピーしたオブジェクトのクラス単位(ActiveRecord::Baseのサブクラス単位)で別DBにつながるみたいなんで、例えばコピーしたクラスがUserクラスだったとすると、このままでもそれ以外の例えばBookクラスがあったとすると、これはstagingにつながったままになっている。)

Herokuでいうと、

heroku run rails c -a staging

して、上のメソッド使ってコピーして、rails cを終了させれば、DBの接続も切られて、新たにrails cしたときには再びデフォルトのstagingの方につながるので、この用途だけで使ってる分には問題ないっちゃないけど、 でもアプリケーションの中のロジックとして使うならちゃんと明示的に戻してやる必要あり。

  def self.copy_to_production(object)
    begin
      #DBを本番に切り替え
      object.class.establish_connection :adapter => 'postgresql',
        :encoding => 'unicode',
        :database => ENV["PRODUCTION_DB_NAME"],
        :pool => '5',
        :username => ENV["PRODUCTION_DB_USERNAME"],
        :password => ENV["PRODUCTION_DB_PASSWORD"],
        :host => ENV["PRODUCTION_DB_HOST"],
        :port => ENV["PRODUCTION_DB_PORT"]


      #本番にデータを追加
      attrs = object.attributes
      attrs.delete("id")
      attrs.delete_if{|item| item.include?("_id")} #外部キーは環境で異なるので削除
      new_object = object.class.create(attrs)
      new_object.save
    ensure
      object.class.establish_connection(ENV["DATABASE_URL"]) #元に戻す
    end
  end 

begin 〜 ensureで書いてやればOK。 establish_connectionはDB情報の引数での渡し方いろいろあって、

establish_connection (ActiveRecord::ConnectionHandling) - APIdock

URLでも渡せるし、上でやったようにhashで一個一個渡してもOK。

herokuだとENV["DATABASE_URL"]にデフォルトで必要情報はいってるから、戻すときはこれでOK。

動的にconfigurationになにか追加されている可能性があるため、元々の設定を取得しておいて、あとからそれを戻すほうが正解。

    original_config = ActiveRecord::Base.configurations[Rails.env] ||
                      Rails.application.config.database_configuration[Rails.env]   
    begin
    ...
    ensure
      object.class.establish_connection(original_config) #元に戻す
    end

参考:

When connecting to a second database, take care not to overwrite existing connections - makandropedia

これによると、begin 〜 ensureの書き方だとtransactionを絡めようと思うとうまくいかないらしく、サブクラス作って

class ReadDatabaseConnection < ActiveRecord::Base
  def self.abstract_class?
    true # So it gets its own connection
  end
end

を実装したうえで、このサブクラスに対してestablish_connection呼んでやれば別connectionが貼られるらしい。 transaction絡める場合は注意。

paperclipでS3に画像保存している場合

あとで書こう。

IAMのユーザでawsコンソールにログインする方法

iam user aws console とか iam user login to aws console

で検索すると古い記事が出てくるんだけど、IAM userのサインインURLを指定されたとおりにつくったらnot foundになる。

→userつくって、それをgroupに所属させたら、IAMのトップページにサインインURLがでてきたので、そこにアクセスして f:id:yuma_nishizaki:20150707211327p:plain

IAMのユーザタブに表示されるユーザIDと、Manage Passwordから設定したパスワードでログインしたら入れた。 f:id:yuma_nishizaki:20150707211436p:plain

herokuのdbをローカルにインポートする方法

herokupostgresのバックアップのダウンロードが管理画面からできるようになってた。 pgbackupsアドオンがアドオンじゃなくなって、herokupostgresの標準バックアップツールになったタイミングで変わったみたい。 前はコマンドラインからしかできなかったはず。

コマンドラインからの参考: www.ownway.info

①heroku postgresのページからインポートしたいDBを選ぶ f:id:yuma_nishizaki:20150706235453p:plain

②PG BackupsタブからDownloadボタンでダウンロードする f:id:yuma_nishizaki:20150706235509p:plain

③リストアコマンド打つ

pg_restore --verbose --clean --no-acl --no-owner -h localhost -U db_user_name -d store_development your/file/path

Rubyにとってのメソッド引数のタイプチェックとは

JavaとかObjective-Cとか静的な言語ではメソッドの引数やクラスのインスタンス変数などは予め型を指定しておく必要があって、その型に合わない引数が渡されると、コンパイル時にエラーになることによって、そのメソッド内では安心して引数にその型にしかわからないメッセージを送ることができる。

Rubyメソッドの引数やインスタンス変数の型チェックは行わないで、ゆるゆるでなんでも受け入れる代わりに、メソッド内で引数に対してconversion methodsと呼ばれる型変換メッセージを送った上で、変換された引数に対して安心してその型にしかわからないメッセージを送る。 f:id:yuma_nishizaki:20150703005954p:plain

例えばto_sで、なんでもかんでもStringに変換できるとかだと魔法みたいだけど、なぜこれが実現できているかというと、ほんとに何でもかんでもいろんなコアクラスにto_sが実装されているから実現できている。

f:id:yuma_nishizaki:20150703235144p:plain 何でもかんでもにto_sが実装されている

とはいえとはいえコンパイラチェックが効いている言語と比べると、1段階甘い「安心」であるような気がする。 言語として強制的にコンパイラチェックを通して、引数の型が保証されているのに対して、Rubyでは何でもかんでも入ってきた有象無象に対して、「to_sとかって意味わかります?」って聞いて、返事が返ってくれば「この人Stringっぽいよ」ってなって、Stringとして扱い始める。

なぜ不安なのか

不安な点を列挙してみると、

①to_s理解できないパターンがある

②to_sで変換した結果が、何になるのかよくわからない(そのオブジェクトに任されている)

メソッド使う側として考えると、そのメソッドの中でちゃんとコンバージョンメソッドとかでチェックしてくれているか不安。特にサードパーティコードから受け取った値をサードパーティコードにまた渡すような場合は、超不安になる。(例:Railsでコントローラから受け取ったparamsを直接Modelのwhereとかの引数で渡すとか)

nilに意味を持たせたい場合でも、nil.to_sすると""になってしまうので、正常なstringを受け取っているケースと同じフローにのってしまう ⇛なんでもかんでもコンバージョンさせていいのかな問題 例:Railsでコントローラでparams受けて、Modelをフィルタリングしたいんだけど、hashであるparamsにキーがない場合は特定の動きさせたいとか

そしてテスト依存症に

to_sとかでコンバージョンさせているものの、①②とかでどっかでエラーになるんじゃないかとか思い始めると、nilとかto_sがなさそうなクラスとかを引数にするテストとかをわざわざ書きたくなってしまって、開発効率がめっちゃ落ちてる気がする。

「そんな自分を変えたいと思って、この記事を書きました」

一個一個不安を自分で回答していってみようと思う。

①to_s理解できないパターンがある

①は、to_s理解できない時点でno method errorになるはずなんで、静的な言語のコンパイルの時のエラーが実行時に出てきたと考えれば静的な言語と同じこと

②to_sで変換した結果が、何になるのかよくわからない(そのオブジェクトに任されている)

②は、コンパイラチェックでも型のチェックはしている一方で、その内容がなにかまではチェックしてないので、静的な言語と同じこと

メソッド使う側として考えると、そのメソッドの中でちゃんとコンバージョンメソッドとかでチェックしてくれているか不安

人を信じましょう。なんなら、あらかじめメソッド利用側でコンバージョンメソッド呼んどけば

nilに意味を持たせたい場合でも、nil.to_sすると""になってしまうので、正常なstringを受け取っているケースと同じフローにのってしまう

to_sの前にnil?で判定すればOK。コンバージョンメソッド使うとなると、「せっかくどんなクラスでも魔法のように意図したクラスに変換できるんだから、その引数が関わるフローも魔法のように画一であるべきだ」みたいな欲がでるんだけど、そもそも意味を持たせたいという意図がはっきりしてるんならシンプルに分岐させればOKだと思う。

nilに意味を持たせたいケースなんぞあるかというと、hashをメソッドの引数にとったりすると、「特定のkeyがない」という状況を判定したいケースがある。

引数でユーザIDを渡された場合には、ユーザに紐付いたギャグを取得し、ユーザIDがないか、指定されたユーザIDでギャグが見つからない場合にはランダムにギャグを返すというActiveRecordモデルのメソッドを考える。

class Gag < ActiveRecord::Base
  belongs_to :user
  def self.get_suitable_one(args=Hash.new)
#  self.with_user_id(args[:user_id].to_s)
    return self.random_scope if args[:user_id].nil? || self.with_user_id(args[:user_id]).empty?
    self.with_user_id(args[:user_id].to_s)
  end
end

to_sしておけばstringを渡せるので、とにかくto_sしてActiveRecordにあとは任せればいっか!的なノリになるんだけど、(そして多分空のstring渡してもいい感じに意図通りにしてくれそうだけど)、サードパーティコードが想定してるかどうか微妙なパターンを作ってしまうのは不安・・・というケースで、to_sする前に意味を持ったnilを調べてやれば不安解消になる。

Dependency Injectionが良い理由

Dependency Injection(DI)というオブジェクト指向言語のコーディングテクニックがある。

あるクラスAの中で他のクラスBのインスタンスを生成するのをやめて、メソッドの引数とか、Aの初期化時に引数としてインスタンスを渡そうというテクニック。

良いところ1:変更に強い

PersonalComputerというクラスを作るとして、osのバージョンがわかるos_versionというメソッドを作るとすると

def PersonalComputer
  def os_version
    Windows.new("9").version
  end
end

PersonalComputer.new.os_version
# => "version9"

となる。 これはDependency Injectionしてない例。

ここで、PCにインストールしているOSを変えたくなったとして、変えようと思っても、このPersonalComputerクラスはWindowsクラスと完全にくっついてる(依存している)ので、PersonalComputerを変更しないと変えることができない。

def PersonalComputer
  def os_version
    Ubuntu.new("15").version
  end
end

PersonalComputer.new.os_version
# => "version15"

一方でDependency Injectionしている例をあげる。

def PersonalComputer
  def initialize(os)
    @os = os
  end
  
  def os_version
    @os.version
  end
end

PersonalComputer.new(Windows.new("9")).os_version
# => "version9"
PersonalComputer.new(Ubuntu.new("15")).os_version
# => "version15"

こちらはPersonalComputerクラスを変更することなくOSだけ挿げ替えができているので、1つ目のメリットとして変更に強いというのがあげられる。

良いところ2:テストしやすい

このos_versionメソッドをテストすることを考えると DIなしの方では

def PersonalComputer
  def os_version
    v = Windows.new(version:"9", user:"John", time:"15:00:00",...).version #めんどくさい
  end
end

実はWindowsクラスの初期化が超めんどくさかったとして、PersonalComputerクラスのテストをしているはずなのに、Windowsクラスの初期化に作業時間をとられるという非効率が発生するし、テスト失敗時にPersonalComputerかWindowsかどちらが悪かったのか判断しづらい。 一方でDI版では

os = double("windows", :version => "9")
PersonalComputer.new(os).os_version

のようにスタブを使って、このテストで必要になるversionメソッドだけ設定してやった上でテストすれば、無駄な時間は取られないし、純粋なos_versionメソッドのテストになる。 よって2つ目のメリットとしてテストのしやすさがあげられる。

良いところ3:名前

Dependency Injection、略すとDIという名前がかっこいい。

私はDependency Injection(DI)を使っている・・・。
○○さん、今回はDIで解決しておきました。

よって3つ目のメリットとして名前のかっこよさがあげられる。

総じて言うといい感じ。

参考: www.amazon.co.jp

エンジニアの判断責任

エンジニアの特性としてなのか、僕個人の特性なのか不明だけど、非技術者から技術的内容を質問されると、できるだけ嘘のないように、正確に答えようとする意識が働く。

アプリの作り方が二通りあって、作業のコストとか作りのきれいさを考えると現実的に作り方Aを採用するしかないような時も、

「AとBどっちがいいと思う?」

「技術的にはどっちも可能なんですが、AよりはBは技術的コストが高くなります。」

ABを時間をかけて検討するとか、余裕がある会社ならいいけど、例えばスタートアップ企業では1つの判断にそんなに時間かけてる余裕ないし、明らかに自分よりも技術について確かな判断ができる人間が会社内部にいない場合には、もっと断定的に、エンジニアの判断としてAを猛烈にプッシュするとか、強制的に採用するよう仕向けるとか、っていうのが責任になってくるんじゃないかなとスタートアップで生活してみておもう。

正確に伝える裏には責任取れないので判断は任せますという意識もあったりするし、あとは自分が判断していいんものかという謙遜の部分もあったりするけど、フラットに見て自分より技術詳しい人間いないなら、形だけ任せます形式とっても実質的に責任は技術者である自分にあると考えるべきだろう。

 

大体、非技術者に技術的コストが高くなりますとか言ったって感覚として理解できるわけないので、

「AとBどっちがいいと思う?」

「A一択です。馬鹿なこと言ってないで他の重要なこと決めましょう」

という不遜な態度こそが、事業全体を救うことになるよ、きっと。