サカナ未遂

プログラミング、筋トレ、子育て

SQLアンチパターン4章、5章のまとめ

会社の輪読会でSQLアンチパターンの4章、5章を読んだのでそのまとめ。

SQLアンチパターン

SQLアンチパターン

  • 作者:Bill Karwin
  • 発売日: 2013/01/26
  • メディア: 大型本

4章 キーレスエントリ(外部キー嫌い)

先に結論 - 外部キーつけろ!!!

アンチパターン:外部キーを使用しない

外部キー制約を省略するとDB設計がシンプルになり、柔軟性が高まり、実行速度が早くなると思ってる人もいるかも知れないが、そこには代償があり代償は別の形で支払う必要がある。

外部キーを使用しない場合の大変さ

完璧なコードを前提にする必要がある

  • テーブルの関連付けを常に維持するコードを書くこと
  • 行の挿入時には外部キー列の値が参照先のテーブルの既存の値を参照してることを確認すること
  • 行の削除は関連する子テーブルも適切に更新されること

つまるところ「ミスをしないようにすること」

ミスを調べないといけない

  • 破損したデータを調べるスクリプトをたくさん作成する必要がある。似たようなクエリをすべての参照に対して書かないといけない
  • チェックする頻度も検討しないといけない

すべてのアプリケーションからのアクセスを把握する

  • DBに関するコードが完璧だとしても、直接SQLを実行する場合などは、データが破損する可能性がある。
  • クエリーを変更する場合、DB操作するすべてのアプリとスクリプトの変更を把握しないといけない。

外部キーを書きたくない理由

外部キーを書きたくない理由は、複数のテーブルの関連し合う列を更新するときに外部キー制約が邪魔になるから(キャッチ =22)。

アンチパターンを利用していい場合

外部キーをサポートしてないDBを使う場合

4章まとめ

外部キーつかえ!! (最初に書いたけど)

5章 EAV

(エンティティ・アトリビュート・バリュー)

可変項目のサポート

オブジェクト指向をもとにしたテーブル設計をする場合に、基底クラスでサブクラスでテーブルを分けるとする。

その場合、サブクラスに対応するテーブル毎に異なる項目を設定する必要がある。 そんなとき、汎用的な属性テーブルがあれば解決すると考えてしまう。 (これがアンチパターンのはじまり)

EAVの例 Issueテーブル

issue_id
1234

IssueAttributesテーブル

issue_id attr_name attr_value
1234 product 1
1234 data_reported 2009-06-01
1234 status NEW
1234 description 保存処理に失敗する

EAV

名前/値ペアともよばれる。

メリットは - 両方のテーブルの列数を減らせる - 新しい属性が増えても列数を増やさなくていい - 属性が存在しないエンティティの該当列にNULLが入ってるNULLだらけのテーブルになるのを防げる。(使わない項目が多すぎるテーブルとか)

EAVのデメリット

  • テーブルから報告日の項目を取得したい場合、クエリが冗長になる。
  • 必須属性を設定できない
  • SQLのデータ型を使えない。
  • 参照整合性を強制できない (外部キーをつけるとすべての行に適用されてしまう)
  • 属性名補わないといけない (attr_nameに入る名前がバラバラになる可能ある)
  • 行を再構築しないといけない

クエリで各属性の行をJOINする必要があるし、クエリ作成時には属性の名前をすべて指定する必要がある。

issue_id data_reported status priority description
1234 2009-06-01 NEW HIGHT 保存処理に失敗する

アンチパターンを使用していい場合

RDBでEAVを使う理由は簡単に見つからない。 RDBの長所が失われるから。 非リレーショナルなデータ管理が必要なら非リレーショナルな技術を使うべき。 (dynamoDB、Cloud DataStore、Cassandra、MongoDB、Redisとか)

EAVを使わずにEAVが扱うようなデータを格納する方法

シングルテーブル継承

すべてのタイプの属性を個別の列に格納して、関連するすべてのサブタイプを1つのテーブルにまとめる。

デメリット
  • NULLと非NULLの列がバラバラに点在してしまう。
  • 新しいオブジェクトタイプが増えるとその分の属性が増えるので列数の実際的な上限に達する可能性もある。

具象テーブル継承

サブタイプ毎にテーブルを作成する。 (基底の属性 + サブ固有属性のテーブル) サブタイプに存在しない属性列を格納する必要がないことがメリット

デメリット
  • すべてのサブタイプに共通する属性とサブタイプ固有の属性の区別が簡単につかない
  • 共通属性に新しい属性を追加する場合、すべてのサブタイプの属性の変更が必要になる。
  • すべてのオブジェクトを取得したいときもテーブルがわかれるので面倒

クラステーブル継承

オブジェクト指向の継承を模倣する、 基底型のテーブルを1つ作り、サブタイプ毎にテーブルをつくる。 サブタイプテーブルは基底型テーブルに対する外部キーの役割を持つ主キーを設定する。 基底型テーブルと1対1の関連が強制される。

半構造化データ

XMLJSONなどの構造化されたデータをそのまま打ち込む (ジェイウォーク大丈夫?)

デメリット
  • SQLが特定の属性にアクセスする手段を殆ど持ってない。

新しい属性を随時定義するための高い柔軟性が必要な場合に適している

パーフェクトRuby on Rails 増補改訂版 2章〜3章を読んだ

と続けて読んだので、そのままパーフェクトRuby on Rails増補改訂版を2章〜3章読んだ。

Rails本2冊読んだばかりなので、知ってる箇所を飛ばして、知らないところについてまとめた。

2章

ActiveRecord::Relation

ActiveRecord::Relationは内部でどのようなSQLを発行するか、という情報だけを保持する。実際にSQLの実行結果が必要になるまではDBへのアクセスは発生しない。 メソッドチェインによるクエリ構築を行うためこのように設計されている。 明示的に任意の箇所でSQLを発行したい場合はto_aメソッドを呼び出して即座にSQLを発行できる。

Scopeの定義

Scopeで定義した条件の結果がnilの場合、該当Scopeの検索条件を除外したクエリを発行するので意図しない結果になることがある nilを返す必要がある場合はクラスメソッドとして定義する方が良い。

ActiveRecord::Enum

sales_statusカラムに対して以下のようにenumを設定する。

class Book < ApplicationRecord
  enum sales_status: {
    reservation: 0,
    now_on_sale: 1,
    end_of_print: 2,
  }
end

ステータス毎に述語メソッドで状態を問い合わせることが可能

pry(main)> book = Book.last
pry(main)> book.reservation?
=> true

# !をつけるとそのenum値へ更新することができる。
pry(main)>  book.now_on_sale!
pry(main)>  book.now_on_sale?
=> true

# カラム名_before_type_castで実際の値を確認することができる。
 pry(main)> book.sales_status_before_type_cast
=> 1

enum型の名称で検索するためのscopeも追加される。

 pry(main)> Book.reservation
  Book Load (0.1ms)  SELECT "books".* FROM "books" WHERE "books"."sales_status" = ?  [["sales_status", 0]]
=> [#<Book:0x00007fbf8eb082b8
  id: 3,
  name: "enum Book 1",
  published_on: nil,
  price: 100,
  created_at: Mon, 11 Jan 2021 02:35:23.632974000 UTC +00:00,
  updated_at: Mon, 11 Jan 2021 02:38:22.399098000 UTC +00:00,
  publisher_id: 1,
  sales_status: "reservation">]

その他

  • デフォルトスコープはモデルクラスの操作全てに影響を与えるので注意深く検討してから使用すべき。
  • コールバックが実行されないメソッドの実行は気を付ける(update_系とか)

3章

Rackとは?

RackはWebアプリケーションサーバーとRailsの間に存在する。 Rackを経ることでアプリケーションサーバーとRails疎結合になるので、それぞれの組み合わせを変更することが容易になる。 Rackからフレームワーク層に到達するまでに処理を挟むことができるRackミドルウェアと呼ばれる機構があって、独自の処理やよく使う機能を差し込むこともできる。 Rackが利用するエントリーポイント用ファイルは一般的にはconfig.ruというファイルを利用する。

感想

Rails本3冊目なので、もっと早く読み終わると思ってたが、思った以上に内容が充実していて、3章まででも結構時間がかかった。 さすがのパーフェクトシリーズで、もはやRuby自体の説明はなく、いきなりRailsの説明から入る初級者をぶった切った内容、渋い。
Rubyの経験がない人はチェリー本Ruby自体を勉強してから望んだ方が良いと思う。 Ruby覚えて、これからRailsを覚えたい人は、まずRailsの教科書を読んでから挑戦した方が理解が進むと思われる。 4章以降もかなり濃い内容なのでまとめていくには時間がかかりそう。ゆっくり読んでいきます。

独習Ruby on Railsを読んだ

こないだ現場Railsを読んだら、Railsに知らないことや忘れてるところが結構あった。 blog.tera-chan3700.com

一応業務でRails触ってるけど、既存コードの改修だったりでなんとなくこなせていたけど、業務で使ってる以上のことが全然成長してないので、まだまだ基礎的なとこ固めようと思い、今度は独習Ruby on Railsを読むことにした。

独習Ruby on Rails

独習Ruby on Rails

  • 作者:小餅 良介
  • 発売日: 2019/06/19
  • メディア: 単行本(ソフトカバー)

3章までは、軽く読んだのでまとめてない。 4章以降で初めて知ったことなどメモしておく。

4章

rails dbconsole(rails db)

実装されてるDBのコマンドラインツールを起動してくれる。 SQLite3/MySQL/PostgreSQLの3種類が対象。 --sandboxオプションは使用できない。

rails stats

現在のアプリケーションのRubyコード統計情報を表示するコマンド。

rails notes

ソースコードに埋め込んだコメントの中からTODO、FIXME、OPTIMIZEのキーワードがあるものを抽出し内容と場所を一覧で表示してくれる。

6章

フォームオブジェクト

現場Railsでもちょっとだけ記載があったフォームオブジェクト。 特定のモデルに限定されないか、DBの登録に直接関係ない入力のバリデーションは入力専用のオブジェクト(フォームオブジェクト)を使うことで整理ができる。 利用承諾など、チェックボックスだけがあり、対応するモデルがない場合などに使える。 app/forms/acceptance.rb みたいな感じでファイルを作成し、ActiveModelを継承させることでバリデーションの機能を実装することができる。

Validateクラスで共通の独自ヘルパーを実装

app/validators配下にActionModel::EachValidatorを継承したクラスを作り、validate_eachを実装することでモデル共通のバリデーションを作成することができる。 バリデーションをまとめることができるのは知らなかった。

コールバッククラスの共通化

バリデーションと同様コールバックも共通化できる。 app/callbacks配下にクラスを作成し、中にバリデーションの処理を実装する。

class MessageOut
  def self.before_validation(obj)
    # バリデーション処理
  end
end

呼び出し側

class Book < ApplicationREcord
  before_validation MessageOut

  ....
end

こんな感じ。

7章

ポリモーフィック関連

子モデルで複数の親モデルを持つ時にポリモーフィック関連を使う。 userモデル、bookモデルを親に持つpictureモデルを作る場合、以下のようにmigrationファイルを作成

class CreatePictures < ActiveRecord::Migration[6.1]
  def change
    create_table :pictures do |t|
      t.references :imageable, polymorphic: true
      t.string :path_name

      t.timestamps
    end
  end
end

db:migrationするとPictureモデルは以下のようになる

class Picture < ApplicationRecord
  belongs_to :imageable, polymorphic: true
end

各親モデルにPictureモデルへの関連と行う。 bookモデル

class Book < ApplicationRecord
  has_many :pictures, as: :imageable
end

userモデル

class User < ApplicationRecord
  has_many :pictures, as: :imageable
end

こうすることによって、PictureはUserとBookを親に持つことができる。

仮想属性

テーブルカラムを持たない仮想的な属性(attributes API)を作ることができる。 Addressモデルに対してattributeを追加することで使用できる。

class Address < ApplicationRecord
  attribute :location, :string, default: '東京都葛飾区'
end

8章

コレクションルートとメンバールート

  • コレクションルート 複数のリソースを対象とするルートかidを持っていない新規リソースに対するルート

  • メンバールート idパラメータで特定できるルート

shallowルート

親子関係にあるリソースで子リソースをidで一意に特定できる場合、子のIDだけでアクセスできるようにするのにshallowを使う。

shallow do
  resources :user do
    resources :hobbies
  end
end

または

resources :users, shallow: true do
  resources :hobbies
end

のように設定する。

リソースフルルートのグループ化

名前空間によるリソースルートのグループ化を行った場合、名前空間はコントローラのファイルが配置されるディレクトリ名に相当する。 なのでコントローラは「名前空間名」ディレクトリに移動させる必要がある。

リソースルートだけをグループ化したい場合はscopeメソッドを使う。 scopeの場合だとコントローラの配置移動の必要はない。

ルートの共通化

同じ目的のルートパス名を複数で使用する場合は、concernを使うことで共通化できる。

# 共通ルート
concern :searchable do
  get 'search', on: :collection
end

# 各リソースフルルートで共通ルートを紐付ける
resources :user,s concerns: :searchable
resources :books, concerns: :searchable

9章

respond_toメソッド

リクエストされるフォーマットによてレスポンスを柔軟に変更することができる。

def show
  @user = User.find(params[:id])
  respond_to do |format|
    format.html
    format.json {render json: @user}
  end
end

上記のコードの場合

  1. http://localhost:3000/users/1
  2. http://localhost:3000/users/1.json

で1の場合html、2の場合jsonでレスポンスを返してくれる。

10章以降

viewに関するとことか、とりあえず直近の業務では触ることないのでサラッと読んで終わり。 必要な時が来たら読み直そうと思う。

所感

現場Railsを読んだばかりだし同じような内容の箇所は飛ばして読んだが、 一つ一つの機能がより細かく説明されている。特に章末の練習問題がためになる。 写経だけしてプログラム動かすとできた気になるが、練習問題で自分で考えてコード書くとやっぱり身に付く。 scaffoldでさえ本見ないと、どうやるんだっけ?となってるので良い機会だった。 同じ技術の本を続けて読むと、1冊目は結構時間かかるけど、2冊目以降はある程度知ってる箇所飛ばして知らないとこにフォーカスできるので、この流れで次はパーフェクトRuby on Raisを読んでみようかと思う。

Railsで共通で使用できるバリデーションを作る

Railsのモデルに独自のバリデーションを作成する時、以下のように実装する

class User < ApplicationRecord
  validate :email_check

  private

  def email_check
    # Eメールのバリデーション処理
    .....
  end
end

この場合、Userモデル限定のバリデーションとなる。 特定のオブジェクトに限定されずに共通で使用できるバリデーションを作成するには app/validatorsディレクトリの下に、新しいクラスを作成しvalidate_eachメソッドを実装する。

#app/validators/email_address_validator.rb
class EmailAddressValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
      record.errors[attribute] << (options[:message] || "はメールアドレスではありません")
    end
  end
end

# 各クラスで使用できる。
class Person < ApplicationRecord
  validates :email, presence: true, email_address: true
end

class User < ApplicationRecord
  validates :email, presence: true, email_address: true
end

validate_eachの第一引数recordは、呼び出し側のオブジェクト、第二引数attributeは指定した属性名、第三引数valueは指定した属性の値が入る。

呼び出し側で、email_address: trueとすることで、ValidateクラスがEmailAddressValidatorであることを示すことになる。

参考書籍に

独習Ruby on Rails

独習Ruby on Rails

  • 作者:小餅 良介
  • 発売日: 2019/06/19
  • メディア: 単行本(ソフトカバー)

現場で使えるRuby on Rails 5 速習実践ガイドを読んだ

積読していた現場Railsを読みました。

全部読むの結構時間かかったけど、知らなかったこと、忘れていたこと結構あったのでそれらをまとめまた。

Chapter1

1-7 読めると便利! Rubyっぽい書き方

この辺りのイディオム、ちょくちょく見るし便利だけどいつも忘れる。

nilガード

number ||= 10

# number || (number = 10) と同じ動き

numberに値が入っていればそのまま、nilなら10を代入する。

ぼっち演算子

&.をつけてメソッドを呼び出すとレシーバがnilでもエラーが発生しない。

obj = nil

# objはnilなのでeachを使おうとするとエラーになる。
obj.each{|x| puts x }
NoMethodError: undefined method `each' for nil:NilClass

# ぼっち演算子をつけるとエラーにならない
obj&.each{|x| puts x }
=> nil

%記法

# %wは文字列の配列を生成する
%w(foo bar piyo)
=> ["foo", "bar", "piyo"]

# %iはシンボル の配列を生成する
%i(foo bar piyo)
=> [:foo, :bar, :piyo]

Chapter3

slimを使う。

下記を追加することで、slimが使える

gem 'slim-rails'
gem 'html2slim'

コマンドを実行することで既存のファイルをslimに変換してくれる

 bundle exec erb2slim app/views/layouts/ --delete

現職ではvueを使ってるので、slim使ったことなかった。pugみたいなもんか。

i18nでエラーメッセージを日本語にする

下記コマンドでja.ymlを取得してconfig/localesにおく。

wget https://raw.githubusercontent.com/svenfuchs/rails-i18n/master/rails/locale/ja.yml --output-document=config/locales/ja.yml

config/initialilzers/locale.rbに以下を記載する。

Rails.application.config.i18n.default_locale = :ja

リクエストパラメータ

GETでもPOSTでもパラメータの受け取り方をプログラマが区別しなくていいようにparamsが使える。 なんとなく使ってたけど、確かにGETでもPOSTでも同じように使えるのはRailsがやってくれてたのか。

レンダーとリダイレクト

レンダーはアクションに続けてビューを表示すること。リダイレクトはアクションを処理した直後にビューを表示せず別のURLに案内すること。

simple_format

以下のコード、hメソッドでXSS対応をした上で改行を
に変換し、さらに

タグで囲んでくれる。

simple_format(h(@task.description), {}, sanitize: false, wrapper_tag: "div")

Chapter4

バージョンを1つ戻してから1つ上げる。 バージョンが戻ることの確認とかに使う。

bin/rails db:migrate:redo

マイグレーションファイルのupとdown

マイグレーション失敗時に自動で戻せない場合に必要。 add_columnとかの場合の失敗は自動で戻してくれるから不要だけど、カラムにlimit: 30とか追加する時にはdownも書いておくと戻せる。downがないと例外が発生する。

validateのスキップ

falseを設定することでvalidateをスキップできる。

task.save(validate: false)

persisted?メソッド

オブジェクトがDBに登録されているかどうか確認ができる。

redirect_toのパラメータにmodelのオブジェクト

redirect_toのパラメータにmodelオブジェクトすると、showのページに遷移する。

validatesとvalidateの違い

標準の検証はvalidatesで自前の検証処理はvalidateになる。

パスワードのハッシュ化

has_secure_passwordを使うことでパスワードをハッシュ化できる。 使うにはbcrypt(gem)が必要。 has_secure_passwordをモデルに記述すると、passwordとpassword_confimartion属性が追加される。 2つの属性が一致した時だけパスワードを登録することができる。 テーブルにはpassword_digestの名前でカラムを用意しておく必要がある。これはhas_secure_passwordを使用するときの命名ルールである。

認証のためのauthenticate

authenticatehas_secure_passwordを記述した時に自動で追加される認証のためのメソッド。引数で受け取ったパスワードをハッシュ化して、オブジェクトに保存されてるdigestと一致するか調べる。

helper_method

コントローラにhelper_methodでメソッドを定義すると、viewからでも呼び出すことができる。

skip_before_action

before_actionをスキップすることができる。

Chapter5

Rspecについて

Rspecは動く仕様書として自動テストを書くという発想で作られてる。Rspecは「テスト」ではなく「Spec」を書くという気持ちが大事とある。 「動く仕様書」か、なるほどなるほど。 とりあえず書くもんだと今まで書いてきたけど、こういうフレームワークの思想みたいなものを知ることで、納得してテストを書いていける。

SystemSpecについて

FeatureSpecの代わりっぽい?、結局SPAでどうやるのか?と調べたらいけそう。 ここを見てみた。 tech.unifa-e.com

弊社ではE2EはRequestSpecしか書いてなかったけど、SystemSpecも導入した方が良いのかな。

visit

特定のURLでアクセスする。ログイン画面にアクセスする場合とかは以下のように記述する。

visit login_path

fill_in

パラメータで指定したラベルがついたテキストフィールドに値を入れる。 以下のように記述する

fill_in 'メールアドレス', with: 'a@example.com'

shared_examples_for

itなどを共通化することができる。

shared_examples_for 'タスクが画面に表示されること' do
  it { expect(page).to have_content 'タスク' }
end

コンソールのサンドボックス

Railsコンソールにサンドボックスあるの知らなかった〜。-s付ければいいのか

bin/rails c -s

Chapter6

CSRFはシーサーフと読まれることがある。

SQLインジェクションに気を付けるパターン

whereメソッドなどでSQL文字列を直接渡して条件指定するときはSQLインジェクションに気を付ける。 こんな場合

User.where("name = '#{params[:user_name]}'")

こっちにした方がいい。

User.where('name = ?', params[:user_name])

プレースホルダを使うと、「'」とかをエスケープしてくれる。

Chapter7

ransackを使うときに気を付けること

modelに以下のコードを入れることで特定のカラムに対してしかクエリーを発行できないようにすることができる。 下のコードは関連を指定できる。[]にすることで意図しない関連を含めないようにする。

def self.ransackable_attributes(auth_object = nil)
  %w[name created_at]
end

def self.ransakable_associations(auth_object = nil)
  []
end

mailcatcher

mailcatcherは送信したメールをブラウザ上で確認することができるgem コンソールから以下のコマンドで使える

mailcatcher

config/environments/development.rbに以下の設定をすると使える

config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = { address: '127.0.0.1', port: 1025 }

ActiveStorage

クラウドサービスにファイルをアップロードしてActiveRecordモデルに紐付けることが簡単にできる。 config/storage.ymlに保存先を設定する。 コメントアウトされているが、AWSやGCSも簡単に設定できる。

Chapter9

redoを習慣にする。

ロールバックできないトラブルを防ぐためにマイグレーションを書いたとき、ロールバックが成功することを確認することが重要。

マイグレーションファイルからアプリケーションコードの参照は避ける。

マイグレーションコードは積み重なっていくのでいつ実行しても同じように動く必要がある。 アプリケーションコードを使用する場合、その内容が変更(モデルにバリデーションが追加されるなど)したらマイグレーションファイルの挙動も変わってしまうため、避けた方が良い。

スキーマキャッシュ

マイグレーションファイルでモデルインスタンスを作成すると、ActiveRecordスキーマをキャッシュする。同じテーブルに対して連続してマイグレーションを行う場合、キャッシュが効いて意図しない挙動になることがあるので、reset_column_informationスキーマキャッシュを更新するよう習慣づける必要がある。

Chapter10

共通機能のモジュールのMix-in

RailsでMix-inのモジュールを書く時にはActiveSupport::Concernをextendする。 クラスレベルの拡張を素のrubyに比べて少し書きやすくしてくれる。

SIT

ActiveRecordモデルの継承する場合、継承元と継承先で1つのテーブルに対応づけを行うことをSIT(Single Table Inheritance)と呼ぶ。 対応づけらえるテーブルには継承されたテーブルが必要とするカラムを全部盛り込む必要がある。

複数のモデルがカラム処理は特定処理の専門家を作る

コントローラで2つのモデルを扱う処理の場合、片方のモデルにコードを書くと、他のモデルのデータの依存性を高めるのでよくない。 2つのモデルのデータを扱う専門のクラスを作成して処理を書くと良い。

サブリソース単位でコントローラを分割

コントローラは1つのモデルに対して1つでなければならないわけではない。 サブリソースへの操作を対応する複数のコントローラに分けることができる。

所感

まとめて感じたのは特にviewで使う機能に関する知識が結構欠落していること。 SPAがスタンダードになりつつあるので出番が減ってるのは確かだが、Railsを使って仕事をしていくなら最低限は覚えておかないとなぁとしみじみ思う。 あと、Chapter10は現場でかなり参考になる情報だった。定期的に読みなそうと思う。 いつ買ったのかAmazonの購入履歴で調べたら、買ってから2年も経ってた、、、もっと早く読んどけば良かった。 今年は積読消化を頑張らないと

JavaScriptのawait式をAsync Functionの中で使用するときの注意点

JavaScriptのPromiseとかawaitとか雰囲気で乗り切ってきたけど、ちゃんと腰据えて勉強しようと思って購入したJavaScript Primer

非常に分厚いけど、読みやすくサンプルコードも豊富なので理解しやすかった。 まだまだ全部よめてないけど、非同期処理のところを集中的に読む。

第22章で気をつけないとと思った箇所

async function asyncMain(){
  await new Promise((resolve) => {
    setTimeout(resolve, 16); 
  });
};

console.log("1. asyncMain 関数を呼び出します");
asyncMain().then(() => {
  console.log("3. asyncMain関数が終了しました。");
});
console.log("2. asyncMain 関数外では次の行が同期的に呼び出される");

このコードを実行した場合、asyncMainでawaitをつかって処理をとめているけど、関数を呼び出してるメインの処理はとまらず最後のconsole.logを出力する。 あくまで関数の中ではとまっているだけである。 awaitで何でも止めれると思っていたので知ることができてよかった。

CloudRunを使ってみた

GCPのCloudRunというサービスを使うことがあったので簡単な使い方をメモっときます。

CloudRunとは

公式URLより https://cloud.google.com/run?hl=ja

コンテナ化されたアプリケーションをすばやく安全にデプロイ、スケーリングできる、フルマネージド型のコンピューティング プラットフォーム

とあります。 コンテナに入れたwebアプリを、そのままデプロイできるってことですね。 GCPのContainerRegistoryにコンテナをpushし、CloudRunでデプロイするだけで動かせます。

CloudRunの料金

https://cloud.google.com/run/pricing?hl=ja f:id:tera_chan3700:20200618212128p:plain

ちょっと試すくらいならほぼ無料で使えます。

あとContainerRegistoryの料金がかかりますが、これも微々たるものです。 https://cloud.google.com/container-registry/pricing?hl=ja 個人で試すには懐が痛まなくて好きそうです。

注意

CloudRunからCloudRun、または他のサーバレスを無限ループで呼ぶような処理があると危険です。そこは個人で気をつけてください。 例:https://blog.mmmcorp.co.jp/blog/2019/12/25/lambda-cloud-bankruptcy/ これはAWSですが、GCPでも起こりえます。

サンプル

GOで簡単なエコーサーバーを作ります

package main

import (
    "fmt"
    "log"
    "net/http"
    "net/http/httputil"
)

func handler(w http.ResponseWriter, r *http.Request) {
    dump, err := httputil.DumpRequest(r, true)
    if err != nil {
        http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
        return
    }
    fmt.Println(string(dump))
    fmt.Fprintf(w, "<html><body>hello Cloud Run!!!</body></html>")
}

func main() {
    var httpServer http.Server
    http.HandleFunc("/", handler)
    log.Println("start http listening :8080")
    httpServer.Addr = ":8080"

    log.Println(httpServer.ListenAndServe())
}

8080ポートにアクセスすると、「hello Cloud Run!!!」の文字列を返すサーバーです。 実行して確認します。

$ go run main.go
2020/06/18 20:01:04 start http listening :8080

サーバーが起動したのでアクセスしてみる f:id:tera_chan3700:20200618213242p:plain

レスポンスが帰ってきました。

次にこれをビルドして実行するDockerfileを作ります。

FROM golang:latest

#ディレクトリ作成
WORKDIR /go/src/go-image
#ホストOSのmain.goをWORKDIRにコピー
COPY main.go .

#バイナリを生成
RUN go install -v .

#バイナリを実行
CMD ["go-image"]

これをビルドします。

$ docker build -t gcr.io/<自分のプロジェクトID>/cloudrun-test:v1 .

ローカルで実行してみます

$ d run -p 8080:8080  gcr.io/<自分のプロジェクトID>/cloudrun-test:v1

アクセスします。 f:id:tera_chan3700:20200618213636p:plain 動きました。ちゃんとコンテナの中で起動することが確認できました。

コンテナレジストリへのpush

今回はステージングをかります。 gcloudコマンドで事前にログインしています。

GCPにpushする。

docker push gcr.io/<自分のプロジェクトID>/cloudrun-test:v1

入ったことを確認 f:id:tera_chan3700:20200618213808p:plain

CloudRunへのデプロイと実行

サービスを作成をクリック f:id:tera_chan3700:20200618213957p:plain

適当に入力して「次へ」をクリック f:id:tera_chan3700:20200618214119p:plain

さっきpushしたイメージを選択して作成ボタンをクリック f:id:tera_chan3700:20200618214315p:plain

しばらくするとデプロイが完了し、エンドポイントが作成されます。 f:id:tera_chan3700:20200618214521p:plain

ここにアクセスします。 f:id:tera_chan3700:20200618214609p:plain

動きました。 ローカルで作ったコンテナが簡単にデプロイでき、動かすことができます。 タイムアウトなど制限もありますが、ローカルで動いてるコンテナを簡単にデプロイできるのは便利です。