こんにちは。
ライジングサン・システムコンサルティングの岩佐です。
この記事では、FileMakerプラットフォームで開発する カスタム App に Dropbox との連携機能を組み込むにあたっての実践テクニックをご紹介します。
Dropboxとの連携と聞くと、中にはFileMakerのデータベース(fmp12ファイル)を、Dropboxを使って共有する方法と思われる方もいらっしゃるかも知れません。しかし、このDropboxを用いたファイル共有は、データベースを破損させるリスクが極めて高い方法なので、絶対に行わないでください。
※データベースの共有は、必ずFileMakerServerを利用しましょう。
今回ご紹介するテクニックは、FileMakerの標準機能で開発する場合、一般的にはオブジェクトフィールドに格納する外部ファイルを、Dropboxに格納するテクニックになります。
記事の最後には、サンプルアプリケーションのダウンロードもございますので、ぜひお楽しみください。
目次
1.オブジェクトデータをDropboxへ保存する利点
もかかわらず、なぜわざわざDropboxと連携する必要があるのでしょうか?
FileMaker開発者がDropboxとの連携を積極的に検討すべき理由は大きく3つあります。詳しくは弊社ブログのこちらの記事をご参照ください。
この記事では、より具体的にDropboxとの連動を積極的に検討すべきケースと、FileMakerの標準機能のみでも問題ないケースを比較して、Dropboxと連携するメリットの考察を深めてみたいと思います。
1-1. オブジェクトフィールドでも問題のないケース
FileMakerのオブジェクトフィールドをそのまま使ってもさほど問題にならないのは、保存されるデータが年間数GBレベルの比較的小さな規模のソリューションです。また、オブジェクトフィールドに保存する1つのファイルのサイズが数KB、数MBレベルの比較的小さなファイルが主なケース。
これぐらいの規模であれば、昨今のネットワークインフラや標準的なハードウェア、もしくはクラウドプラットフォームを利用していれば、さほど大きな問題にはならないでしょう。
もちろん、オブジェクトフィールドの外部保存を有効化することによるfmp12ファイルの肥大化やフラグメンテーションの抑制等の基本的な対策は極めて重要です。しかし、Dropboxとの連携を積極的に検討する規模では無いでしょう。
1-2.Dropboxとの連携を積極的に検討すべきケース
一方、Dropboxとの連携を積極的に検討すべきケースは、オブジェクトフィールドに格納する対象データの合計が、年間で数百GB、もしくは数TBレベルになる大規模なソリューションの場合。そして、1つのオブジェクトフィールドに保存されるファイルの大きさが、動画や音声、もしくはCADデータのような数十MBから数百MB、時にはそれ以上のサイズになる場合です。
こういったケースの発生が想定される カスタム App の開発では、Dropboxとの連携を積極的に検討すべきです。
まず、年間で数百GB、数TBレベルでオブジェクトデータが増加すると、FileMakerServerの保存容量もさることながら、バックアップやリストアなど障害時を想定とした運用が極めて難しくなります。
また、自前でこれだけのストレージを用意するとなると、イニシャルコストの増大も無視できない問題になります。
更に、1つのファイルサイズが大きくなると、アップロード・ダウンロードの時間が長くなります。当然その処理の間は、FileMakerクライアントの操作が一切できないので、エンドユーザにとっては極めてストレスフルな状態になってしまいます。
このようなケースが想定される カスタム App の開発は、積極的にDropboxとの連携を考慮すべきです。
2.実践的な7つのテクニック
ここからは、実際にDropboxと連携するソリューションを開発する上で重要な、7つの実践テクニックについて説明していきます。
この先の内容を理解するためには、基本的なWebAPIの知識、FileMakerプラットフォームのバージョン16より実装された、【URLから挿入】スクリプトステップのcURLオプションの使い方に関する基礎的な知識が必要です。
もしこのあたりの知識、経験にあまり自信がない場合は、株式会社フルーデンス・小巻氏が公開しているブログ記事、及びWebセミナーの動画が参考になります。ぜひ、ご一読ください。
さて、Dropboxとの連携が必要となるような規模のソリューションは、ほぼ100% と言っていいほど、FileMaker Server にホストして利用することが前提の カスタム App になると思います。
ですので今回ご紹介するテクニックも、FileMaker Server にホストして利用することを前提としたテクニックをご紹介します。また、本記事の最後にご案内するサンプルアプリケーションも、FileMaker Server でホストした状態で利用することが前提としています。
2-1.Dropbox Businessを契約し、その配下のユーザでアプリを登録する。
Dropboxにおける情報資産。つまりファイルの所有者は、該当のファイルが保存されているストレージのユーザになります。ですので、ビジネス目的での利用であれば、必ず法人での契約を前提としたDropbox Business を契約し、その契約配下で作成したアカウントで登録したDropboxアプリを利用することが極めて重要です。
これが、スタッフ個人のアカウント配下にあるDropboxアプリとなると、そのアプリ配下にあるファイルの所有者は、該当スタッフ個人となります。これは、ビジネス利用として絶対に避けるべきです。
例えば、ある社内開発者が、まずは試しにと個人のDropboxアカウントでパイロットアプリを開発するのは問題ありませんが、パイロットアプリや試験運用が目的ではなく、実業務で実際に使う カスタム App に組み込む場合は、必ず法人名義でDropbox Businessを契約し、その配下のDropboxアプリと連携するようにしましょう。
2-2.特別な理由が無い限り、アクセスタイプはApp folderを選択する。
Dropbox Developerサイト(https://www.dropbox.com/developers/) でアプリ登録をする際に、2つ目のステップとしてアクセスタイプの選択があります。こちらは、特別な理由が無い限り、App folder配下にしておくのが良いでしょう。
もう一つの選択肢である Full Dropbox の違いは、該当のアプリがどの場所にある資産(ファイル・フォルダ)にアクセスできるかの違いになります。
2-2-1.App folder がアクセス可能な場所
App folder を選択すると、該当のアプリは、アプリオーナーのホームフォルダ配下に作成されるアプリというフォルダの更にもう一つ1の階層に作られるアプリ名のフォルダ配下のみコントロール可能になります。
例えば、登録するアプリ名がrsc_dbxContainerというアプリ名で、アクセスタイプがApp folderの場合、該当アプリがアクセスできるのは、http://dropbox.com/home/アプリ/rsc_dbxContainer/ 以下の領域のみ自由にコントロールできます。
2-2-2.Full Dropbox がアクセス可能な場所
一方、Full Dropbox を選択すると、該当アプリが紐付いているDropboxアカウント配下の全てのDropbox アップロードファイル、及びフォルダへのアクセスが可能となます。具体的には、http://dropbox.com/home/ 以下、全ての領域へのアクセスが可能となります。
2-2-3.アプリのアクセス権は、必要最低限にしておく
Full Dropbox を選択することは、アプリに必要以上の権限を付与することの裏返しでもあります。Dropbox のデベロッパーガイドを読むと、アプリには必要最低限の権限のみを与えておくことが望ましいとされていますし、一般的なセキュリティポリシーを考慮しても、必要最低限の権限のみを与えるという考え方は常識の範疇ではないかと思います。
FileMakerプラットフォームの標準機能でいうオブジェクトフィールドのみを、ストレージへの投資やネットワークトラフィックの分散等を目的として、Dropbox と連携するのであれば、平たく言うとオブジェクトフィールドの「外部保存先」がDropboxになるだけと解釈できます。
であれば、App folder 配下にアクセスできるだけで十分なはずです。
Full Dropbox の権限が必要なのは、アプリと人の両方が同じレベルで読み書きするディレクトリにアクセスするような機能要件が発生したときでしょう。
ただし、アプリとエンドユーザ双方が、自由にアクセスできる場所に、ある一定の理屈に基づいたディレクトリ構造でデータを保存していた場合、人間の誤操作によって、パス構造の崩壊やデータの誤削除が発生し、思わぬバグやトラブルが発生する可能性もあります。
ですので、アクセスタイプは App folder を選択しておき、さらに登録アプリ配下のフォルダについては、ローカルマシンとの同期を外しておく。そうすれば、こういった誤操作などによるデータの論理破損リスクをかなり低減することができます。
また、App folder 配下でのアプリでは、share_folder メソッドなど、いくつかのAPIメソッドが使えない制限がかかっています。これは、APIを介さずにアップロードされるファイルを制限することが目的なのではないかと考えられます。
つまりアプリ以外、Dropbox の標準UIや各OSのファイルシステムからの操作も許すようなソリューションを開発したい場合は Full Dropbox を、100% アプリからコントロールするソリューションを構築したい場合は、App folder を選択すると良いでしょう。
2-3.AccessTokenは暗号化し、完全アクセス権でのみアクセス可能にしておく。
Access Token とは、FileMaker の カスタム App から Dropbox API を利用するための「合言葉」のようなものです。この合言葉が合致することによって、Dropbox API は「その合言葉は◯◯さんの□□アプリだから、このディレクトリ配下で、このAPIを実行可能」といった具合に、データへのアクセス認証と、アクセス権レベルを特定します。
ですので、Access Token は最も漏洩させてはならない情報です。Dropbox API の場合、この Access Token が漏洩すると、該当のアプリ配下にあるデータには全てアクセス可能となります。ですので最悪のケースだと、登録している全ファイルをインターネット経由で持ち去らる可能性があります。
よって、Access Tokenはしっかりと保護するのが開発者としての義務です。
幸いにして、FileMakerプラットフォームでは、 Ver16より各種暗号化アルゴリズムを簡単に利用できる組み込み関数が実装されました。今回のサンプルアプリケーションでは、この中からCryptEncrypt関数とCryptDecrypt関数を用いて、FileMakerの完全アクセス権を持つアカウントでしか、FileMakerのフォルダ内に保存している Access Token にアクセスできない制御を実装しています。
また、API 実行時にかわされる Dropbox と FileMaker クライアントの会話がSSLで暗号化されているように、FileMaker Server と FileMaker クライアントとの間も、同じくSSLで暗号化することが重要です。
2-4.アップロードするファイルの選択時も含めてオブジェクトフィールドは使わない。
Dropboxにアップロードするファイルを、GUIを通してエンドユーザに選択させる場合、FileMakerでは【ファイルを挿入】スクリプトステップを使って、アップロード対象ファイルを特定する動きが一般的です。
この「ファイルを挿入」ですが、Ver16より、挿入先として変数が指定可能となりました。つまりオブジェクトデータを変数に格納し、そのままDropboxへアップロードすることが可能になったのです。
この素晴らしいところは、グローバルフィールドを一切使わないところです。グローバルフィールドを使わず変数のみで処理ができれば、すべてのデータ操作はローカルマシン上で完結します。
ですので、FileMakerクライアントとFileMakerサーバ間でムダなネットワークトラフィックが発生しません。これは、FileMakerServerにホストされている場合は極めて重要なテクニックになります。
この記事の最後でダウンロードできるソリューションは、FileMakerServerにホストして利用することが前提のソリューションになっています。そして、ここに記述している通りDropboxへアップロードするファイルを選択し、そしてDropbox API を使ってファイルをアップロードするまで、グローバルフィールドは一切利用していません。
2-5.ファイルアップロード後のDropboxとの会話はDropboxIDを使う。
FileMaker の カスタム App から、Dropboxへファイルをアップロードするときには、upload メソッドを使います。そして、このメソッドが成功すると、以下のようなJSONのレスポンスが帰ってきます。
{
"name": "Prime_Numbers.txt",
"id": "id:a4ayc_80_OEAAAAAAAAAXw",
"client_modified": "2015-05-12T15:50:38Z",
"server_modified": "2015-05-12T15:50:38Z",
"rev": "a1c10ce0dd78",
"size": 7212,
"path_lower": "/homework/math/prime_numbers.txt",
"path_display": "/Homework/math/Prime_Numbers.txt",
"sharing_info": {
"read_only": true,
"parent_shared_folder_id": "84528192421",
"modified_by": "dbid:AAH4f99T0taONIb-OurWxbNQ6ywGRopQngc"
},
"property_groups": [
{
"template_id": "ptid:1a5n2i6d3OYEAAAAAAAAAYa",
"fields": [
{
"name": "Security Policy",
"value": "Confidential"
}
]
}
],
"has_explicit_shared_members": false,
"content_hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}
いくつもの情報が返ってきますが、この中で極めて重要なのは idです。
このidは、Dropbox上にある全てのファイルに一意に割り当てられているUUIDのようなものと考えれば良いでしょう。ですので、目的のファイルをダウンロードしたり、上書き更新したり、削除したりするときには、このID用いると、API経由でのファイルへのアクセスがとても簡単になります。
今回のサンプルアプリケーションでは、FileMaker側のテーブルには、このDropboxIDのみを保持しておき、ダウンロードや削除など、連携に必要な様々なAPIで利用しています。ぜひ参考にしてみてください。
2-6.データのダウンロードには get_temporary_link メソッドを使う。
FileMakerの カスタム App から、Dropboxに保存されているファイルをダウンロードする場合、普通にAPIのリストから判断すると 一般的には download メソッドを使いたくなるのではないかと思います。
しかし、download メソッドの戻り値は、まさにバイナリデータ。FileMaker プラットフォーム の言葉で表現するとオブジェクト型のデータです。ですので、その戻り値を受け取るには、どうしてもオブジェクトフィールドが必要になります。
先にも申し上げたとおり、FileMakerServerにホストしているソリューションでオブジェクトフィールドを用いる場合、たとえそのオブジェク型フィールドがグローバル保存だったとしても、必ずサーバ側にデータが転送されることになります。
このようなムダなネットワークトラフィックを避けるためには、グローバルフィールドを使わないでデータをダウンロードする必要があります。
それを実現するのが get_temporary_link メソッドです。
このメソッドを実行すると、一時的なダウンロード専用のURLが発行されます。具体的に、get_temporary_link メソッドを実行した場合のDropbox API からのレスポンスは以下のとおりです。
{
"metadata": {
"name": "Prime_Numbers.txt",
"id": "id:a4ayc_80_OEAAAAAAAAAXw",
"client_modified": "2015-05-12T15:50:38Z",
"server_modified": "2015-05-12T15:50:38Z",
"rev": "a1c10ce0dd78",
"size": 7212,
"path_lower": "/homework/math/prime_numbers.txt",
"path_display": "/Homework/math/Prime_Numbers.txt",
"sharing_info": {
"read_only": true,
"parent_shared_folder_id": "84528192421",
"modified_by": "dbid:AAH4f99T0taONIb-OurWxbNQ6ywGRopQngc"
},
"property_groups": [
{
"template_id": "ptid:1a5n2i6d3OYEAAAAAAAAAYa",
"fields": [
{
"name": "Security Policy",
"value": "Confidential"
}
]
}
],
"has_explicit_shared_members": false,
"content_hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"link": "https://dl.dropboxusercontent.com/apitl/1/YXNkZmFzZGcyMzQyMzI0NjU2NDU2NDU2"
}
レスポンスの一番最後に link という要素が確認できると思います。この link 要素の値が一時的に発行されたダウンロード専用のURLです。このダウンロード専用のURLは、発行から4時間で期限切れとなります。
このlink要素の値になっているURLを、JSONGetElement関数で取り出し、FileMakerの【URLを開く】スクリプトステップで開くと、OSの標準になっているブラウザが起動し、すぐにダウンロードが開始されます。ちなみにこのダウンロード専用リンクからのデータダウンロードは、おそらくdownload メソッドを使ったときよりも、かなりダウンロードスピードが速いと思います。
こうすることで、オブジェクトフィールドを一切使うことなく、データをダウンロードすることが可能になります。
さらにget_temporary_link を用いるもう一つの利点は、FileMakerクライアントとは別プロセスでダウンロードが実行されることです。ですので、ダウンロード中でも、FileMaker上では別の作業や処理を継続して行うことが可能です。これは特に大きなファイルをダウンロードするときの待ち時間を、別の作業に割り当てることによってユーザの待ち時間ストレスを軽減が図れる極めて重要なテクニックです。
2-7.iOSデバイスからのアップロードはローカルファイルで実行する。
ご存知の通り、iOS上で動くFileMakerGoには、【デバイスから挿入】というとても便利なスクリプトステップがあります。このスクリプトステップは、iOSデバイスのカメラやマイクから、写真・動画、及び音声ファイルをオブジェクトフィールドに直接挿入することができます。
しかし、このスクリプトステップで挿入する対象のオブジェクトフィールドが、FileMaker Server上にホストされたファイル内にある場合は注意が必要です。理由は、2-4.アップロードするファイルの選択時も含めてオブジェクトフィールドは使わない。にも書いたとおり、デバイスを通してオブジェクトフィールドに格納されるメディアデータが、一時的にしろFileMakerServerに転送されるからです。
もちろん、デバイスから挿入されるメディアデータが、解像度の低い写真程度ならあまり問題にならないと思います。
しかし、動画や音声など、一度にアップロードするファイルのサイズが数十MBから数百MBになる場合は注意が必要です。そして更に注意すべきは、数十名、数百名のユーザが、一斉に似たような時間帯に動画や音声のアップロード機能を使うような運用が想定されるケースです。こういったケースでは、その時間帯に、大量のネットワークトラフィックがFileMakerServerに集中することになり、深刻な応答性能の低下が懸念されます。
この問題を回避するには、各iOSデバイス上にデプロイされたローカルファイル(fmp12)が保有するオブジェクトフィールドに、デバイスからのメディアデータを格納します。そして同じくローカルファイルから、直接Dropboxへアップロードする必要があります。
2-7-1. iOSデバイスにおけるメディアデータのアップロード
ただし、ローカルファイルでの処理が必要なのは、メディアデータのアップロードのみで、それ以外のデータ処理は、FileMakerServerで制御したほうが効率的です。
ですので、iOSデバイスからのメディアデータの操作は、【デバイスからの挿入】とDropboxへのアップロードのみをローカルデバイスで実行するという少しトリッキーな処理を実装する必要があります。
以下、今回のサンプルソリューションで実際に採用している処理の動きを簡単に説明します。
2-7-2.各リソースの配置状態
- カスタム App はFileMakerServerにホストされているdbxContainer.fmp12
- ユーザは、iOSデバイス、デスクトップにかかわらず、このdbxContainer.fmp12を開いて カスタム APP を起動する。
- dbxContainer.fmp12 には、Dropboxにアップロードしたファイル情報を格納する DropboxDataSetテーブル、DropboxDataテーブル以外に、Resourceテーブルが格納されている。
- Resourceテーブルには、DropboxControlerというオブジェクトフィールドがあり、そのオブジェクトフィールドには、DropboxController.fmp12ファイルが格納されている。
- このDropboxController.fmp12は、iOSデバイスで動画や音声をアップロードする際に、iOSデバイスのテンポラリフォルダにエクスポートされ、ローカルデバイスで起動する。
2-7-3.iOSデバイスにおける処理フロー
- ユーザは、dbxContainer.fmp12に格納されているレイアウト上に配置された「動画を撮影」ボタンをタップする。
- カスタム App は、Resource::DropboxContollerフィールドに保存されているDropboxController.fmp12を、Get ( テンポラリパス ) で取得できるフォルダにエクスポートし、そのエクスポートしたfmp12ファイルを開く。
- ユーザは、テンポラリフォルダにエクスポートされたDropboxContoller.fmp12上で、動画や音声を記録後に、アップロードボタンをタップする。
- カスタム App (DropboxController.fmp12)は、Dropbox API の upload メソッドを実行して、オブジェクトフィールドに格納されたメディアデータをアップロードする。
- カスタム App (DropboxController.fmp12)は、アップロードの結果を、FileMakerServerに格納されているdbxContainer.fmp12に返す。
文字に起こすとかなり複雑でわかりにくいかもしれませんが、この記事の最後にフリーでダウンロードできるサンプルの カスタム App は、完全アクセス権付きでダウンロードすることが可能なので、ぜひスクリプトデバッガで1ステップずつ追いながら、具体的な動きを確認してみてください。
3.サンプルソリューションの解説
ここからは、本記事の最後にダウンロードのご案内をしているサンプルソリューションの解説になります。具体的なアプリを動かしながらの解説が一番わかり易いと思いますので、ここからは文章ではなく、サンプルソリューションの中身を解説する動画を主体に情報をお届けします。
3-1.Dropboxへのアプリケーション登録から はじめてのファイルアップロードまで
この動画では、Dropbox.com/Develperサイトから、サンプルソリューション用のDropboxアプリを登録して Access Token を生成し、それを今回配布しているサンプルソリューションに登録して、最初のファイルをアップロードするところまでの手続きを解説しています。
3-2.Access Token の暗号化の仕組み
この動画では、Dropbox Develperサイトで生成した Access Token を、FileMakerのカスタムApp内でどのように暗号化してハンドリングしているのかについて解説します。
3-3.デスクトップソリューションにおけるアップロードの仕組み
この動画では、デスクトップアプリからのアップロード処理について解説します。とくにオブジェクトフィールドを一切使わずに、大きなサイズのファイルをアップロードするテクニックは極めて重要なので、ぜひ動画を参考にしながら、実際のソリューションで動きを確認してみてください。
3-4.デスクトップアプリケーションにおけるダウンロードの仕組み
この動画では、デスクトップアプリにおけるダウンロードの仕組みを解説します。先の実践テクニックでも述べたとおり、ここではdownload メソッドではなく get_temporary_link を使うことで、オブジェクトフィールドを全く利用せずにファイルをダウンロードするテクニックを解説します。
3-5. iOSデバイスにおけるアップロードの仕組み
この動画では、iOSデバイスのカメラから動画を撮影して、Dropboxへアップロードする仕組みについて解説します。実践テクニックにも書いたとおり、iOSデバイスで撮影した動画をアップロードする場合は、iOSデバイスのローカルドライブに撮影とアップロードのみを実行するミニアプリを一時的にデプロイして処理を実行することで、FileMaker Server へのムダなトラフィックが発生しないような工夫が施されています。
3-6.iOSデバイスにおけるダウンロードの仕組み
この動画では、iOSデバイスから撮影し、Dropboxにアップロードした動画を、同じくiOSデバイスにダウンロードして閲覧する仕組みを解説します。
4.Dropbox API コール数の制限について
最後にDropbox API を利用する上で知っておきたい API コールの制限について解説します。
APIのコール数における上限は大きく2つあります。1つは1ヶ月で実行可能なコール数の上限値。もう一つは、1分とか5分といった短時間内におけるコール数の上限値です。
4-1.1ヶ月のコール数上限
まず前者の1ヶ月間で実行可能なAPIコール数は25,000回になります。だたし、このコール数にカウントされるのは upload メソッドのみで、それ以外のAPIコールについてはカウントされません。FileMaker の カスタム App と連携するソリューションで、月間25,000回以上のアップロードが発生するような大規模なアプリはあまりないと思いますので、この上限がネックになることはあまりないと思います。
しかし、ソリューションの要求仕様によっては、初回リリースの時点等で大量のデータをDropboxにアップロードする必要がある場合も考えられます。こういった場合は、APIによるアップロードではなく、まずはロー仮マシンで必要なディレクトリ構造とファイルの配置を行い、Dropbox の標準同期アプリで、Dropbox側に転送すると良いでしょう。
4-2.Rate Limit (一定時間内におけるコール数上限)
後者の制限は Rate Limits という仕組みで制限がされています。こちらのガイドを読む限り、具体的なしきい値などが公開されているわけではなく、APIをコールしたときのJSONの戻り値で、制限値を取得する形になっているようです。
https://www.dropbox.com/developers/reference/data-ingress-guide
この Rate Limits が設けられているそもそもの背景は、短時間で大量のリクエストを送りつけてサービスをダウンさせる悪質な攻撃にDropboxが対抗するための処置と思われます。ですので、コーディングミスによる無限ループ等をやらかさない限り、この制限に引っかかることは考えづらいでしょう。
5.まとめ・サンプルソリューションのダウンロード
このブログ記事では、DropboxとFileMakerプラットフォームを連携するメリットを考察しつつ、実際のレン系ソリューションを構築する上でぜひ抑えておきたい実践テクニックについて解説してきました。
文章だけでこのテクニックを伝えるのは限界があるので、いつものようにサンプルアプリケーションを用意しております。
ぜひ、お手元にダウンロードしていただき、実際のソリューションで使われているテクニックを確認してみてください。
※サンプルソリューションを動かすには、FileMakerのVer17以降が必要です。
また、サンプルソリューションはロックフリーになっています。
ダウンロードURLには、完全アクセス権のアカウント名とパスワードを記述しています。こちらでサンプルソリューションを開いていただければ、全ての中身を確認いただくことが可能です。
また、サンプルソリューションで使われているカスタム関数やスクリプトは自由にあなたのソリューションにコピーして使っていただくことができます。(ただし、それに伴って発生したあらゆる障害に関しては責任を追いかねます。)
最後までお読みいただきありがとうございました。
この記事にご興味を持たれた方には、こちらの記事もおすすめです。