新規プロジェクトでGraphQL-Rubyを利用する際に気をつけること

GraphQL-RubyでAPI開発する際に、以下のように--apiオプションをつけたくなる。

rails new iikannzino_service --api

そうすると、rails generate graphql:installとした時にgraphiql-railsがinstallされなくなってしまう。

rails g graphql:install --no-apigraphiql-railsを強制installすることは可能だが、アセットパイプラインの設定を自分で行う必要があるので面倒だ。

よって、無難にrails new iikannzino_serviceとするのが良いと思う。

Feature/better api only support by ThisIsMissEm · Pull Request #772 · rmosolgo/graphql-ruby · GitHub

初めてのGraphQL ―Webサービスを作って学ぶ新世代API

初めてのGraphQL ―Webサービスを作って学ぶ新世代API

  • 作者:Eve Porcello,Alex Banks
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2019/11/13
  • メディア: 単行本(ソフトカバー)

「Design It!」は、人生に影響を与える本になりそうです

設計から携われるタスクがあり希望の上、チーム異動しました。どのように設計を行うか、何を考慮する必要があるのか、どのように関係者(ステークホルダー)に説明するかなど、ありとあらゆることが手探り状態です。「Design It!」を読んだことで具体的なアクションまで落とし込めたような気がします。あとは、歯を食いしばってやるだけです。

Design It! ―プログラマーのためのアーキテクティング入門

Design It! ―プログラマーのためのアーキテクティング入門

  • 作者:Michael Keeling
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2019/11/25
  • メディア: 単行本(ソフトカバー)

概要

本書は、3部構成でした。目次は以下の通りです。

アーキテクトの道具箱は、実践で困った時に辞書的に使う時に読むものだと思います。演習問題で本当に使える知識なっているかを確認することができます。一人でやっても効果が薄いと思ったので、時間があるときに勉強会を開いてワイワイしながら解きたいと思える内容でした。

  • 第I部 ソフトウェアアーキテクチャ入門

    • 1章 ソフトウェアアーキテクトになる
    • 2章 デザイン思考の基礎
  • 第II部 アーキテクチャ設計の基礎

    • 3章 デザイン戦略を立てる
    • 4章 ステークホルダーに共感する
    • 5章 アーキテクチャ上重要な要求を掘り下げる
    • 6章 アーキテクチャを選ぶ(君がアーキテクチャに選ばれる前に)
    • 7章 パターンで土台を作る
    • 8章 意味のあるモデルで複雑さを扱う
    • 9章 アーキテクチャデザインスタジオを開く
    • 10章 設計判断を可視化する
    • 11章 アーキテクチャを記述する
    • 12章 アーキテクチャに通知表をつける
    • 13章 チームのアーキテクト力を強める
  • 第III部 アーキテクトの道具箱

    • 14章 問題理解のアクティビティ
    • 15章 潜在的な解決策を探るアクティビティ
    • 16章 設計をタンジブルにするアクティビティ
    • 17章 設計の選択肢を評価するアクティビティ

以下、書籍を読みながら取ったメモを残しておこうと思います。

アーキテクチャ上重要な要求を掘り下げる

  • アーキテクチャ上重要な要求(ASR)
    • 制約
      • 一旦決定してしまうと交渉不可能となるので、主導権を握っておきたい
    • 品質特性
      • 外部から観測可能
    • 影響を与える機能要求
    • その他の影響を及ぼすもの

制約には、「与えられた制約」とシステムが成長するに伴って増える技術的負債のような「与えられた制約」がある。

ASRを見つけるために、ステークホルダーが何に気をかけているのかを問いづづける。 2人の人物が品質特性シナリオ読んだ時、システムについて同じ理解をしなければならない。

アーキテクチャを選ぶ(君がアーキテクチャに選ばれる前に)

ソフトウェアアーキテクチャを設計するということは、不確実性のもとで決定を下すということ。

ソフトウェアアーキテクチャを選択する際には、ステークホルダーが判断しやすいようにトレードオフ分析できる意思決定マトリクスを作成する。

パターンで土台を作る

  • レイヤー
    • 機能的にまとまったモジュールのグループ
    • 上位層は下位層の利用が許可されているが、その逆は成り立たない
  • ポートとアダプタ
    • 核となるビジネスロジックを分離する
  • パイプとフィルタ
    • フィルタと呼ばれるコンポーネント群が一つの変換またはデータ操作を担当する
  • サービス指向アーキテクチャ
    • 独立したコンポーネントが特定の機能を提供するサービスとして実装されている
  • Publish-Subscribe
    • プロデューサーとコンシューマは独立して存在し、互いに認識しない
  • 共有データ
    • 複数のコンポーネントで共通のデータストアを介してデータにアクセスするパターン
  • 多層
    • 実行時の構造を論理的なグループにまとめる。設計時は、レイヤー。

意味のあるモデルで複雑さを扱う

物に名前を付けるには段階がある。(DDDのユビキタス言語を目指す)

モデルは、コードから分離されているという欠点を持つ。

アーキテクチャを記述する

  • 考えを明確にする
    • 自分の知識と向き合い、分かっていること、分かっていると思っていること、そして分からないことを明らかにする

アーキテクチャに通知表をつける

アーキテクティングの最終的な成果はビジネスおよびステークホルダーの目標を満たすシステム設計して実装すること。

まとめ

  • 「Design It!」を読んで学んだことをアウトプットした
  • 時間ができた時に、勉強会開きたいので一緒にやる人募集!

Design It! ―プログラマーのためのアーキテクティング入門

Design It! ―プログラマーのためのアーキテクティング入門

  • 作者:Michael Keeling
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2019/11/25
  • メディア: 単行本(ソフトカバー)

Best Practice Ruby on Rails Refactoringは、Rails開発を初めて半年のエンジニアにピッタリの本だった

Rails AntiPatterns: Best Practice Ruby on Rails Refactoring (Addison-Wesley Professional Ruby Series) (English Edition)

Rails AntiPatterns: Best Practice Ruby on Rails Refactoring (Addison-Wesley Professional Ruby Series) (English Edition)

  • 作者:Chad Pytel,Tammer Saleh
  • 出版社/メーカー: Addison-Wesley Professional
  • 発売日: 2010/11/09
  • メディア: Kindle版

Best Practice Ruby on Rails Refactoringのモデル章を読ました。

本書は、Railsガイドの知識を読んだが実践でどうもいいコードにならないと思っているエンジニアにオススメできます。

学んだことを使える知識にしたいと思ったので言語化することにします。

デメテルの法則

デメテルの法則は、モジュール間の結合度を下げる手法で最小知識の原則とも呼ばれいます。基準となるオブジェクトは、隣接するオブジェクトの情報のみを知っているべきであるということです。Rubyの場合、オブジェクト間の通信において.の数が一つだけになるようにすればいいです。

書籍では、以下のコードをリファクタすることでデメテルの法則が紹介されていました。

class Customer < ActiveRecord::Base
  has_many :invoices
end

class Invoice < ActiveRecord::Base
  belong_to :customer
end

InvoiceクラスからCustomerクラスの情報を取得する場合、以下のようになります。

@invoice.customer.name

これをデメテルの法則に従うようにリファクタリングすると、@invoice.customer_name となります。

class Customer < ActiveRecord::Base
  has_many :invoices
end

class Invoice < ActiveRecord::Base
  belong_to :customer
  
  def customer_name
    customer.name
  end
end

しかし、この方法には欠点があります。取得した属性値に対して線形的にメソッドが増加するためModelが肥大化してしまいます。

class Invoice < ActiveRecord::Base
  belong_to :customer
  
  def customer_name
    customer.name
  end
  
  def customer_street
    customer.street
  end
  
  def customer_city
    customer.city
  end
end

この肥大化は、delegateメソッドで解消できます。

class Invoice < ActiveRecord::Base
  belong_to :customer

  delegate :name,
           :street,
           :city,
           to: :customer,
           prefix: :customer
  end
end

Fat Model

適切な責務が表現された状態で、Fat Modelになるのは致し方ありません。書籍では、分割統治法のアプローチとしてModuleに切り出すことでFat Modelに混在する役割を切り分けていました。 この手法はFat Modelを解決したことにはなりませんが、そもそもFat Modelであることは問題でないと思うので個人的に良い方法であると思います。

上記は、適切な責務を実現している場合に有効です。しかし、現実のプロダクトコードでは適切な責務が表現されていないことがあります。書籍では、この時の対処法が紹介されていました。新しいクラスに責務を委譲することです。

注文クラスであるOrderモデルが例として紹介されていました。

class Order < ActiveRecord::Base
  def self.find_purchased
     ...
  end
  
  def self.find_wating_for_review
     ...
  end
  
  def to_xml
    ...
  end
  
  def to_json
     ...
  end
  
  def to_csv
     ...
  end
end

上記のコードの問題点は、単一責任の原則(SRP)に反することです。to_xmlto_json注文クラスは、どのような形式で出力するかに責務を持っています。以下のように、別クラスに切り出すことで責務を明らかにできます。

class Order < ActiveRecord::Base
  def self.find_purchased
     ...
  end
  
  def self.find_wating_for_review
     ...
  end
  
  def converter
    OrderConverter.new(self)
  end
end

class OrderConverter
  attr_reader :order
  def initialize(order)
    @order = order
  end

  def to_xml
    ...
  end
  
  def to_json
     ...
  end
  
  def to_csv
     ...
  end
end

@order.converter.to_pdfのように呼び出すことができました。

これは、デメテルの法則に違反しています。はじめに紹介した方法でどのようにリファクタできるか考えてみてください。答えは、書籍に記載されています。

Rails AntiPatterns: Best Practice Ruby on Rails Refactoring (Addison-Wesley Professional Ruby Series) (English Edition)

Rails AntiPatterns: Best Practice Ruby on Rails Refactoring (Addison-Wesley Professional Ruby Series) (English Edition)

  • 作者:Chad Pytel,Tammer Saleh
  • 出版社/メーカー: Addison-Wesley Professional
  • 発売日: 2010/11/09
  • メディア: Kindle版

Association extensions

class PetsController < ApplicationController
  def show
    @pet = Pet.find(parames[:id])
    @toys = @pet.find_cute_for_pet
  end
end

class Pet < ActiveRecord::Base
  has_many :toys

  def find_cute_for_pet
    self.where(cute: true)
  end
end

上記のようなコードがある時、has_manyの中にメソッドを押し込むことでどのように利用するかを明らかにできます。また、メソッド名もシンプルになります。

class Pet < ActiveRecord::Base
  has_many :toys do
     def cute
       self.where(cute: true)
     end
  end
end

toysに対して複数のメソッドが定義されるようになったとき、Association extensionsが有効です。

class Pet < ActiveRecord::Base
  has_many :toys, -> { extending ToyAssocationMethods }
end

module ToyAssocationMethods
  def cute
    where(cute: true)
  end
end

まとめ

Railsのモデルに関するアンチパターンを紹介しました。

しかし、安易にリファクタリングに取り掛かってはいけません。

リファクタリングを行うことで、本当に生産性が上がるのかROI(投資利益率)を意識してチームやプロダクトオーナに提案する必要があると思います。

他に興味のあるトピックのみを読んでブログにしたい。

  • Testing
  • Using Third-Party Code
  • Databases

Design Patterns in RubyでStrategyパターンを学んだ

Design Patterns in Ruby でStrategyパターンを学んだ。自分用のメモとして残しておく。

Design Patterns in Ruby (Adobe Reader) (Addison-Wesley Professional Ruby Series) (English Edition)

Design Patterns in Ruby (Adobe Reader) (Addison-Wesley Professional Ruby Series) (English Edition)

  • 作者:Russ Olsen
  • 出版社/メーカー: Addison-Wesley Professional
  • 発売日: 2007/12/10
  • メディア: Kindle版

オブジェクトによって処理は異なるがインターフェースが等しいときStrategyパターンで複雑さを取り除くことができる。

Strategyパターンと同じ問題を解決する手法としてTemplate Methodパターンがある。Template Methodは、継承で問題を解決するため親クラスの情報を引き継いでしまうという欠点がある。

Strategyパターンは、継承の代わりに委譲でアルゴリズムの複雑性を隠蔽する。

以下に簡単なStrategyパターンの例を示す。

class Payment
  def initialize(platform)
    @platform = platform
  end

  def purchase
    @platform.purchase(self)
  end
end

module Platform
  class AppStore
    TARGET_URI = "https://app.store".freeze
    def purchase(context)
      puts "Purchase Service on App Store and process complex tasks"
    end
  end
end

module Platform
  class GooglePlay
    TARGET_URI = "https://google.play".freeze
    def purchase(context)
      puts "Purchase Service on Google Play and process complex tasks"
    end
  end
end

Strategyパターンの利点と欠点

  • サブクラスでなく委譲である
  • 条件文を排除できる
  • 同じ振る舞いに対して異なる実装を提供できる
  • クライアントは、各Strategyの実装を知っている必要がある
  • Contextの情報を全く利用しないStrategyが存在する

参考文献

マンガでわかる Strategy

Rubyであるメソッドの定義場所を知る方法

OSSなど実装を見る機会が乏しいコードに触れる時、あるメソッドがどこに定義されているか継承を辿るとデバッグに時間がかかる時がある。

そんな時は、methodsource_locationメソッドを利用すると便利である。

GraphQL::Types::String.method( :graphql_definition ).source_location # GraphQL-Rubyのソースをみるとき 

改訂2版 パーフェクトRuby

改訂2版 パーフェクトRuby

  • 作者:Rubyサポーターズ
  • 出版社/メーカー: 技術評論社
  • 発売日: 2017/05/17
  • メディア: 大型本

GraphQLで独自Scalar型を定義する時に気をつけること

GraphQL-Rubyでは、IntFloatStringBoolean,IDといったビルドイン以外にISO8601DateTimeISO8601Date型が提供されています。

また、独自Scalar型を定義することができ、その方法はドキュメントに記載されています。

class Types::Url < Types::BaseScalar
  def self.coerce_input(input_value, context)
    url = URI.parse(input_value)
    if url.is_a?(URI::HTTP) || url.is_a?(URI::HTTPS)
      url
    else
      raise GraphQL::CoercionError, "#{input_value.inspect} is not a valid URL"
    end
  end

  def self.coerce_result(ruby_value, context)
    ruby_value.to_s
  end
end

上記のように独自Scalar型を定義できますが、graphql-specから一点忘れてはいけないことがあります。

それは、InputとResultでバリデーションを行い適切なエラーを出す必要があるということです。

Types::Urlでは、self.coerce_inputメソッドでGraphQL::CoercionErrorを出力していることがわかります。

また、Resultではruby_value.to_sとあり一見するとエラーを出していないように思われます。to_sメソッドは、ほぼ全てRubyオブジェクトに適応できてしまうため結果がIntegerであるパターンを考えます。

シンプルな独自ScalerであるBigIntがGraphQL-Rubyに実装されています。もし、Arrayが与えられた時、value.to_iを実行するとNoMethodErrorが出力されます。rescueなどで拾ってやる必要はありそうですが、Resultでもバリデーションをチェックすることが必要であるとわかります。

lib/graphql/types/big_int.rb

# frozen_string_literal: true

module GraphQL
  module Types
    class BigInt < GraphQL::Schema::Scalar
      description "Represents non-fractional signed whole numeric values. Since the value may exceed the size of a 32-bit integer, it's encoded as a string."

      def self.coerce_input(value, _ctx)
        Integer(value)
      rescue ArgumentError
        nil
      end

      def self.coerce_result(value, _ctx)
        value.to_i.to_s
      end
    end
  end
end

まとめ

GraphQLで独自Scalerを定義する際は、バリデーションをしてエラーを出すべきだ

初めてのGraphQL ―Webサービスを作って学ぶ新世代API

初めてのGraphQL ―Webサービスを作って学ぶ新世代API

  • 作者:Eve Porcello,Alex Banks
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2019/11/13
  • メディア: 単行本(ソフトカバー)

alert(1)では、危険性がわからなかった人にも優しいXSS解説

XSSとは、攻撃者によって意図しないHTMLやJavaScriptを挿入される脆弱性です。

XSSを説明する際に、ローカルサーバーを立ち上げた後にブラウザ上でhttp://localhost:8080/?keyword=<script>alert(1)</script>にアクセスすると警告ダイヤルが表示される例を目にすることが多いと思います。

なるほど、危なさそうだ!と思いながら具体的な攻撃手法が思い浮かばずモヤモヤしていました。本記事では、このモヤモヤを解決するため攻撃方法を紹介します。絶対に、他者のサイトで実行しないで下さい。

$ php -v
PHP 7.1.32

$ php -S localhost:8080 -t `pwd`
<?php
  setcookie("email", "example@gmail.com", time()+3600, "/", "localhost", FALSE, FALSE);
?>

<body>
  <?php echo $_GET['keyword']; ?>
</body>

setcookieで設定されたクッキーは、document.cookieで取得できます。

setcookieで設定したCookie値はdocument.cookieの最後尾にあったので、汎用性がない方法ではありますがdocument.cookie.split(" ").slice(-1)[0]で取り出せます。

ここで、http://localhost:8080/?keyword=<script>alert(1)</script>http://localhost:8080/?keyword=<script>alert(document.cookie.split(" ").slice(-1)[0])</script>に変更すると自分のCookie値が取り出せていることがわかります。

自分のCookie情報を取れたとしても、意味はありません。攻撃するのには、他者のCookie情報が必要なのです。

次に、Window.locationを紹介します。ドキュメントの中に、location = "http://www.mozilla.org";があります。これは、指定したURIに移動することできます。以下のように、実行すると当ブログのTOPページに移動することが確認できます。

http://localhost:8080/?keyword=<script>location="https://www.imaharutech.work"</script>

XSSがあるサイトでは、自サイトからlocationを利用してリダイレクトのようなことを行うことができます。

上記とdocument.cookieを組み合わせると他者のCookie値を抜き取ることができます。

つまり攻撃専用のダミーサイトを立ち上げ、srcactionが利用できるHTMLタグを設置し攻撃サイトへCookie値を含んでリダイレクトさせるのです。

対策

HTTP Only

HttpOnlyを設定すると、 JavaScriptからクッキーの読み出しを禁止することができます。

<?php
  setcookie("email", "example@gmail.com", time()+3600, "/", "localhost", FALSE, TRUE); // HttpOnly: True
?>

<body>
  <?php echo $_GET['keyword']; ?>
</body>

TRACEメソッドの禁止

Http Onlyをするだけは、TRACEメソッドとXMLHttpRequestを組み合わせた攻撃に耐えることはできません。 現在のブラウザでは、XMLHttpRequestからTRACEメソッドを実行することはできないようですがApacheを利用する場合、 TraceEnable Offの設定をしておくといいでしょう。

また、NginxはデフォルトでTRACEメソッドを許可していません。

エスケープ

二つの対策方法は、重要な情報にアクセスできないという制限をしたのみで根本的な問題解決になっていません。

XSSは、HTMLやJavaScriptを判別することなく利用していることが原因で起こってしまう脆弱性でした。

HTMLとJavaScriptをただの文字列にして解釈してやれば問題は解決します。

そのためには、HTMLやJavaScriptと解釈してしまう<などを文字実体参照に置換すればいいです。

PHPの場合、htmlspecialcharsを利用します。

エスケープとサニタイズは違うので興味がある調べてみて下さい。

まとめ

XSS対策には、特殊文字をエスケープする

参考文献

実はそんなに怖くないTRACEメソッド | 徳丸浩の日記

体系的に学ぶ 安全なWebアプリケーションの作り方 第2版 脆弱性が生まれる原理と対策の実践

体系的に学ぶ 安全なWebアプリケーションの作り方 第2版 脆弱性が生まれる原理と対策の実践

  • 作者:徳丸 浩
  • 出版社/メーカー: SBクリエイティブ
  • 発売日: 2018/06/21
  • メディア: 単行本

GraphQL-RubyでUnion型を実装する方法

GraphQLにおけるUnion型は、Object型のリストを表現します。

実装方法は、ドキュメントを参考にすると以下の通りです。

簡易的にUnionを試すためUserクラスにcommentsメソッドを実装していますが、本来はSTIやCTIなどの実装を表現する場合に利用します。それ以外で利用例があれば是非、コメントを頂けると幸いです。

query {
  user {
    nodes {
     id
     commnets {
       __typename
       ... on Post {
        color
      }
      ... on Image {
        url
      }
    }
  }
}
module Types
  class User
    field :id, ID, null: false
    field :comments, Types::CommentSubject, null: false
    
    def comments
      if [true, false].sample
        object.blog_post # BlogPost Model
      else
        object.image # Image Model
      end
    end
  end
end
module Types
  class CommentSubject < Types::BaseUnion
    description "Objects which may be commented on"
    possible_types Types::Post, Types::Image
  
    # Optional: if this method is defined, it will override `Schema.resolve_type`
    def self.resolve_type(object, context)
      if object.is_a?(BlogPost)
        Types::Post
      else
        Types::Image
      end
    end
  end
end

Union型の実装時に、possible_typesで少なくとも一つの型を宣言する必要があります。

初めてのGraphQL ―Webサービスを作って学ぶ新世代API

初めてのGraphQL ―Webサービスを作って学ぶ新世代API

  • 作者:Eve Porcello,Alex Banks
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2019/11/13
  • メディア: 単行本(ソフトカバー)

Rubyの予約語を取得する方法

Rubyの予約語を取得するワンライナーを知った。いくつか疑問があるので忘れないように残しておく。

RubyToken::TokenDefinitions.select { |definition| definition[1] == RubyToken::TkId }.map { |definition| definition[2] }.compact

しかし、v2_7_0_preview3 でファイルが削除されていることから要注意である。

github.com

また、予約語の__ENCODING__が取得できないという問題もある。

これに関しては、ブログにてRuby1.9前後で削除されるようになった可能性がある。

改訂2版 パーフェクトRuby

改訂2版 パーフェクトRuby

  • 作者:Rubyサポーターズ
  • 出版社/メーカー: 技術評論社
  • 発売日: 2017/05/17
  • メディア: 大型本

Rubyでは、クラスの比較ができる

Rubyでは、クラスを比較できることを知ったのでメモとして残しておく。

String < Object

比較演算子は、Moduleクラスに存在している。

ここで、以下を比較すると結果としてnilが返る。

Kernel < BasicObject

これは、BasicObjectとKernelが継承関係にないからだ。

Module#>から、継承関係にないクラス同士の比較では nil を返します。とあるのでnilとなった。

メタプログラミングRuby 第2版

メタプログラミングRuby 第2版

  • 作者:Paolo Perrotta
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2015/10/10
  • メディア: 大型本