まっしろけっけ

めもてきなやーつ

GW にやったことをメモ

はじめに

だいたい大型連休は "どこか出かけるか〜?" となった後に "連休で混んでるときにわざわざ出かける意味とは...?" となって結局出かけないというとこに着地する。

で、何をやっているか?というと下記な感じになる。

  • ジム行く
  • 可愛い愛犬の散歩行く
  • 可愛い愛犬とゴロゴロする
  • 技術書読む
  • ゲームをする

だいたい技術書読んで飽きたらゲームして疲れたら技術書読んで〜をループして合間に他のことをする感じで生活していた

今回の GW にどんな技術書を呼んだか?をメモっておくだけの記事

読んだ技術書

プログラマのためのDocker教科書 第2版

プログラマのためのDocker教科書 第2版 インフラの基礎知識&コードによる環境構築の自動化

プログラマのためのDocker教科書 第2版 インフラの基礎知識&コードによる環境構築の自動化

GW 前半に読んだのがコレ

完全に雰囲気で Docker を使っていたので、ちゃんと学んでみるか〜となって読んで見た。
読んでみたのだが雰囲気で使ってたことがほぼ当たってたのでfmfmと確認になったという結果になったが自分の使い方が間違ってなかったのだなぁとなってよかった。

最後に kubernetes のざっくりとした説明が載っていてそこはこのあと読んだ本のこともあり為になった。
Docker 良くわからんという人にはとてもオススメだと思う。

入門 Kubernetes

入門 Kubernetes

入門 Kubernetes

GW 後半に読んだのがコレ

最近話題の k8s についてマイクロサービスとの相性も良いとのことなのと社でも k8s 使って何かが行われようとしているっぽい雰囲気なので触っておこうという感じで読んだ。

minikube を使って環境作ってひたすら yaml を書き kubectl を駆使してアレコレした。
ちょうど今マイクロサービス化を進める上でめんどくさいなぁと感じていた部分の大半が k8s 使えばいい感じになるんじゃね?という感じに思えてきたので、早く導入したい!!1という気持ちになった。
書の中には若干不親切では?という箇所がいくつかあったのが気になったが k8s を理解するにはとても良いのでは?

エンジニアリング組織論への招待

入門 Kubernetes 読み終わった後に読み始めた。

なんかいいらしいとの噂を聞いていたので買っていたけど積んでたやつ

まだ読了してないので感想は特になしという感じ

最後に

今回は珍しく映画でも見に行くか〜と予約せずに渋谷に TOHO まで歩いたもののダルいなってなって昼飯だけ食べて帰ってくるという事件があった。

Rails Developers Meetup 2018: Day 2 で minne での CM 対応でのハイブリッドクラウド運用という話をした。 #railsdm

はじめに

Rails Developers Meetup 2018 こちらのイベントの登壇のお誘いが @kenchan があり話すということが決まったのが昨年の末とかだった記憶

話す内容を考えていたのだけれど、Elasticsearch 周りの話とかオンラインで全テーブルの DB の文字コードを変えた話とかその他色々考えたんだけど、被りそうなのと 30 分枠だと長すぎるか〜という感じだったのであまり聞いたことがなくちょうど年末くらいにやっていたハイブリッドクラウドを絡めた CM 対応の話をするか〜となった。

資料はこちら

speakerdeck.com

Rails のコード 3 行しかないので Rails Developers Meetup #とは となるような内容で、自分が主に担当した部分を多めに事例を紹介しました。
もう少し流れがスムーズになるように資料作れればよかったな〜というのと途中にちょっとしたトラブルがありアレだったという反省

AWS + Nyah 環境というのは社内でも珍しい構成でツラみとかもあるので気になる人はなんか聞いてください。

最後に

次喋る機会があるならちゃんと Rails の話をしたいね!

資料には含めなかったアレです。
pepabo.com

GraphQL の spec に関してアレコレ考えている

はじめに

最近 GraphQL を本格的に使い始めるぞいとなってんですよ。
経緯は下記参照

それでいくつか filed とか定義してデータ取れるようにしつつ rspec で test 書いててう〜むとなったので自分の考えをまとめて世の知見を知りたいとなったという経緯

前提

環境はこんな感じ

  • Rails なアプリケーション
  • graphql-ruby
  • graphql-batch
  • graphql-guard

その1 request spec は書く必要ないのでは?

実際に呼ばれるのは HogeSchema.execute なので request spec では GraphQL API に対して execute に渡される引数周りのチェックをすれば良いと考えた。
expect(HogeSchema).to receive(:execute).with(hoge, hoge, hoge) くらいを引数のパターン分あると良さそう。

何故ならば実際のクエリ書いてテストしていくとネストの深い巨大なクエリが出来上がって、
そのクエリの検証も辛いしレスポンスの json の検証もネストが深くなり辛いということになると考えたから。

実際に 4 種類ほどのデータ取れるようにしただけで辛いなって気持ちになったのでこの考えに至った。
ではどういうことをテストすれば安心を得られるだろうか?と考えた結果がその 2,3 になる

その2 GraphQL::ObjectType 毎に spec を書いていく

GraphQL で大事なのはそれぞれの GraphQL::ObjectType で定義された各スキーマ要素をチェックしていくと良さそう

どんな field が定義されているか?をチェックする。
PostType = GraphQL::ObjectType.define do
  name "Post"
  description "A blog post"
  field :id, !types.ID
  field :title, !types.String
  field :body, !types.String do
    guard ->(obj, args, ctx) { ... }
    resolve ->(obj, args, ctx) { ... }
  end
end

上記のような PostType があった場合は下記のようにチェックする。

describe PostType do
  subject { described_class.fields.keys }
  it { is_expected.to eq(["id", "title", "body"])
end

この他にも field 毎の型定義も想定した通りになっているかチェックしておきたい。
Ruby とかやってると型とかそんなに気にしないかもしれないけれど静的型付け言語がこの API を使うとなった場合はいい加減な型定義は出来ないのでちゃんとしておきたいという気持ち。

しかし一個一個書いていくのもダルいなと感じ他ので db のカラムのデータ型がそのまま field になっている場合が多いと考えるとある程度自動でチェックする仕組みは用意できそうなのでは?と考えたのでちょっとゴニョゴニョしようかなと考えている
db 以外の field はちゃんと個別チェックする。

graphql-guard の設定が正しいかをチェックする

これは README に書いてあるのだけれど下記のように呼び出せるので返り値を適切にチェックすると良さそう。

describe PostType do
  it do
    body = PostType.field_with_guard('body')
    expect(body.guard(obj, args, ctx)).to be_truthy
  end
resolve もチェックする

これも上記と同じようにチェックできる

describe PostType do
  it do
    expect(PostType.fields["body_url"].resolve(obj, args, ctx)).to eq("...")
  end

しかしこの方法だと resolve 内で graphql-batch の loader を使ってる場合にエラーが出るんでどうしたら...というのが悩み

その3 実際のクエリ書いてチェックする必要ってある?

その1 とも被る内容なのですが、その2 の spec をしっかり書いていれば schema の定義はしっかりと出来ているわけでその状態でデータ上手く取れない〜となった場合はクエリが悪いのだろうということになると考えられるからです。

それでも心配なら全スキーマを取ってくるクエリ書いてエラーでないねっていう程度はいいかもしれん(けど、factory bot でデータを用意するのすらだるい)

でも個別(field 毎)のエラーのチェックはクエリ投げないとダメなんかな〜という感じだが現状エラーが出るようなパターンを書いてないのでなんとも言えない

最後に

まだ本格的に使い始めて3,4日程度なのと通常の query のみで mutation に関しては全くという感じなので上に書いた考えは変わってくるかもしれない。

ペパボ社内で他に GraphQL を使っているサービスがあったので、どういう方針なんって聞きにいくついでに今の考えまとめようと思って雑に gist 書き出したらこういう考えに至った。
でそれを共有したらこんなこと考えてたんですよ〜とやまちゃんがナイスなブログを書いてくれて優秀な若者便利!!1となった

blog.kymmt.com

続:社内のテックミーティングでマイクロサービスの基本的なことについて喋った

はじめに

下記の記事で基本的なことを非エンジニアにもわかりやすく喋った。
で、次は下記の資料の課題をどうやって技術的に解決していくの?という部分を説明しなければいけなかったのでサラッと資料で説明したという経緯

shiro-16.hatenablog.com
× モノシリック
◯ モノリシック

資料

speakerdeck.com

具体的にこれ使うというのは記述してるけどその説明は口頭で説明したり、参考のページを紹介したりなどした。
あとは実際に導入するときに pull request にアレコレ書くのでそんな感じにした。

最後に

喋ったのは一週間くらい前なのだけれど slideshare に uploade できないマンと化してしまって...となっていたのでアップが遅れた。
もう speakerdeck にしよと思い立ったのでピッとアップしたという感じ

社内のテックミーティングでマイクロサービスの基本的なことについて喋った

はじめに

定期的に(?)開催されている minne のテックミーティングでマイクロサービ化を進める上で基本的なことをエンジニア以外の人にも知っておいて欲しかったので喋った。

資料

www.slideshare.net


基本的なことをエンジニア以外の人にも知っておいて欲しかったという前提があるので技術的に具体的にこういう風に〜というのは話さず概要をわかりやすく説明したつもり。

最後に

LT 5 分!という感じだったけど絶対終わらないんでという前置きをして 15 分喋った。
今回喋った内容はマイクロサービスアーキテクチャに載っている内容がほとんどです。

rspec-mail_matcher という gem を作った

経緯

仕事で開発している minne というサービスの Rails の version を 5.1.3 から 5.1.4 にあげようと雑に bundle update rails して見たら CI が通らんぞってなっていろいろ調べて行った結果。

CI が通らなくなった箇所

mailer の spec が落ちるようになっていた。具体的な内容はこんな感じ

it '本文に url が含まれること' do
  url = %r{http://example.com/?test1=1\&test2=2}
  expect(mail).to have_body_text(/#{url}/)
end

CI の結果を見ると本文に含まれる URL が http://example.com/test1=1test2=2 と & が消えた状態になってしまっていた(& が消えてることに気づかずにあってるやん!なんで!と二時間ほどハマったのは内緒)

原因の調査その 1

have_body_text という matcher は email-spec という gem で実装されている。

で email-spec を読むと #default_part_body という method から取得した文字列をメールの本文として扱っているので #default_part_body の結果が期待するものかとりあえず調べる。

it '本文に url が含まれること' do
  url = %r{http://example.com/?test1=1\&test2=2}
  binding.pry
  expect(mail).to have_body_text(/#{url}/)
end

spec を実行して止まったところで mail.default_part_body とやると確かに & が消えた状態になっている。


なるほどでは mail.html_part.body では?ということで実行して見ると & は & となっていて消えてはいない。なるほど〜 ここ の HTMLEntities の処理がおかしいっぽいぞというのがわかった

原因の調査その 2

その 1 で htmlentities がアレっぽいというのがわかったので処理を追っていくと ここ の処理で HTML 関連の文字列置き換えをしているぞというのがわかる。

prepare(source).gsub(@entity_regexp){
  binding.pry
  if $1 && codepoint = @map[$1]
    codepoint.chr(Encoding::UTF_8)
  elsif $2
    $2.to_i(10).chr(Encoding::UTF_8)
  elsif $3
    $3.to_i(16).chr(Encoding::UTF_8)
  else
    $&
  end
}

上記のように pry を追記して spec を実行して止まったところでそれぞれの違いを見て見た。

# 5.1.3
pry> prepare(source)
=> "......"
# 5.1.4
pry> prepare(source)
=> "......"

prepare(source) は差分なし

# 5.1.3
pry> $1
=> "amp"
#5.1.4
pry> $1
=> nil

なるほど〜、ということは String#gsub が ActiveSupport あたりで override されてるのか?という考えにたどり着く。
上記の pry 時に caller を実行すると activesupport/lib/active_support/core_ext/string/output_safety.rb が 5.1.4 だと追加されていることがわかる。
確証を得たいので下記で確認。

# 5.1.3
pry> prepare(source).class
=> String
pry> prepare(source).class.ancestors
=> [ActiveSupport::ToJsonWithActiveSupportEncoder,
 String,


#5.1.4
pry> prepare(source).class
=> String
pry> prepare(source).class.ancestors
=> [ActiveSupport::SafeBuffer,
 ActiveSupport::ToJsonWithActiveSupportEncoder,
 String,.....

なるほどね。ということで ActiveSupport::SafeBuffe が追加されたことで htmlentities の処理がうまく動かなくなってしまったことが原因だということが判明。

解決方法を考える

email-spec or htmlentities に PR 投げるか〜というのを最初考えてリポジトリ見たら 3 years ago とかの文字が並んでてなるほど...という感じになった。
それなら自分で作るか〜という気持ちになって今回作ったのでした。

email-spec との差

minne の spec を書き換えるのはダルいと思ったので基本的に matcher の method 名は email-spec に合わせる方向で設計したのだけれど、email-spec の default_part_body に関しては納得いかなくて html_part, text_part の順で取得されるのでこの仕様を知らないと have_body_text で text mail の本文をチェックしているはずなのに html mail の本文チェックしてて同じ記述があるから CI 通った(逆も然り)ということが起きそうなのがダメっぽいな〜という感じになった。

そこで have_body_text で text mail の body を have_body_html で html mail の body をチェックする matcher を定義して実装した。
しかし、現状の v0.1.2 だと 3,4 時間で雑に作ったので multipart? が false の際は雑に #body を使ってしまっているので次の version ではそこら辺の metcher を用意するか have_body_ でそれぞれチェックするかの処理を実装する予定

github.com

最後に

5.1.3 と 5.1.4 のソースの差分 ActiveSupport っぽいなというのはわかっていたけれど、どこがどう問題になっているのか調べて見るか〜と言った感じ。
だらだらと調査内容から書いたのはこのブログを PR に貼り付けて説明いらずにするためです。

Rails + GraphQL で REST じゃない API を作る

はじめに

ここに書いている内容は僕が仕事で開発を行なっている minneAPI に GraphQL を導入するにあたり
gist に雑にまとめてメンバーに共有した内容で公開できない部分をアレしたやつです。
(minne の API は現状オープンなものではないです。

GraphQL #とは

メリット

  • クライアントが必要とするデータを 1 回のリクエストで取得できるようになる
    • REST に近い API だとモバイルアプリで 1 画面を表示するために複数回のリクエストを投げる必要がある
  • どのデータが必要なのかは投げられたクエリから判断するので無駄なデータを返す必要がなくなる
    • 古い version で使っているので消せない json の key どうする?とか悩まなくていい
    • 無駄なデータを返す必要がないのでほんの少しだけ通信コストを抑えられる(高速化)
    • 無駄な JSON を parse する必要がなくなる(高速化)
  • どのデータが必要なのかは投げられたクエリから判断するので大きなロジックの変化がない場合は server 側の実装が不要
    • 投げたクエリによって返すデータが変わるだけなので、デザインの変更でこのテーブルのこのカラムの情報が必要ってなった場合もアプリ側でクエリを修正するだけ
  • クエリには型が存在する
    • ruby では厳密に型を気にすることはない
    • しかしアプリで使用している言語は静的型付けである為、型は重要
  • graphql-ruby ではカーソル形式のページネーションをデフォで提供してくれている

デメリット

  • server 側の実装はそれなりに大変
    • クエリを覚えたり、 N+1 対応したり
      • 補助するための GraphiQL が優秀なのである程度はツールに頼れる
      • N+1 に関しては graphql-batch gem を使うとスマートに解決できそう
  • クライアント側で、何のデータが必要なのかを「すべて」明示する必要がある
    • コードを書く量が増える
    • クエリを覚えなければならない
      • どのデータを使用しているか明示されるため、後で見た時にわかりやすいかもしれない

実際に使ってみた

使う model は下記 (実際は login_id, password とかもあるけど割愛

今回は users#show products#index 相当の処理を実装する

$ rails g model User name:string
$ rails g model Product user_id:integer  name:string 
$ rails g model Love user_id:integer  product_id:integer 

model 周りの実装も割愛

初期設定

# Gemfile
+gem 'graphql'
+gem 'graphql-batch'

Gemfile に常軌を追加して bundle install する
その後諸々の初期ファイルを作ってくれる下記を実行

$ bundle exec rails generate graphql:install
Running via Spring preloader in process 58662
      create  app/graphql/types
      create  app/graphql/types/.keep
      create  app/graphql/ponica_schema.rb
      create  app/graphql/types/query_type.rb
add_root_type  query
      create  app/graphql/mutations
      create  app/graphql/mutations/.keep
      create  app/graphql/types/mutation_type.rb
add_root_type  mutation
      create  app/controllers/graphql_controller.rb
       route  post "/graphql", to: "graphql#execute"
     gemfile  graphiql-rails
       route  graphiql-rails

Gemfile に graphiql-rails が追加されたり routes.rb に graphql の endpoint や graphiql-rails の mount が追加されたりしている。

graphiql-rails の認証周りの設定

graphiql-rails から graphql の API を呼ぶ際にヘッダーにデフォで○○設定したい〜とかいう場合は下記のように initializer で設定する

# config/initializers/graphiql.rb
GraphiQL::Rails.config.headers["X-Hoge"] = -> (context) { "Hoge" }

Github みたいに v4 にしてみる

まずは routes.rb 編集

# routes.rb
  scope 'v4', module: 'v4' do
    post "/graphql", to: "graphql#execute"
  end

controller を移動
mv app/controllers/graphql_controller.rb app/controllers/v4/

# app/controllers/v4/graphql_controller.rb
- class GraphqlController < ApplicationController
+ class V4::GraphqlController < ApplicationController
...

graphiql で使う path も変更

-    mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql"
+    mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/v4/graphql"

この状態で rails s で server を立ち上げる。

http://localhost:3000/graphiql にアクセスすると GraphiQL の画面が表示される

# request
query {
  testField
}

# response
{
  "data": {
    "testField": "Hello World!"
  }
}

みんな大好き Hello World が出来た

認証周り

ログイン認証とかは今あるものをそのまま使えば ok で GraphQL の処理の中で current_user を使えるように context に格納しておく

# app/controllers/v4/graphql_controller.rb
     context = {
-      # Query context goes here, for example:
-      # current_user: current_user,
+      current_user: current_user
     }

schema 設定

初期設定 の時に app/graphql 以下に諸々のファイルが生成されるのでそこら辺をいじる

app/graphql/ponica_schema.rb というのができているので ponica 部分をいい感じの名前に変える
mv app/graphql/ponica_schema.rb app/graphql/minne_schema.rb とか、で下記のように編集する

# app/graphql/minne_schema.rb
-PonicaSchema = GraphQL::Schema.define do
+ MinneSchema = GraphQL::Schema.define do

# app/controllers/v4/graphql_controller.rb
-    result = PonicaSchema.execute(query, variables: variables, context: context, operation_name: operation_name)
+    result = MinneSchema.execute(query, variables: variables, context: context, operation_name: operation_name)

type を定義していく

順番的には type を定義した後に graphql-batch の処理を実装した方がわかりやすいと思いますが長くなるのでまとめてます。

# app/graphql/minne_schema.rb
MinneSchema = GraphQL::Schema.define do
+ use GraphQL::Batch
# app/graphql/types/user_type.rb
Types::UserType = GraphQL::ObjectType.define do
  name "User"
  global_id_field :id

  field :id, !types.ID
  field :name, !types.String
  connection :products, Types::ProductType.connection_type # 先で述べたページネーションを実装してくれる
end

# app/graphql/types/product_type.rb
Types::ProductType = GraphQL::ObjectType.define do
  name "Product"
  global_id_field :id

  field :id, !types.ID
  field :name, !types.String

  field :user, !Types::UserType do
    resolve -> (obj, args, context) { RecordLoader.for(User).load(obj.user_id) } # N+1 解消用の処理
  end

  field :loved, !types.Boolean do
    resolve ->(obj, args, ctx) {
      LovedLoader.for(ctx[:current_user]).load(obj.id) # いいねしたかどうかをまとめて判定
    }
  end
end

# app/graphql/record_loader.rb
class RecordLoader < GraphQL::Batch::Loader
  def initialize(model)
    @model = model
  end

  def perform(ids)
    @model.where(id: ids).each { |record| fulfill(record.id, record) }
    ids.each { |id| fulfill(id, nil) unless fulfilled?(id) }
  end
end

# app/graphql/loved_loader.rb
class LovedLoader < GraphQL::Batch::Loader
  def initialize(user)
    @user = user
  end

  def perform(ids)
    love_product_ids = @user.loves.where(product_id: ids).pluck(:product_id)
    ids.each { |id| fulfill(id, love_product_ids.include?(id)) }
  end
end

コメントで書いた通り graphql-ruby はページネーション用の処理も簡単に実装してくれる。

また graphql-batch を使えば N+1 の問題も解消可能

loved に関しても 1 レコード毎いいねをしたかを判定するのではなくまとめて判定している

query を定義する

 Types::QueryType = GraphQL::ObjectType.define do
   name "Query"
-  # Add root-level fields here.
-  # They will be entry points for queries on your schema.

-  # TODO: remove me
-  field :testField, types.String do
-    description "An example field added by the generator"
+  field :user do
+    type Types::UserType
+
+    argument :id, !types.String
+
+    description "Find a User By account"
+    resolve ->(obj, args, ctx) {
+      User.find(args[:id])
+    }
+  end
+
+  connection :products, Types::ProductType.connection_type do
+    description "all Product"
     resolve ->(obj, args, ctx) {
-      "Hello World!"
+      Product.all  # 雑に all とかしてるので実際はごにょごにょする必要があります
     }
   end
 end

実際に使ってみる

直接 id を指定してもいいが variables を使うこともできる

__typename とかで type を見たり__hogeで色々見れる

# users#show っぽいやつ
# request
query GetUser($id: String!) { 
  user(id: $id) { 
    __typename
    name
    products(first: 2, after: "Mg==") { # first が件数 after がカーソル
      edges {
      	node {
          id
          name
        }
      }
      pageInfo {
        startCursor
        endCursor
      }
    }
  }
}

# variables
{
  "id": "2"
}

# response
{
  "data": {
    "user": {
      "__typename": "User",
      "name": "test user",
      "products": {
        "edges": [
          {
            "node": {
              "id": "25",
              "name": "hoge"
            }
          },
          {
            "node": {
              "id": "24",
              "name": "fuga"
            }
          }
        ],
        "pageInfo": {
          "startCursor": "MQ==",
          "endCursor": "Mg=="
        }
      }
    }
  }
}
# products#index っぽいやつ
# request
query {
  products(first: 2) { 
    edges {
    	node {
        id
        name
        loved
        user {
          name
        }
      }
    }
    pageInfo {
      startCursor
      endCursor
    }
  }
}

# response
{
  "data": {
    "products": {
      "edges": [
        {
          "node": {
            "id": "1",
            "name": "hoge",
            "loved": true,
            "user": {
              "name": "hoge user"
            }
          }
        },
        {
          "node": {
            "id": "2",
            "name": "fuga",
            "loved": false,
            "user": {
              "name": "fuga user"
            }
          }
        }
      ],
      "pageInfo": {
        "startCursor": "MQ==",
        "endCursor": "Mg=="
      }
    }
  }
}

わかりやすいようにバラバラで呼んでますがクエリを変更すれば上記の 2 つを一回のリクエストで取得することが可能です。
Rails のログを見ればわかりますが N+1 も発生していません。

mutation に関して

mutation に関しては REST から移行するメリットが見出せていないという感じが今のステータスです。
なので処理を記述するのは割愛(あとで別記事として書くかも

エラーハンドリング周り等は下記あたりが参考になりました。
GraphQL -Mutation Query Implementation - Ruby on Rails - Rails Kitchen
GraphQL Ruby Error Handling - Rails Kitchen

最後に

まだ query をどう定義するのがベストなのか? + cache 周りの検証が出来ていないのでその辺り頑張る + mutation メリットが見出せてないのでそこら辺知見がある人と話してみたい。 REST に引っ張られがちになるので考え方を変えるのが大変だったりする。

個人的には GraphQL すごくいいなと感じていて、graphql-ruby, graphql-batch, graphiql-rails もすごく良く出来ているという感想なんで導入進めるぞという気持ち

下記のページを参考にしました。(英語のページを見ながら実装したんでなんか間違ってたら指摘を〜

GraphQL | A query language for your API
GraphQL - Welcome

書いてて疲れたんで最後雑になっちゃったな