Amazon S3 Presigned URLを使用して、Herokuに大きなファイルをアップする方法

# はじめに
こんにちは！
SalesforceやHerokuの導入・開発を行っている株式会社Prazto(プラート)の代表をしております[芳賀](https://twitter.com/hagasatoshi1 "芳賀")と申します。

本日は、Herokuで構築した顧客向けポータルにサイズの大きなサイズのファイルをアップする場合の留意点と、S3 Presigned URLを使用したその留意点の回避方法についてご紹介をいたします。

# 記事の要約
- 「インフラエンジニアがいらないPaaS」Herokuの特徴と留意すべき事項
- Herokuのポータルに大きなファイルをアップする際の留意点
- S3 Presigned URLを使用すると、無理なく実装することが可能
- Heroku Connectを使用してSalesforceと連携する場合にも活用できる

# 「インフラエンジニアがいらないPaaS」Herokuの特徴
まず本題に入る前に、Herokuの特徴について簡単におさらいします。

数多くあるPaaSの中でのHerokuの特徴のわかりやすい表現として、**「インフラエンジニアがいらないPaaS」** が私はとてもしっくりきます。 インフラ管理の部分が開発者から隠蔽化され、無意識に最適な構成が設定・運用されるため、アプリケーション構築に集中することができる為です。

私はインフラは苦手なのですが、Herokuに限っては数年間実際に無理なく運用できた経験があり、継続的な改修で細かいアップデートを繰り返すような場合には本当にHerokuに助けられました。

- **Herokuの特徴「インフラ管理がとても簡単」**
    - **Dynoというシンプルな性能単位**
        - アプリケーションの性能を、**[Dyno](https://jp.heroku.com/dynos "Dyno")** という単位の数だけで決定することができ、CPUやメモリなどの詳細なプロビジョニングは行わないで構成できます。ロードバランサーなども意識せずとも自動的に設定されます。
    - **Gitと高度に連携したデプロイ**
        - Herokuサービスに内包されるGitとアプリケーションサーバーとが高度に連携しており、**[Procfile](https://devcenter.heroku.com/ja/articles/procfile "Procfile")** に起動時のコマンドを記載して、開発資源をHeroku Gitにgit pushすれば、依存関係の解決やCPUの再起動等が自動的に実行され、最新資源でのWebアプリケーションが自動的に再起動されます。

このような特徴が「インフラ管理がとても簡単」につながっているのですが、表裏一体で通常のPaaSにはあまり見られない制約も多くあります。高度に管理されたPaaSだからこそ、その制約を正しく理解して、正しいユースケースにHerokuを適用すべきということですね。

- **Herokuで開発を行う際に意識すべき代表的な制約**
    - **30秒でのリクエストタイムアウト**
        - 公式リファレンスは **[こちら](https://devcenter.heroku.com/ja/articles/request-timeout "こちら")** 
        - Web Dynoはユーザーからのリクエストを受け付けて処理を開始してから30秒以上経過しても処理が終了しない場合には、そのリクエスト処理は強制的に終了されます。
    - **ファイルストレージが無い(リブートすると消える)**
        - ユーザーからHerokuにファイルをアップして保存するストレージ領域は無いため、そのような機能要件がある場合にはAmazon S3のような外部ストレージを検討する必要があります。サーバサイド処理でファイルシステム内に保存したとしても、Dynoがリブートするとそのファイルは消えてしまいます。リブート後はHeroku Gitの資源と完全に一致します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41156/db8aa526-23f8-0e76-af41-dd29c4a270b3.png)

これらの制約を踏まえて、前述した構成について考えたいと思います。

# Herokuポータルで、大きなファイルをアップロード／ダウンロードしたい
本記事では、以下のような機能要件の実装方法を考えたいと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41156/65f0cc97-2df4-610a-561b-97b58e116da9.png)


直感的に以下のような実装方法が考えられますが、大きなファイルを前提に考えると上手くいきません。

- 画面からファイルをアップして、Herokuサーバーのファイルシステムに保存する。
- 画面からファイルをアップして、HerokuのサーバサイドからAmazon S3にAPIを使用してファイルを保存する。

### 画面からファイルをアップして、Herokuサーバーのファイルシステムに保存する？
この方法は、前述の制約によって日次のDynoのリブートの後、次の日にはアップしたファイルは消えてしまいます。


### 画面からファイルをアップして、HerokuのサーバサイドからAmazon S3にAPIを使用してファイルを保存する？

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41156/2258017a-3fb1-cb00-367f-3ebeb3c18acd.png)

一見するとシンプルな構成でOKそうですが、大きなファイルを前提に考えると上手くいきません。

- Web Dynoの30秒制限にかかってしまい、処理が途中で強制終了する可能性がある。
- 大容量のファイルがアプリケーションサーバーを経由するため、アプリケーションサーバーに高い負荷がかかってしまう。



# S3 Presigned URLを使用したファイルのアップロード／ダウンロード
S3 Presigned URLがどういう機能なのかは、とてもわかりやすく纏めてある記事がありましたので、こちらを参考にして下さい。

https://qiita.com/tmiki/items/87697d3d3d5330c6fc08

こちらの記事の言葉をそのまま引用させていただきますと、以下のような特徴を持っています。

- S3上にあるファイルを一時的に不特定多数に公開したい場合や、IAM Userアカウントを持っていない人に対して一時的にファイルのダウンロード／アップロードさせたい場合があります。このような場合に用いることができる手段として「S3 Presinged URL」があります。
- Presigned URLは有効期限を持たせることができる
- AWS Credentialsを持たないユーザに対してS3 Bucketに一時的にアクセスさせることを目的として発行する、一時的なURLといえます。

クライアントから直接S3にアップする構成も特徴となっており、これにより大容量ファイルのアップにおいてもアプリケーションサーバーに負荷をかけずに実現することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41156/515080cb-43b0-a298-18ea-8dd4414b2667.png)


Herokuのサーバサイド処理でPresigned URLを発行しクライアントに返却、クライアントからPresigned URLを使用してS3にファイルをアップロードする構成です。

**システム構成としては、ブラウザから直接S3にアップしているため、Web Dynoの30秒にはこのアップロード時間は含まれず、またアプリケーションサーバーに負荷をかけることもありません。**

大きなファイルのダウンロードについても、以下のようにクライアントから直接S3からのダウンロードになるため、同じように留意事項の回避につながります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41156/cbfdfeb6-f8a5-4f00-4ce9-81e5e07d9747.png)


# Heroku Connectを使用してSalesforceとの連携システムを構築する場合にも便利
## SalesforceとHerokuでの構成の典型例
Salesforceは言わずと知れた世界No.1のCRMでさまざまな事がノーコードで出来る素晴らしいプロダクト、Herokuは前述のようにアプリケーションの構築のみに集中出来るプラットフォームで、この２つを組み合わせることで魅力的なポータルサービスを効率的に構築可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41156/9c21773b-ba93-6b02-feb1-fcc59e9ab19f.png)


- SalesforceとHerokuでの構成の典型例
    - 社内向けに、ノーコードで構築・改修ができるようにSalesforceで構築する。
    - お客様向けに、リッチな画面をHerokuで構築する。
    - Heroku Connectを使用して、HerokuとSalesforceのデータを双方向連携する。


このようなシステムを構築する際に、多く機能要件として挙がるのが今回のような大きな
ファイルの連携です。

## Salesforce側でもS3 Presigned URLを使用したファイルのダウンロードは比較的簡単に実装可能

Salesforceから社内ユーザーが大きなファイルをダウンロードする際にも同じような配慮が必要です。
といいますのも、HerokuのWeb Dynoの30秒制限と同じく、Salesforceにもガバナ制限という強い制限があるため、単純にApexで直接ファイルを取得しようとしても処理中にガバナ制限に抵触して異常終了することが予想されるためです。

Salesforceを前提にした実装でも、比較的簡単に同じような実装は可能です。

- `S3添付ファイル(S3Attachment__c)`という名称でカスタムオブジェクトを作成して、S3のファイル１つに対して１レコードを作成する。
- S3Attachment__cの拡張VFページを作成して、参照画面をこのVFで上書き設定する。
- コントローラ拡張の処理で、このレコードの参照時に、S3 Presigned URLを発行してそのURLにリダイレクトするように実装する。

これにより`S3添付ファイル(S3Attachment__c)`のレコードを参照した際に、S3 Presigned URLにリダイレクトされるようになります。リダイレクトですのでクライアントから直接S3 Presigned URLへアクセスするため、ガバナ制限には抵触しない形で大容量のファイルのダウンロードが実施されることになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41156/1a4636cc-66e8-5767-0eb6-c216c21d2ec0.png)

## Heroku ConnectでファイルのURLのみをSalesforceに連携する
以上より、Heroku ConnectではURLのみをSalesforceに連携してサイズの大きなファイル本体は連携せずに、以下のような構成で機能要件を実現可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41156/8175c77a-2516-65a9-eec3-3d5cad87a191.png)

- Herokuポータルでは、Presigned URLを使用してS3にファイルをアップロード
- アップロードしたファイルのURLをHeroku Postgresに登録して、Heroku Connect経由でSalesforceに連携する
- Salesforce側では、Presigned URLを使用してS3からファイルをダウンロード

# 最後に
今回は、Herokuを活用する際のTipsとして、Amazon S3を使用した便利な方法をご紹介させていただきました。

弊社はSalesforceのコンサルティングパートナーなのですが、以下のような導入案件に多く携わっているような気がしています。
- しっかりとCRMとしての標準機能を利活用した、SalesCloud/ServiceCloud/....の新規導入
- 外部システムとのインテグレーション

今後も活用できそうなパターンがありましたら、また記事として書きますので、お役に立てたらメチャクチャ嬉しいです！
(次回はSalesforceとTableauを連携した複雑なKPI/メトリクスの可視化の方法を書きたい)