ハトネコエ Web がくしゅうちょう

プログラミングやサーバー・Web制作、チームマネジメントなど得た技術のまとめ

Firebase の対抗馬?! Back4App を使ってみたよ!

1. Back4App との出会い

2022/11/28 から Heroku の無料枠が廃止されるニュースを見ながら、
「そういえば最近は似たものでどういうものがあるんだろう?」と思って、
「Heroku Alternative」でググってみました。

調べていくと、

などがありましたが、今回気になったのは、
その検索ワードで一番上に出てくる「Back4App」。

「Heroku Alternative」の検索結果は Back4App が書いているブログが一番トップ

この記事では、
Herokuの代替プラットフォームベスト10という記事タイトルのわりに、
Herokuの代替と言えるような、アプリケーションサーバーとDBサーバーがセットで提供されるようなPaaSはあまり紹介されていない印象です。

出典: https://blog.back4app.com/ja/%E5%B9%B4heroku%E3%81%AE%E4%BB%A3%E6%9B%BF%E3%83%97%E3%83%A9%E3%83%83%E3%83%88%E3%83%9B%E3%83%BC%E3%83%A0%E3%83%99%E3%82%B9%E3%83%8810/

まあ、それはともかく!

Back4App を知らなかったので、
これはどんなサービスなのか調べてみました。

2. ライバルは Firebase

彼らのTwitterアカウント の固定ツイートがこれです。

Anyone else tired of #firebase unpredictable pricing as soon as your app starts to grow?!?! This is how they do it 👉https://www.youtube.com/watch?v=rr7Z270h1zQ

日本語に訳すと、

アプリが成長していくと想定外な価格になっていく Firebase にうんざりしていません?!
これが奴らのやり方です 👉 https://www.youtube.com/watch?v=rr7Z270h1zQ

なかなか攻めていますね?!?!

実際、データベースがあったり、ジョブがあったり、
プッシュ通知を送れたりなど、Firebase と同じような機能があるように、
このツイートを見る前から感じていました。

Back4App のデータベース

謎にブロックチェーンの機能もあります……(笑)

3. Back4App は Parse のホスティングサービス

Back4App をかんたんに言うと、実はこれ
Parse Serverホスティングサービスです!

その Parse とはなにかと言いますと、Parse Inc. が作り上げた、
モバイルアプリ開発のためのプラットフォームです。

Parse Inc. は2011年に設立され、2013年に8500万ドルで Facebook(現在のMeta)社に買収され、
2017年にサービス終了した会社です。(参考: Parse, Inc. - Wikipedia

そこが、サービス終了前の2016年2月に自社のサービスをオープンソースとして公開した*1のが Parse Server で、
それを管理するダッシュボードとして2016年3月にリリースされた*2のが、 Parse Dashboard なのです。

ブロックチェーン機能なんかは Parse のドキュメントを見た限りなさそうでしたので、
Back4App は、Parse Server をベースに拡張をおこなったホスティングサービスと言えるでしょう。

それゆえ、一部の機能は、Back4App のドキュメントに書かれていない代わりに、
Parse Platform のドキュメントに書いてあったりします。
これから Back4App をさわる方は、このことを覚えておくといいでしょう。

4. 実際にさわってみた

少しだけですが実際にさわってみました。

トップページはこんな感じで、
GoogleGitHub のアカウントで一瞬で会員登録して始められます。

Googleログイン、GitHubログイン、メールアドレス&パスワードログインに対応

最初は React で挑戦してみたのですが、
Parse の JavaScript SDK にバグがあって save や first メソッドが機能しなかった(現在は修正済み)ので、
Flutter で挑戦してみることにしました。

👆 データベースのテーブル(ParseではClassと言う)の一覧取得とデータ挿入をおこなうもの

👆 Google OAuth ログインで、Parse の User, Session テーブル(Class)の作成をおこなうもの

特にGoogleログインについてはドキュメントが少なくて苦労しました。

5. さわってみての感想

Firebase に比べると情報がやや少ないですが、
ドキュメントはわりと充実していると思いました。
Back4App のドキュメントを見るべきか Parse のドキュメントを見るべきかわからない部分があるのが、やや不親切です。

データベースはとても見やすいです。
Webページ上でそのままデータを確認できます。

Firebase も最近になってデータベースが見やすくなりましたが、
Back4App のデータベースは、カラムの追加・データの追加・フィルターなどが直感的におこなえ、この点は Firebase よりとても優れています。

各データへのアクセス制限についても、Firebase は複雑なルールの記述が必要なのですが、
Back4App は Web UI や API などで簡潔に設定できます。
結果的に よりセキュアに作りやすいのは Back4App かな と思いました。

Back4AppのデータベースのUI。フィルター機能などもある

Firebaseのデータベースは、1個1個クリックしないと情報が見られず不便だった

2022年10月に搭載された『クエリビルダー』で、ようやく一覧で見られるようになった

6. 親切なサポート

Back4App のことについてツイートしたら、
「わからないことがあったら気軽に聞いてみてくれ」と、カスタマーサクセスエンジニアの方からリプライが届きました。

試しにACL(アクセスコントロール)の挙動についてわからないことがあったので聞いたら、
なんと40分後にわかりやすい回答が返ってきて、これにはとても驚きました!

てっきり「ありがとう、それについてはサポートに連絡してくれ! 👉 https://..../support」のような返信がされるだろうと思っていたから、
大変驚きました。こういう対応をしてもらえると、サービスへの好感度が上がりますね!

7. 料金がネックか

料金については Firebase の方が優れていると思います。
Back4App の無料プランでは、リクエスト数が月に25,000回までと決まっています。
作りにもよりますが、公開したウェブアプリケーション
データベースの読み出し・書き込みが月に25,000回で済むことはなかなかないと思います。

1日800回程度ですからね。
Firebase の Firestore の無料枠は『書き込み 2 万件/日』『読み取り 5 万件/日』ですから、
逆にこちらはそうそう超えません。
Firebase を本番運用したことある人ならわかると思いますが、Firebase を個人で使って無料枠を超えることはあまりありません。

ここがあるので、個人的にはやはり Firebase を選んでしまうかなあ……というのが思うところです。

Parse Server を自分でホスティングするというのも、なかなかアリな選択肢かもしれませんね!
GraphQL サーバーを Web UI で管理できるところが便利そうです。

基礎から学ぶ Flutter

基礎から学ぶ Flutter

Amazon

AWS Lambda で SES を通してメール送信するときに送信元を文字化けさせない

送信元に日本語を使うと文字化けが……

AWS Lambda を使って、AWS SES API を介したメール送信をしようと思いました。

import * as AWS from 'aws-sdk';

const params = {
  Source: 'テスト送信元 <sender@example.com>', // 送信元
  Destination: {
    ToAddresses: [
      'receiver@example.com',
    ],
  },
  Message: {
    Subject: {
      Charset: 'UTF-8',
      Data: 'Test email',
    },
    Body: {
      Text: {
        Charset: 'UTF-8',
        Data: 'こんにちは、テストです',
      },
    },
  },
};

await new AWS.SES().sendEmail(params).promise();

いろいろ省略していますが、おおむねこんな感じでメール送信しました。
(しっかりしたものは https://aws.amazon.com/jp/premiumsupport/knowledge-center/lambda-send-email-ses/ をご覧ください)

が、しかし……

送信元の名前がめちゃくちゃ文字化けしました。

=?charset?encoding?encoded-text?= の形式で記載する

「encodeURIComponent とか必要なのか〜?」といろいろ試してみても上手くいかなかったのですが、
公式ドキュメントを読んだら、謎を脱する一歩を進められました!

https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/SES.html#sendEmail-property

Amazon SES does not support the SMTPUTF8 extension, as described in RFC6531. For this reason, the local part of a source email address (the part of the email address that precedes the @ sign) may only contain 7-bit ASCII characters. If the domain part of an address (the part after the @ sign) contains non-ASCII characters, they must be encoded using Punycode, as described in RFC3492. The sender name (also known as the friendly name) may contain non-ASCII characters. These characters must be encoded using MIME encoded-word syntax, as described in RFC 2047. MIME encoded-word syntax uses the following form: =?charset?encoding?encoded-text?=.

RFC 2047 に従って、
=?charset?encoding?encoded-text?= みたいな書き方をすればいいよ! とのこと。

……と言われても、RFC 2047 のドキュメントが長くてわかりづらいなあ、と思っていたら、
SendGrid のサイトがいい感じに説明してくれていました。

https://sendgrid.kke.co.jp/blog/?p=10958

件名が「こんにちは」という内容を、文字セット「UTF-8」、エンコード方式「Base64」で指定する場合、メールソースのSubjectヘッダは次のような内容になります。
Subject: =?UTF-8?B?44GT44KT44Gr44Gh44Gv?=
(中略)
=?文字セット?エンコード方式?エンコード後の文字列?=
ここで、文字セットには、メール本文と同様「UTF-8」や「ISO-2022-JP」といった値が入り、エンコード方式には、Base64の場合「B」、Quoted-Printableの場合「Q」いずれかの値が入ります。

「なるほど!」と思って、 最初に記したコードの From 部分を

Source: '=?UTF-8?B?44GT44KT44Gr44Gh44Gv?= <sender@example.com>', // 送信元

のようにして送ってみました。

キタァ!
送信元の名前が無事、日本語で届いてくれました。

JavaScript で文字列の BASE64 エンコードをするには

地味にここも難しいです。

https://developer.mozilla.org/ja/docs/Glossary/Base64 で記載されているように、

btoa(unescape(encodeURIComponent('こんにちは')));
=> '44GT44KT44Gr44Gh44Gv'

でいけるのですが、 https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/unescape に書かれているように
unescape は基本使うべきでない、とされているのでこれを使っていいかは迷うところです。

で、他の方法もいくらか知りました。

https://qiita.com/i15fujimura1s/items/6fa5d16b1e53f04f3b06 に記載されているような

btoa(String.fromCharCode.apply(null, new TextEncoder().encode('こんにちは')));
=> '44GT44KT44Gr44Gh44Gv'

という方法もあります。

また、Lambda を Node.js で動かしているので、
Node.js の Buffer オブジェクトを使って

Buffer.from('こんにちは', 'utf8').toString('base64');
=> '44GT44KT44Gr44Gh44Gv'

とする方法もあります。

こうして Unicode 文字列を送信元に設定することが!

以上のことを踏まえて、
=?UTF-8?B?<BASE64エンコードした文字列>?= のような送信元となるよう、
BASE64エンコードを先におこなうこととしました。

import * as AWS from 'aws-sdk';

const senderNameBase64 = Buffer.from('テスト送信元', 'utf8').toString('base64');

const params = {
  Source: `=?UTF-8?B?${senderNameBase64}?= <sender@example.com>`, // 送信元
  Destination: {
    ToAddresses: [
      'receiver@example.com',
    ],
  },
  // 以下略

これで、Lambda から AWS SES を介して送るメール送信元に、
ASCIIコード外の文字列を使うことができるようになりました!!!

調べた結果だけ書いているので短い記事に収まっていますが、
ここまでたどり着くのに数時間かかりました……。

私のように困った誰かの、助けになれましたら幸いです!

rubocop-rails で to_time が怒られなかったので調査した

1. オプションで指定しない限り to_time は怒られない

コードレビューをしていたら

"2020-01-01 10:00".to_time

のようなコードがあり、
「そこは .in_time_zone の方がいいですよ」とコメントしようとして、「あれ?」と思いました。

なんで「 Time.nowTime.zone.now にしなさい」と
いつもは怒ってくれる rubocop-rails が反応してくれていないのでしょう?

そこでドキュメントを確認すると、
Rails/DateAllowToTimefalse の設定に変更しない限り、
to_time の使用を怒ることはない
とわかります。

2. to_time を使うことの問題

これは少し不思議です。
to_time を使ってしまっては、 config.time_zone で設定している
Railsアプリケーション側のタイムゾーンでなく、システム側(OS側)のタイムゾーンを見に行ってしまいます。

root@9f373dcf6715:/app# export TZ=America/Los_Angeles
root@9f373dcf6715:/app# bundle exec rails c

[1] pry(main)> Time.now
=> 2022-06-15 08:23:08.607609413 -0700
[2] pry(main)> Time.zone.now
=> Thu, 16 Jun 2022 00:23:16.122905467 JST +09:00

[3] pry(main)> "2020-06-01 10:00".to_time
=> 2020-06-01 10:00:00 -0700
[4] pry(main)> "2020-06-01 10:00".in_time_zone
=> Mon, 01 Jun 2020 10:00:00.000000000 JST +09:00

[5] pry(main)> Date.current.to_time
=> 2022-06-16 00:00:00 -0700
[6] pry(main)> Date.current.in_time_zone
=> Thu, 16 Jun 2022 00:00:00.000000000 JST +09:00

Rails/Date の cop は、
Date.today でなく Date.current を使え」だとか、
Time.now でなく Time.zone.now を使え」だとか、
システム側のタイムゾーンでなくアプリケーション側のタイムゾーンを使うよう統一を求めるものです。

であれば、 to_time は使うべきでないはずです。

3. 原因は DateTime にあり

ここでドキュメントに戻りましょう。

AllowToTime is true by default to prevent false positive on DateTime object.

とあります。
DateTimeオブジェクトにおける誤検知を防ぐため」……? どういうことでしょう。

その答えはこちらの issue にあります。

https://github.com/rubocop/rubocop-rails/issues/288

要約すると、DateTimeクラスのときに、
すでにアプリケーション側のタイムゾーン情報を持つことが出来て、
そのタイムゾーン情報は to_time しても引き継がれるから問題ないはずだ、という主張です。

[1] pry(main)> DateTime.current
=> Thu, 16 Jun 2022 00:50:18 +0900
[2] pry(main)> DateTime.current.to_time
=> 2022-06-16 00:50:23.70027614 +0900

なるほど、たしかにこのことを考えると、
to_time メソッドの使用を必ず怒るという挙動だと、余計なおせっかいになってしまうことがありますね。

# ちなみに DateTime.now はシステム側のタイムゾーンを使ってしまう
[3] pry(main)> DateTime.now
=> Wed, 15 Jun 2022 08:50:45 -0700
[4] pry(main)> DateTime.now.to_time
=> 2022-06-15 08:50:52.038531225 -0700

4. しかし DateTime は deprecated

……なのですが、2020年9月の Ruby のコミット
https://github.com/ruby/date/commit/58ca6e6a3ee20c72a77266e0f74920b12a06ee9d にて、
DateTime は deprecated(非推奨) になりました。

話の発端: https://bugs.ruby-lang.org/issues/15712

というわけなので、もう AllowToTimefalse をデフォルトにしてもいいのではと思いまして、
issue を作っておきました。
https://github.com/rubocop/rubocop-rails/issues/715

すぐには変わらないと思うので、
ひとまずは .rubocop.yml の設定を付け足すことで対応したいと思います。

原因がわかってスッキリしました。