Mix Leap Study 特別編 - レガシーをぶっつぶせ。現場でDDD! コラボカンファレンスに参加しました -- Ruby での開発に活かせるのか?

yahoo-osaka.connpass.com

どうも永田(@kizashi1122)です。 このイベントに参加してきました。

参加のモチベーション

「DDD」という言葉は当然知っている。ただあんまりよくわかっていない。エリック・エヴァンスの本も読んでいない。

こんな状態で、「DDD とは何なのか?」を少しでも理解できればなというモチベーションで挑みました。

聞いた発表

  • ドメイン駆動設計という設計スタイル by @masuda220
  • DDDのモデリングとは何なのか、そしてどうコードに落とすのか by @little_hand_s
  • 現場でドメイン駆動設計を広げるには何をすれば良いか? by @tsuyok
  • 抽象的な教えを試行錯誤しながら解釈した DDD の実践レポート by @suzuki_hoge
  • 過去の失敗例から再考するモデル駆動設計 by @j5ik2o

全部メイン会場やないかい。

正直、増田さん(@masuda220)の発表は概念的な内容も多く、しっかり理解できたかどうかは不安でした。 ただ後半はコードに落ちる部分の説明もあり、なんとなく「こういうことかな」という理解をしました。

私自身は、今は Ruby で開発しているものの Java の経験者でもあり、特に松岡さん(@little_hand_s)とかとじゅんさん(@j5ik2o)の発表はとても腑に落ちるもので、増田さんの発表の後半の続きのような、よりコード側の説明がありDDD初心者にはとても理解しやすかったです。

ただ、これってDDDと言うかこれって・・・では?と思うところがあり、ちょっとモヤっとしていました(・・・については後述)。

懇親会

登壇者との懇親会の参加させていただきました。 ちょうど隣に、登壇者の松岡さん(@little_hand_s)向かいに Yahoo! の辻さん(@crossroad0201)が座られていたので、思い切って上述の疑問をぶちまけました。

私:

「私はDDD初心者で、今日、みなさんの発表を聞いていて感じたのですが、これって純粋にオブジェクト指向をするってことじゃないんですか?

松岡さん、辻さん:

「そのとおりだと思います」

このときの嬉しさ。我が意を得たり。

さらに補足してくれるスマートな松岡さん。

Web アプリケーションなのでややこしくなるんですよ。スタンドアローンアプリなら自然とDDDになるんです。

なるほど。めちゃくちゃわかりやすい解説。さらに松岡さんはこう続けます。

要は、高凝集で疎結合を目指していればいいんですよ。

インゲージでは今、自社サービスを Ruby(Rails)で開発しているので質問。

「松岡さん、やはり DDD と Ruby は相性よくないんですかね?

松岡さん

「よくないと思いますね」

なるほど。 確かに今日の話を聞く限りは、DDD は型安全でないと実装できない・・・とは言ってないものの、型安全であるほうが望ましいだろうなと思っていました。 この1点だけでもRubyなどのLLは不向きなのかなと思います。

また、Ruby on Rails はそもそも DDD が必要ではないようなサービスに向いているのでは?という意見もありました。 適材適所ということでしょうか?

ただ、私は「Ruby on Rails にだって、DDD の考え方はとても重要ではないか。何か活用できるはず」と思っていました。

Ruby on Rails の構成

DDD と Ruby on Rails が相性が悪いということについて、私の今の理解はこうです。

Webアプリケーションというのは、結局のところ、画面からの入力(ほとんどの場合テキスト)をデータベース(SQLレベルではテキスト)に渡す作業ばかりです。

Rails では、画面からの入力は Controller クラスが行い、Controller クラスはデータベースのテーブルの1対1に対応した Model(Rails の文脈の Model)クラスを通じてデータベースの書き込みや読み込みを行うわけです。

つまり、

画面→ Controller → Model → RDBMS

ということになります。ここでは、View はちょっと割愛します。 つまりこうなると、ビジネスロジックを書くのは、Controller か Model になり、Fat Controller になるか、Fat Model になるかの道しかない。 これはDDD的にうまくない。HTTPリクエストに依存している Controller層とデータベースの永続化を担う Model のどちらからがビジネスロジック(ドメインロジック?)も担うのは確かにおかしい。

ただ、なんとなく、ビジネスロジックをこの間に持たせることがうまくいけば、DDD 的なやり方に近づくのではないかなと思っていました。 (上記の理解は間違っているかもしれないです)

まさにこれを考えていたときに、このブログが公開されました。

engineer.crowdworks.jp

すごい。タイムリー。ちょうどこういうことを考えていました。

最後に

この懇親会のあと、終電をなくし、かとじゅんさん、田中さん、辻さんと3時まで色々なお話ができたのはいい思い出です。

DDD 本ちゃんと読まないと。

Mix Leap 運営の方々、特に、Yahoo! の辻さん(@crossroad0201)、ありがとうございました。

自前 Redis から AWS Elasticache に移行した

http://download.redis.io/logocontest/82.png

どうも永田です。

弊社サービスの「リレーション」では、セッション管理やジョブキュー、一時的なデータ置き場として Redis を使っています。 最近、EC2 上に自前で構築していた Redis から AWS Elasticache に移行しました。

動機

自前で構築していた Redis は Sentinel を使ってクラスタを構築していました。特に問題はなかったのですが、やはり管理対象を減らしてマネージドサービスに移行していきたいという思いがあり、移行することにしました。

データの移行

実は自前 Redis は3系のバージョンを使っていました。AWS Elasticache では現時点で最新の5.0.4を使っています。気になるのはデータ(rdbファイル)を移行する際の互換性。

github.com

こちらには

Redis dump file is 100% backwards compatible. An older dump file format will always work with a newer version of Redis.

とあり、バージョンによる互換性は大丈夫だと言えそうです。

ただし、AWS Elasticache はクラスタを起動するタイミングでしか rdb ファイルをインポートできないため、移行は多少ややこしくなります(後述)

Cluster Mode はどちらにすべきか?

AWS Elasticache はそもそもがクラスタなのですが、作成する際に Cluster Mode のチェックボックスがあります。当然オンでしょ?と思ったらハマりました。弊社サービスでは非同期ジョブに Sidekiq を使っており、ジョブキューには Redis を使っているのですが、その Sidekiq が Redis Cluster には対応していないようなのです。

github.com

ここに、Cluster is NOT appropriate for Sidekiq と太字で書かれています。

実際のエラーメッセージは

ERR CROSSSLOT Keys in request don't hash to the same slot.

というもので、https://github.com/mperham/sidekiq/blob/3d622887c81b28cf0ffc5c8c1f7dd626c463f6a1/lib/sidekiq/api.rb#L205-L206 ここに TODO で誰かクラスターサポートしてよとコメントに書いていますね。

では実際の移行手順はどうなるのか?

上述のとおり、rdb ファイルは、Elasticache のクラスタ作成時でないと指定できません。 そのため、多少時間はかかるのですが、以下の手順を取ることにしました。

  1. AWS で Elasticache のクラスタ作成の入力項目を全部埋めておく(rdb の s3 のロケーションも)
  2. アプリケーションをメンテナンスモードをオンにする
  3. バッチや常駐プログラムの起動を停止し、自前 Redis へのコネクションがないことを確認する(コマンドは後述)
  4. 旧 Redis の rdb ファイルを 1 で指定した s3 のロケーションにアップロードする
  5. アップロードしたファイルのS3上でのパーミッションを変更する(詳細は後述)
  6. クラスタ作成の実施する(大体7分から8分くらいか)
  7. ステータスが creating から modifying に変わり、primary endpoint が確定したら、アプリケーションが参照するRedisのホストをその primary endpoint に変更し、設定内容を反映させる
  8. メンテナンスモードをオフにする
  9. セッションが引き継げているかなど動作確認をする

といった流れとなりました。

Redis のコネクションの確認は

$ redis-cli -h <host> CLIENT LIST 

で確認可能です。

また s3 にアップロードした rdb ファイルのパーミッションは

docs.aws.amazon.com

にあるとおり、 [Add Account] に 540804c33a284a299d2547575ce1010f2312ef3da9b3a053c8bc45bf233e4353 を入力し、読み取り権限を与える必要があります。

移行後

残念ながら、AWS Elasticache にしたことによってパフォーマンスがよくなったとかそういった感触はありません。 メモリ空間は今までより増えたので、さらに活用していこうかと思います。

では!

エンジニアは引き続き募集中だよ!

bash スクリプトの罠

こんにちは、masm11 です。お久しぶりです。 今回は bash スクリプトの罠を一つご紹介したいと思います。

症状

以下のような bash スクリプトを作成します。

#!/bin/bash

sleep 5

5秒間 sleep して終了するだけのプログラムです。

実行すると、

$ ./test.sh
$ 

5秒後にプロンプトに戻ります。

次は、端末をもう一枚開き、test.sh を実行中に、以下のように test.sh を書き換えてみます。

$ echo echo kita >> test.sh
$ 

するとどうでしょう。

$ ./test.sh
kita
$ 

後から追加したコードも実行されてしまっています。

解明

いったい何が起きているのでしょうか?

こういう時にはシステムコールをトレースすると便利です。

以下のように実行します。

$ strace -f -o log ./test.sh
kita
$ 

こうすると、どんなシステムコールが実行され、どんな返り値だったかが、log ファイルに 出力されます。

この log ファイルから重要そうな箇所だけを抜き出してみました。

24863 openat(AT_FDCWD, "./test.sh", O_RDONLY) = 3
24863 read(3, "#!/bin/bash\n\nsleep 5\n", 80) = 21
24863 lseek(3, 0, SEEK_SET)             = 0
24863 dup2(3, 255)                      = 255
24863 fstat(255, {st_mode=S_IFREG|0755, st_size=21, ...}) = 0
24863 lseek(255, 0, SEEK_CUR)           = 0
24863 read(255, "#!/bin/bash\n\nsleep 5\n", 21) = 21
24863 wait4(-1,  <unfinished ...>
24863 <... wait4 resumed>[{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 24864
24863 read(255, "echo kita\n", 21)      = 10
24863 read(255, "", 21)                 = 0

最初の 24863 はプロセスIDで、その右に実行したシステムコールの内容が出力されています。

wait4 と書いてあるところが、sleep コマンドの終了を待っているところです。その後に、

24863 read(255, "echo kita\n", 21)      = 10

と続いています。なんと、ここでスクリプトの続きを読んでいました。

まとめ

bash スクリプトは実行中にもスクリプトを read していることがわかりました。 実行中に書き換えると、場合によってはとんでもない目に遭うことが想像できます。

bash スクリプトは実行中に書き換えないようにしましょう!

Ruby で Excel ファイルを作る

お久しぶりです。masm11 です。随分時間があいてしまいました。

以前、Python で Excel ファイルを作成する方法について書きました。 しかし弊社はやはり Ruby がメインなので、今回は Ruby で作成する方法について 紹介したいと思います。

Ruby で Excel ファイルを扱える gem にはいくつかあるようです。

  • Win32OLE
  • WrapExcel
  • Spreadsheet
  • Roo
  • Axlsx

(https://qiita.com/damassima/items/1b791ba90459ef0534fe より)

たくさんありますね。しかし今回は .xlsx ファイルを Linux で(かつ無料で)作成したいので、

  • rubyXL

を使うことにします。

gem のインストール

Rails で使いたいので、Gemfile に以下のように書きます。

gem 'rubyXL', '~> 3.4.3'

そして、いつもどおり、

bundle

です。

Rails でなくただの Ruby プログラムで使いたい場合は、

gem install rubyXL -v '~> 3.4.3'

ですね。

require する

rubyXL にはいろいろな便利メソッドがありますが、 rubyXL 3.4.0 以降では、メモリ使用量を削減するため、これらの便利なメソッドを使うには明示的に require してやる必要があります。

あとで set_number_formatchange_contents メソッドを使いたいので、 ここでは以下のようにします。

require 'rubyXL/convenience_methods/cell'
require 'rubyXL/convenience_methods/workbook'

Rails の場合でも必要です。

その他に require できるものについては、本家に書いてあります。

使う

require したら、コード本体を書いてみましょう。

まずは、Workbook を作成し、その中のシートを取得します。

book = RubyXL::Workbook.new
sheet = book[0]

ではこのシートに値をセットしていきます。

A1 セルに foo を入力するには以下のようにします。

sheet.add_cell(0, 0, 'foo')

簡単ですね。

A2 セルに数値の 2 を入力するには以下のようにします。

sheet.add_cell(1, 0, 2)

'2' と文字列にしてしまうと、勝手に '2 になってしまうので、型には注意してください。 Excel のいつものお約束ですね。

なお、add_cell の引数は、1つめが縦の位置、2つめが横の位置で、どちらも 0 から始まります。

日付の場合は少し特殊です。add_cell メソッドが Date 型に対応していないのです。

A3 セルに日付の 2019/06/10 を入力するには、以下のようにします。

c = sheet.add_cell(2, 0, 0)
c.change_contents(Date.parse('2019/06/10'))
c.set_number_format('yyyy/mm/dd')

1行めでセルに数値型の 0 をセットしています。これでこのセルは数値型になります。 そして2行めで日付をセットしています。change_contents メソッドは Date 型に対応しています。 最後に3行めで日付の表示形式をセットします。

ここで、上に書いた require をしていない場合、以下のエラーになります。

./test.rb:14:in `<main>': undefined method `change_contents' for #<RubyXL::Cell(2,0): 0, datatype=nil, style_index=0> (NoMethodError)

保存する

ファイルに保存します。

book.write('foo.xlsx')

この foo.xlsx を開くと、以下のようになります。

f:id:masm11:20190610224818p:plain

Rails を使った Web サービスなどでは、小さいファイルなら、ファイルに保存するよりメモリ中に作ってくれた方が便利なこともよくあります。 その場合は、以下のようにします。

data = book.stream.read

まとめ

以上、Ruby で簡単に Excel ファイルを作ってみました。

Python で作れることを知った時には、「きっと Ruby でもできるはず!」と思いました。 本当にあるんですね… 便利な世の中です。

ではまた。

Rails Developers Meetup 2019 に参加してきましたー

こんにちは。Re:lation のエンジニアの石田です。

先日行われた Rails Developers Meetup 2019 に行ってきました。

2日間で70近いセッションがあって、どれを選ぶかかなり迷いました。 ホント3日間にしてほしいです。。。 今回は、来月行われる Ruby Kaigi 2019 で発表される方も多くいらっしゃったので、同じような発表については優先度を下げる形で選んで回りました。

ちなみに、参加費は会社が全額負担してくれました。(ありがたい!)

railsdm.github.io

どのセッションも良かったのですが、特に印象に残った発表だけに絞って、書いていきます。

[Day 1: KEYNOTE] "Ask Me Anything" by DHH

www.youtube.com

急遽、翻訳なしでゆっくり喋るという形式で KeyNote が始まって、正直半分も聞き取れなかったです。。。

それでも、Rails6 の受信メールの処理を扱う Action Mailbox の話や Rails 7 での構想についての話は何とかついていけました。 JavaScript Framework には否定的な感じなので、このあたりは今後も自分で環境を構築していく必要性を感じました。

ともあれ、ちゃんと英語を聞き取れるようにならないといかんなぁ。。。

[A-1] アプリケーションを作るときに考える25のこと

www.slideshare.net

自分が Rails に初めて触ったのが Re:lation でその頃に色々とgemを調べたりしたのですが、今のrailsのデファクトスタンダードをキチンと追えているのか自信がなかったので聞いてきました。

知らないgemも多く、その知見も聞いてよかったのですが、github の Dependency graph で追うという発想はまったくなかったです。 技術を追うための1つの方法が知れてラッキーという感じでした。

[B-9] Railsアップグレード百景

speakerdeck.com

Re:lation もアップグレードしていく必要があり、その知見があまりないので聞いてきました。

色々なハマりどころを聞くことができて非常にありがたかったです。 特に Rails 4 と Rails 5 に互換性のない gem (devise など) があることを初めて知ったので、このあたりの対策も考えていかないとキツそうに感じました。

[A-10] 毎日の開発に役立つRailsプラグインづくりの秘訣

speakerdeck.com

現場の問題を解決するためにgemを作るんだという松田さんの話は非常にうなずけました。

ただ、「E2Eテストが書くのがメンドイ」とか「ER図みたい」とかやることの規模が大きすぎてスゲーの一言。 考えついても、この規模のgemを作ろうという気合が出ない自分はダメなのだろうかとちょっと凹みました。

[C-17] Ruby on Railsの正体と向き合い方

speakerdeck.com

DHHの考え方や思想など、あまりインタビュー記事などもを追って見ないので非常にタメになりました。

設計の話などはまだ消化できていない部分もあって、自分の中で消化できるように今後も考えていきたいです。

[A-19] 巨大なモノリシック Rails アプリケーションのマイクロサービス化戦略

speakerdeck.com

クッ社では、モノリシックなRailsアプリケーションをマイクロサービス化していってます。という話。

今回の railsdm のテーマとして、この話題が一番多かったように思います。 次点で CDN Cache とか BFF の話。

マイクロサービス化は難しいとよく聞いてはいましたが、改めて難しいんだなぁと感じました。

あと、ガラケー向けのサイトが未だに一定数のアクセスがあるのは驚きました。 こういうシステムを運用していかなければならないというツラミもあるという話と思いきや、システムを切り出した際に最新技術で動くシステムになった話で非常に面白かったです。

全体的な感想

railsdmは初参加+勉強会参加が久々だったのもあり、話についていけるか不安だったのですが、色々な話を聞くことができて非常に楽しかったです。

自分の勉強会参加したいというモチベーションがまた上がったので、行ってよかったと思いました!

次は RubyKaigi に行きます!

WordPress の内部 HTTPS 通信の問題を解決する

こんにちは。masm11 です。今回は WordPress に関連した話を書きたいと思います。

WordPress はテーマやプラグインがいろいろあって便利です。 テーマが特定のプラグインを必要としている場合は、テーマのインストール時に そのプラグインをインストールする機能もあるようです。 しかし、プラグインがサーバ内にダウンロードされ、そこから改めてダウンロード、イン ストールが行われます。 自サイトが https の場合、ダウンロードする際も当然 https になります。

さて、ここでダウンロードに失敗しました。エラーメッセージは以下のとおりでした。

f:id:masm11:20181228201132p:plain

ダウンロードに失敗しました。cURL error 60: SSL certificate problem: unable to get local issuer certificate

ダウンロードしようとしたファイルの URL も表示されています。 これを解決したいと思います。

curl コマンド

エラーメッセージに cURL とあるので、curl コマンドでアクセスを試みたところ、 確かに同じエラーメッセージが表示されました。

$ curl https://ingage.jp
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: http://curl.haxx.se/docs/sslcerts.html

curl performs SSL certificate verification by default, using a "bundle"
 of Certificate Authority (CA) public keys (CA certs). If the default
 bundle file isn't adequate, you can specify an alternate file
 using the --cacert option.
If this HTTPS server uses a certificate signed by a CA represented in
 the bundle, the certificate verification probably failed due to a
 problem with the certificate (it might be expired, or the name might
 not match the domain name in the URL).
If you'd like to turn off curl's verification of the certificate, use
 the -k (or --insecure) option.
$ 

エラーメッセージから、SSL 証明書絡みであることはすぐに解ります。

SSL 証明書は CA (証明書発行機関) の証明書によって確認でき、CA の証明書はさらに上位の CA の証明書によって確認できる、 という木構造をしています。今回のエラーメッセージはそのことに関連したメッセージのようです。

ブラウザからサイトにアクセスして証明書を確認してみます。

f:id:masm11:20181228203130p:plain

CA の証明書は GeoTrust RSA CA 2018 です。

さて、curl コマンドは

$ type -a curl
curl is /opt/bitnami/common/bin/curl
curl is /usr/bin/curl

PATH 的に /opt/bitnami/common/bin/curl になっているようです。 このファイルは shell script で、その中には

CURL_CA_BUNDLE="/opt/bitnami/common/openssl/certs/curl-ca-bundle.crt"

という行があります。

しかし、この curl-ca-bundle.crt というファイルの中を見ても GeoTrust RSA CA 2018 がありません。 これが問題かもしれません。

中間証明書を

https://www.geotrust.co.jp/resources/repository/intermediate_dc.html

からコピーして curl-ca-bundle.crt の最後に追記してみます。以下のようになります。

... (略)
N+vp1RPZytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZgh5Mm
m7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg==
-----END CERTIFICATE-----

GeoTrust RSA CA 2018
===============================
-----BEGIN CERTIFICATE-----
MIIEizCCA3OgAwIBAgIQBUb+GCP34ZQdo5/OFMRhczANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
... (略)
O9PuiHMKrC6V6mgi0s2sa/gbXlPCD9Z24XUMxJElwIVTDuKB0Q4YMMlnpN/QChJ4
B0AFsQ+DU0NCO+f78Xf7
-----END CERTIFICATE-----

(GeoTrust RSA CA 2018 の行以下が追加した部分です)

すると、curl コマンドで正常につながるようになりました。

$ curl https://ingage.jp
$ 

つまり、必要な CA 証明書が curl-ca-bundle.crt になかったため、ingage.jp の証明書を cURL が確認できなかった、 というのが原因でした。

WordPress

しかし WordPress からは依然としてダウンロードできません。 おそらく、curl コマンドでなく libcurl.so を使っているのでしょう。

散々探し回った結果、 /opt/bitnami/apps/wordpress/htdocs/wp-includes/certificates/ca-bundle.crt というファイルを見つけました。このファイルにも GeoTrust RSA CA 2018 が含まれてい なかったので、同様に追記したところ、見事、正常にダウンロードできるようになりまし た。

まとめと感想

今回は、WordPress が内部で行なっている HTTPS 通信で発生した問題を解決しました。

まさか WordPress 自身が CA 証明書を持っているとは思っていませんでした。 WordPress は知識の少ない人も含めていろんな人を対象にしていると思っているのですが、 そのわりにはいろいろ難しいし知識が必要だなぁ、と感じています。 皆さんはどうでしょうか?

ではまた。

追記

この問題、実は Apache に中間証明書を設定していなかったことが発覚し、 設定することで解決しました。

設定方法については、このページの 4. が参考になると思います。

Bit Journey さんに遊びに行ってきた

f:id:kizashi1122:20180206113531j:plain

東京出張が入ったこともあり、Bit Journey さんに遊びにいってきました。 @gfx こと藤さん、@michimochi こと道川さんとランチがてら色々お話しさせていただきました。

BJさんのサービス https://kibe.la と弊社のサービスはともに SaaS というだけでなく、利用技術に共通点が多く、非常に有意義な情報交換ができました。 といっても、書けることは少ないのでごく一部。

  • API Keyの管理大事
  • Twitter PWAがんばってる
  • SAML 大変
  • Intercom は Slack 連携をやり直すと通知に会社名が含まれるようになる(なった)

ほとんど情報量ないな・・・。

次回は、他のSaaS企業の方ともお会いできそうで楽しみです。