SDKが凄く便利なので実際に作ってみた結果、物凄く便利だった
おはようございます、こんにちは、こんばんは、お久しぶりです、かっちゃんです。
今回はSDKについて書いていきたいと思います。
SDKとは?
そもそもSDKとはなんぞやという方のために、SDKについて簡単に説明していきます。
「SDK」とは「Software Development Kit」を略した言葉で、日本語で簡単にいうと「ソフトウェア開発キット」と言います。
ソフトウェアやWebサービス開発にあたって必要なプログラムやAPIをパッケージ化し、効率的な開発を可能にしてくれる優れたものです。
なんとなくSDKと言うものが分かってきたと思いますのでもう少し詳しく見ていきましょう。
SDKの具体例
BtoBtoCのテナント型ECサイトのSaaSの会員登録APIを外部サイトから使ってもらうために、JavaScript SDKを作っていきたいと思います。
登場人物は下記の通りとします。
- ECサイトSaaSの提供者=SDK提供者
 - SaaSを利用するECサイトのオーナー=SDK利用者
 - 各ECサイトのユーザー=会員
 
会員登録APIを外部サイトから使い会員登録をするには名前、メールアドレス、住所、パスワードの情報が必要のなると仮定します。
JavaScript SDKを使わずにこのAPIを使うとなると、自分で上記4つの入力フォームを作成しないとダメですね。
あとはAPIに繋ぐためにURLの設定や、どのメソッドで繋げば良いのかなど多くの情報を知っていないとAPIを使うことができません。
今から作成するJavaScript SDK を使いますとdivタグと数行のJavaScriptを書くだけで上記4つの入力フォームが自動生成された上に、APIに繋ぐための設定も完了します。
めちゃくちゃ便利ですね、感無量です。
実際に書いていってみよう(SDK利用者側)
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>SDKサンプル</title>
</head>
<body>
<div id="form">
</div>
<script src="https://cdn.example.com/sdk-1.x.x.js"></script>
<script>
    const sdkSample = initializeSdkSample({
        apiKey: 'sample-api-key',
        apiUrl: 'https://shop1.example.com/api/users/register'
    });
    sdkSample.mount('form');
</script>
</body>
</html>
上記の様な簡易的なサイトにフォームタグだけ用意しました。
初期化時にセキュリティの安全面を考慮してSDKを初期化する際に設定値をSDK利用者に入れてもらうようにしています。
- apiKeyなどは後述のCORS制御などに用いる
 - apiUrlもユーザーごとに変えたほうが実はCORSのプリフライトリクエストの制御がしやすい
 

もちろん今の状態ですと添付画像のように何もない真っ白な画面になります。
SDK提供側は利用側で読み込んでいるJSファイルを用意する必要があります。
 
実際に書いていってみよう(SDK提供者側)
*今回はJavaScript SDKに必要な部分だけ書きます。APIの部分については書きませんのでご了承ください。
以下のようなJSファイルを用意してCDNに配置します。
function initializeSdkSample(config) {
    return {
        mount(id) {
            const button = document.createElement('button');
            button.innerText = '会員登録する';
            button.type = "submit";
            // リクエストパラメーター作成
            const nameLabel = document.createElement('label');
            nameLabel.innerText = 'お名前     ';
            nameLabel.className = 'nameLabel'
            const paramName = document.createElement('input');
            paramName.name = 'name';
            paramName.placeholder = 'お名前'
            paramName.className = 'nameInput'
            const addressLabel = document.createElement('label');
            addressLabel.innerText = '住所      ';
            addressLabel.className = 'addressLabel'
            const paramAddress = document.createElement('input');
            paramAddress.name = 'address';
            paramAddress.placeholder = '住所'
            paramAddress.className = 'addressInput'
            const emailLabel = document.createElement('label');
            emailLabel.innerText = 'メールアドレス ';
            emailLabel.className = 'emailLabel'
            const paramEmail = document.createElement('input');
            paramEmail.name = 'email';
            paramEmail.placeholder = 'メールアドレス'
            paramEmail.className = 'emailInput'
            const passwordLabel = document.createElement('label');
            passwordLabel.innerText = 'パスワード   ';
            passwordLabel.className = 'passwordLabel'
            const paramPassword = document.createElement('input');
            paramPassword.name = 'password';
            paramPassword.placeholder = 'パスワード'
            paramPassword.className = 'passwordInput'
            const paramApiKey = document.createElement('input');
            paramApiKey.type = 'hidden';
            paramApiKey.name = 'apiKey';
            paramApiKey.value = config.apiKey;
            const brTag = document.createElement('br');
            const sdkSampleArea = document.getElementById(id);
            sdkSampleArea.appendChild(nameLabel);
            nameLabel.appendChild(paramName);
            sdkSampleArea.appendChild(brTag);
            sdkSampleArea.appendChild(addressLabel);
            addressLabel.appendChild(paramAddress);
            sdkSampleArea.appendChild(brTag.cloneNode());
            sdkSampleArea.appendChild(emailLabel);
            emailLabel.appendChild(paramEmail);
            sdkSampleArea.appendChild(brTag.cloneNode());
            sdkSampleArea.appendChild(passwordLabel);
            passwordLabel.appendChild(paramPassword);
            sdkSampleArea.appendChild(brTag.cloneNode());
            sdkSampleArea.appendChild(button);
            button.onclick = postRequest(config);
        }
    };
}
function postRequest(config) {
    return async function () {
        // 詳細は省略する
        await fetch(config.apiUrl, {
            method: 'POST'
        });
    }
}
これをCDNなどに配置し、利用者側で読み込むと以下のような画面になります。

SDK利用者によってデザインを変えたいかと思うので、class名をそれぞれ振り分けて自由にデザインできるようにしてあります。
このjsファイルを読み込むこと、divタグを準備しidを設定すること、scriptタグ内に設定パラメータを指定してSDKを初期化して準備したdiv要素にマウントする、という約束事3つだけで、TESTというサイトに会員登録ができるようになりました。
めちゃくちゃ便利で簡単ですね、素晴らしきかな。
セキュリティについて
SDK提供者側でやっておきたいセキュリティ対策について何点か記述します。
CORS制御
- SaaSの管理画面などから、ECサイトごとに予めオリジンのホワイトリストを登録してもらいAPIキーを発行する
 - APIキーやドメインからECサイトを特定し、それに紐つくオリジンのホワイトリストでアクセスを許可するかを判定
 
Bot対策
- hCaptcha https://www.hcaptcha.com/
 - CloudflareのTurnstile https://www.cloudflare.com/ja-jp/products/turnstile/
 
上記などを使い対策する、上記の使い方等は今回は省かせて頂きます。
SDKの配信方法について
今回はCDNでSDKのJSファイルを配信する想定で記事を書いていますが、配信方法は他にもありえます。
- JSファイルをSaaSの管理画面からダウンロードして自分で組み込んで使ってもらう
 - npmパッケージにする
 
まとめ
SDKめっちゃ便利ですよね。
全てのAPIにSDKを用意してくれたらめっちゃ楽なんだろうなって思いました。
いちいちDocumentを読み込んで必要な項目を洗い出す作業とか、面倒臭い事が一気に省けるのは神すぎますよね。
以上かっちゃんによるSDKめっちゃ便利だよねっていう話でした。 ありがとうございました。