オーナー通知のLINEを確認してもらったとき、オーナーからメッセージが来た。
「リンクがめちゃ長いんですけど」
LINEの画面でURLが折り返しながら延々と続く。スクロールしてもまだURLが続く。「カレンダーに追加」と書いたつもりのリンクが、読み物になっていた。
なぜ200文字を超えるか
Google Calendar URLにパラメータを付けると長くなる。原因は日本語タイトルだ。
「片づけレッスン 田中花子様」という30文字前後の文字列は、URLエンコードすると90文字以上になる。日本語1文字が%XX%XX%XXという形式になるからだ。それにベースURL・dates・actionパラメータが加わると、合計で200〜300文字になる。
https://calendar.google.com/calendar/render?action=TEMPLATE
&text=%E3%80%90%E4%BA%88%E7%B4%84%E3%80%91%E3%81%93%E3%82%93
%E3%81%BE%E3%82%8A%E6%B5%81%E7%89%87%E3%81%A5%E3%81%91%E3%83...
(まだ続く)
「detailsパラメータを削除する」という対策を試みたが、タイトルだけでも日本語が含まれる以上、根本的な解決にはならなかった。
EC2に短縮URLの仕組みを作った
既存のEC2(FastAPIサーバー)に、リダイレクト機能を追加することにした。URLを短くするのではなく、「短いURLから長いURLへのリダイレクト」を作る。
設計はシンプルだ。
予約が確定すると、GASが長いGoogle Calendar URLを生成する。GASがEC2に「booking_id→長いURL」のペアをPOSTする。EC2がそのペアをin-memoryのdictに保存する。LINEに送るURLはhttps://yoyaku.example.jp/gcal/{booking_id}という短い形にする。オーナーがそのURLをタップすると、EC2が長いURLに302リダイレクトする。
gcal_store: dict = {}
@app.post("/api/gcal/store")
async def store_gcal_url(request: Request):
body = await request.json()
bid = body.get("booking_id", "")
url = body.get("gcal_url", "")
if bid and url:
gcal_store[bid] = url
return {"ok": True}
@app.get("/gcal/{booking_id}")
async def redirect_gcal(booking_id: str):
url = gcal_store.get(booking_id)
if not url:
return JSONResponse({"error": "not found"}, status_code=404)
return RedirectResponse(url)
LINEに届くURLは「https://yoyaku.example.jp/gcal/abc123」という22文字になる。
in-memoryで永続化しないという決断
gcal_storeはPythonのdictだ。EC2のプロセスが再起動すると、保存していたURLが全て消える。
永続化しない設計にした理由がある。
このURLが使われるタイミングを考えた。予約確定と同時にLINEで通知が届く。オーナーはその通知を見てすぐにカレンダーリンクをタップする。「後で使う」という使い方よりも、「すぐ使う」が圧倒的に多い。
EC2が再起動するのは稀だ。デプロイ時か障害時だ。その稀なタイミングで「さっき届いたカレンダーリンクが開けない」という問題が起きたとして、致命的かどうか。オーナーが直接スプレッドシートを見て日時を確認すれば済む話だ。
リスクに対してコストをかけるかどうかの判断は、実際の被害規模で考える。Redisを追加するコストと、稀に起きる「リンクが開けない」問題のコストを比べたとき、in-memoryで十分と判断した。
GAS側のフォールバック
EC2へのURL登録が失敗したとき(EC2が落ちている・ネットワーク障害)のために、フォールバックを入れた。
try {
UrlFetchApp.fetch('https://yoyaku.example.jp/api/gcal/store', {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify({ booking_id: bookingId, gcal_url: longUrl }),
muteHttpExceptions: true,
});
gcalUrl = 'https://yoyaku.example.jp/gcal/' + bookingId;
} catch(e) {
gcalUrl = longUrl;
}
EC2が落ちていたら、長いURLをそのままLINEに送る。「めちゃ長い」という体験にはなるが、リンク自体は機能する。カレンダーに追加できなくなるよりはいい。
応用できるパターン
EC2にリダイレクトストアを置いてLINEに短いURLを送る、このパターンは予約システム専用ではない。LINEで長いURLを送る必要があるあらゆる場面に使える。
外部のURL短縮サービスを使う方法もある。しかし外部サービスへの依存が増えると、そのサービスが終了したときに全リンクが死ぬ。自前のEC2がある場合は、そこに機能を追加する方が制御できる。
*次回は「Stripe Payment Links——個人コーチのオンライン決済をどう設計するか」*
*シンプルシステム株式会社 代表 伊藤勝彦*