こんにちは、てつです!
ビジネスにおいて圧倒的な成功を目指し、有象無象 of 社会人で終わるのではなく、何かに特化した「アンバランスで圧倒的な人材」を目指して日々読書や技術習得に励んでいるあなたへ。最高にエキサイティングな連載第3回の話を始めましょう。
前回(水曜日公開)は、既存のブログ資産を守りながら、新設するアプリ用の通信を安全に逃がす「DNS戦略」と「ミニマリズムな画面設計」について徹底解説しました。美しい「操作画面」という頑丈な器が完成した今、木曜日の今日私たちがやるべきことは、システムへの命の吹き込みです。
いくら見た目がプロ仕様で美しくても、中身のロジックが伴っていなければ、それはただの「動かないハリボテ」です。
結論から言います。どれだけデザインが美しくても、裏側の制御ロジックが脆弱であればWebサービス(SaaS)として成立しません。
今回は、複数ツールでアカウントを共通化してユーザーデータを守るためのデータベース構造、本番環境とローカル環境を自動で切り替えるスマートな仕組み、推して無料枠や利用制限を裏側で厳格にコントロールする判定アルゴリズムを完全公開します。
これは実際の開発現場や自動化ビジネスでも広く使われているモダンな手法であり、個人開発でもプロと同じ堅牢な環境が作れます。
あなたのツールを本物のSaaSへと進化させる「脳みそ」の構築手順を、ステップバイステップで解説していきます。
1. バックエンド・ロジック構築の全体像
まずは、今回実装するバックエンドの処理ロジックとデータベース構造の全体像を、日本語の箇条書きで把握しておきましょう。
- アカウント・プラン情報のデータ構造化(DynamoDB ユーザー認証)
- ユーザーIDを主キーとして、ハッシュ化パスワード、プラン、当月の利用回数を1つのテーブルで管理します。
- 環境変数 IS_AWS によるデータベースの接続先自動切り替え
- 環境変数 IS_AWS(AWS_EXECUTION_ENV)の有無を判定し、ローカル環境(JSONファイル)と本番環境(AWS DynamoDB)をコードの書き換えなしで自動でスイッチします。
- フリーミアム制限ガードとプラン判定 ロジックの構築
- ユーザーが記事生成を行う際、現在のプラン設定と月間利用上限を照合し、制限を超えている場合や未解放の高級モデルを選択した場合は Python 制限ロジックが処理を安全に割り込み中断(ガード)します。
2. ステップバイステップの解説(データベース & ロジック構築)
複数ツールでアカウントを共通化!SaaSの土台を作る「DynamoDB ユーザー認証」データ構造
個人開発でWebアプリを構築する際、多くの人が「サーバーの中にテキストファイルや簡易なデータベース(SQLiteなど)でユーザー情報を保存すればいいや」と考えてしまいがちです。しかし、将来的にビジネスを横展開していくつもの自動化ツールを販売・運営する未来を見据えるなら、それは悪手となります。
なぜなら、サーバー内にデータを閉じ込めてしまうと、新しいツールを作るたびにユーザーに新しいアカウント登録を強いることになり、管理コストもツールごとに倍増してしまうからです。
この課題を根本から解決するために、私たちはAWSの独立したデータベースサービスであるDynamoDBを採用し、「DynamoDB ユーザー認証」の基盤を完全に外部へ切り離します。これにより、将来あなたが第2、第3の自動化ツールを開発した際にも、全く同じDynamoDBテーブルを参照させるだけで、ユーザーのアカウント情報や契約プランを一瞬で共通化・横展開できるようになります。
DynamoDBは「単一責任の原則(SRP原則)」に基づき、シンプルで極限まで洗練された以下のデータ構造(1テーブル)で管理します。
| 属性名(フィールド) | データ型 | 役割(初心者向けの解説) |
user_id | 文字列(String) | 主キー(プライマリキー)。ユーザーを一意に識別するID |
password | 文字列(String) | 安全にハッシュ化(暗号化)されたログインパスワード |
plan | 文字列(String) | 現在の会員プラン(free / premium_single / premium_all) |
current_usage | 数値(Number) | 今月、システムを使って記事を生成した累積回数 |
config_data | マップ(Map) | ユーザーが個別設定したプロンプトテンプレートなどのカスタマイズデータ |
ここで重要なセキュリティの最低限の嗜みとして、ユーザーのパスワードは絶対にそのままの文字列(プレーンテキスト)でデータベースに保存してはいけません。万が一のデータ流出に備え、後述するロジック内で「SHA-256」という不可逆な数式を用いてハッシュ化(復元不可能な文字列に変換)した上で保存する設計を徹底します。
開発スピードを最大化する!「環境変数 IS_AWS」による本番DBとローカルJSONモックの自動切り替え術
インフラをAWS上に構築したからといって、コードを数行書き換えるたびに毎回AWSにプログラムをデプロイ(配備)して動作確認を行うのは、開発効率が悪すぎます。数分かかるデプロイ待ちの時間は、あなたの貴重な副業時間を奪う最大の敵です。
かといって、ローカルPCでテストするためにコード内の接続先を localhost に書き換え、本番に上げる前に手動で AWS DynamoDB に書き戻す、といった運用は「書き換え忘れによる本番環境の破壊」を引き起こす原因になります。
そこで導入するのが、「環境変数 IS_AWS」(AWSのコンテナ環境に自動で注入される AWS_EXECUTION_ENV の有無)をトリガーとした、本番データベースとローカルJSONモックの自動切り替えロジックです。
プログラムは起動した瞬間、自分が今「ローカルPC(自分のパソコン)」と「AWS(クラウド上)」のどちらで動いているかを自動で検知します。ローカルであればPC内のJSONファイルにデータを読み書きし、AWS環境であれば自動的に本番のDynamoDBテーブルへと接続先をスマートに繋ぎ変えます。これにより、開発者は環境の差分を1ミリも意識することなく、ローカルで爆速でテストを回し、完成したらそのまま本番へデプロイすることが可能になります。
実際のデータアクセス層として新設した src/database.py の完全なコードを以下に掲載します。1行ごとに何をしているか、初心者向けに解説を添えました。
import os # オペレーティングシステム(OS)の機能を利用するためのライブラリを読み込み
import json # ローカルのテスト用データ(JSONファイル)を扱うためのライブラリを読み込み
import hashlib # パスワードを安全にハッシュ化(復元不可能な状態に)するためのライブラリを読み込み
import boto3 # AWSのサービス(DynamoDBなど)をPythonから操作するためのライブラリを読み込み
# クラウド環境(AWS Fargateなど)で実行されているかどうかを環境変数の有無で判定
IS_AWS = "AWS_EXECUTION_ENV" in os.environ
TABLE_NAME = "MonographUsers" # 使用するDynamoDBのテーブル名を定義
MOCK_FILE = "local_db_mock.json" # ローカルPCでのテストに使用するファイル名を定義
if IS_AWS:
# AWS環境である場合は、DynamoDBを操作するためのリソースオブジェクトを生成
dynamodb = boto3.resource("dynamodb", region_name="ap-northeast-1")
table = dynamodb.Table(TABLE_NAME) # 定義したテーブル名にアクセスするためのオブジェクトを取得
else:
# ローカル環境である場合は、テスト用の擬似ファイル(モック)が存在するか確認
if not os.path.exists(MOCK_FILE):
with open(MOCK_FILE, "w", encoding="utf-8") as f:
# ファイルが存在しない場合は、空の辞書(データを入れる器)を書き込んで初期作成
json.dump({}, f, ensure_ascii=False, indent=4)
def hash_password(password: str) -> str:
# パスワードを安全に保存するため、SHA-256というアルゴリズムでハッシュ化する関数
# 入力されたパスワードの文字列をバイトデータに変換し、ハッシュ計算を行って16進数の文字列として返却
return hashlib.sha256(password.encode("utf-8")).hexdigest()
def get_user_data(user_id: str) -> dict:
# データベースから特定のユーザー情報を取得する関数
if IS_AWS:
# AWS環境の場合は、主キーである user_id を指定して DynamoDB からデータを取得
response = table.get_item(Key={"user_id": user_id})
# データが見つかった場合はその中身(Item)を返し、見つからない場合は空の辞書を返却
return response.get("Item", {})
else:
# ローカル環境の場合は、擬似JSONファイルを開いて読み込み
with open(MOCK_FILE, "r", encoding="utf-8") as f:
data = json.load(f)
# ユーザーIDに紐づくデータを取得し、存在しない場合は空の辞書を返却
return data.get(user_id, {})
def update_user_data(user_id: str, user_data: dict) -> None:
# ユーザーのすべての設定情報やプラン状態をデータベースに上書き保存する関数
if IS_AWS:
# AWS環境の場合は、渡されたユーザーデータをそのまま丸ごとDynamoDBへ保存
# DynamoDBの仕様に合わせ、主キーである user_id をデータ内に確実に含める
user_data["user_id"] = user_id
table.put_item(Item=user_data)
else:
# ローカル環境の場合は、一度ファイルから全ユーザーのデータを読み出し
with open(MOCK_FILE, "r", encoding="utf-8") as f:
data = json.load(f)
# 対象ユーザーのデータを最新の状態に更新
data[user_id] = user_data
# 更新されたすべてのデータを再度JSONファイルに綺麗に書き込み(保存)
with open(MOCK_FILE, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=4)
def increment_usage_count(user_id: str) -> None:
# 記事の生成に成功した際、ユーザーの今月の利用回数を「+1」加算する関数
if IS_AWS:
# AWS環境の場合は、DynamoDBの超高速な「アトミック加算(ADD操作)」を呼び出し
# データを一度手元に読み出すことなく、データベースの内部で直接安全に数値を+1します
table.update_item(
Key={"user_id": user_id},
UpdateExpression="SET current_usage = current_usage + :inc",
ExpressionAttributeValues={":inc": 1}
)
else:
# ローカル環境の場合は、一度データを読み出してから加算して書き戻す(Read-Modify-Write)
user_data = get_user_data(user_id)
# 現在の利用回数を取得し、データがなければ0回として初期化、そこに1を加算
current_usage = user_data.get("current_usage", 0)
user_data["current_usage"] = current_usage + 1
update_user_data(user_id, user_data)
フリーミアムの命綱!無料枠とモデル制限を冷徹にコントロールする「プラン判定 ロジック」の裏側
SaaSビジネスにおいて、無料枠や機能制限の制御は「サービスの利益」に直結する最も重要な命綱です。
もしこの制限ロジックを「無料プランは30回まで」のようにプログラム内に直接数値を書き込んでしまう(ハードコーディングする)と、将来的に「有料の竹プラン(Proプラン)」「プロ用の松プラン(Studioプラン)」といったプランの多角化・変更を行う際、システムが複雑化してあっという間に破綻してしまいます。
これを防ぐため、今回のアップデートではアプリの冒頭に PLAN_CONFIG という設定を明確に定義し、拡張性を持たせた「プラン判定 ロジック」へと全面刷新しました。
裏側の 「Python 制限」ロジックは、ユーザーが「生成開始」ボタンを押した瞬間に、冷徹かつ確実に作動します。現在のプランが持つ月間上限(monthly_limit)とデータベースから取得した現在の利用回数(current_usage)を照合し、制限を超えている場合や未解放の高級モデルを選択した場合は、生成処理の関数を呼び出す前に「安全に割り込み中断(ガード)」をかけます。
また、操作画面(フロントエンド)におけるモデル選択のUI表現にも、徹底的なこだわりを込めています。
有料モデルの横に絵文字を付けて誤魔化すようなチープな装飾は一切排除しました。代わりに、モデル名のすぐ横に [free / pro / studio] のような洗練されたテキストブラケット表記をシンプルに添える設計にしています。
画面全体のミニマルでプレミアムな質感を完璧に維持しつつ、ユーザーに対して「どのプランにアップグレードすればこの機能が解放されるのか」をスマートかつ明確に認識させることができます。
以下に、アプリケーション(src/app.py)のコアとなるプラン判定ガードレールの完全な実装コードを掲載します。
import streamlit as st # Webアプリケーションの画面を簡単に構築するためのライブラリを読み込み
import database as db # 先ほど作成した database.py モジュールを db という名前で読み込み
# アプリケーションで提供する3段階の料金プランのルールを定数として美しく定義
PLAN_CONFIG = {
"free": {"name": "無料プラン(梅)", "monthly_limit": 30, "allowed_models": ["gemini-2.5-flash"]}, # 月30回、Flashモデルのみ
"premium_single": {"name": "Proプラン(竹)", "monthly_limit": float("inf"), "allowed_models": ["gemini-2.5-flash", "gemini-2.5-pro"]}, # 無制限、Proモデル解放
"premium_all": {"name": "Studioプラン(松)", "monthly_limit": float("inf"), "allowed_models": ["gemini-2.5-flash", "gemini-2.5-pro", "claude-3-5-sonnet"]} # 無制限、全モデル解放
}
def check_user_plan_and_execution(user_id: str, selected_model: str) -> bool:
# ユーザーが処理を実行できる状態にあるか(プランや回数の制限をクリアしているか)を厳格に判定する関数
# データベースから最新のユーザー情報を取得
user_data = db.get_user_data(user_id)
# ユーザーの現在のプラン情報を取得し、未設定の場合は安全のために最も制限の強い「free」として扱う
current_plan_key = user_data.get("plan", "free")
# 現在の当月利用回数を取得し、データがなければ0回として初期化
current_usage = user_data.get("current_usage", 0)
# 取得したプランキーを基に、PLAN_CONFIG からそのプランの具体的な制限ルール(辞書)を取得
plan_rule = PLAN_CONFIG.get(current_plan_key, PLAN_CONFIG["free"])
# 判定ルール1:月間の生成回数上限に達していないかチェック
if current_usage >= plan_rule["monthly_limit"]:
# 上限を超えている場合は、画面に警告を表示して、処理を実行不可(False)として中断
st.error(f"【利用制限】{plan_rule['name']}の月間生成回数上限({plan_rule['monthly_limit']}回)に達しました。")
st.markdown("⚠️ 引き続き無制限に利用するには、上のメニューから上位プランへのアップグレードを行ってください。")
return False # ロジックの実行をここで完全にガード(ブロック)
# 判定ルール2:選択されたAIモデルが、現在のプランで許可されているかチェック
if selected_model not in plan_rule["allowed_models"]:
# 許可されていない高級モデルを選んでいる場合は、エラーメッセージを表示して処理をブロック
st.error(f"【プラン制限】選択されたモデルは現在の{plan_rule['name']}ではご利用いただけません。")
st.info(f"このモデル({selected_model})を利用するには、上位プランへのアップグレードが必要です。")
return False # ロジックの実行をここで完全にガード(ブロック)
# すべての厳しい判定チェック(ガードレール)を無事に通過した場合のみ、実行可能(True)を返却
return True
# --- 画面上での「生成ボタン」押下時の実装イメージ ---
user_id = st.session_state.get("user_id", "demo_user") # セッションから現在ログイン中のユーザーIDを取得
# ドロップダウンからユーザーが選択したモデル名を取得(ブラケット表記でプレミアムな質感を演出)
selected_model = st.selectbox("使用するAIモデルを選択", ["gemini-2.5-flash", "gemini-2.5-pro [pro / studio]", "claude-3-5-sonnet [studio]"])
# 表示用の文字列から余計なブラケット部分を切り離し、純粋なモデル名に整形
pure_model_name = selected_model.split(" ")[0]
if st.button("記事を自動生成する"):
# ボタンが押された瞬間、裏側の判定関数を呼び出して厳格にチェック
if check_user_plan_and_execution(user_id, pure_model_name):
# チェックを無事にクリアした場合のみ、実際の生成処理を実行
st.success("プランチェック通過。記事の生成処理を開始します...")
# 記事の生成に成功したため、データベースの利用回数をアトミックに+1加算
db.increment_usage_count(user_id)
st.write("当月の利用回数が正常にカウントされました。")
3. 専門家・現役エンジニアの視点
システム運用監視のプロが語る!個人開発だからこそ実装すべき「暗号化とアトミック操作」のセキュリティ思想
日々の実務として、24時間365日、大規模なネットワークセキュリティやシステムの安定稼働を最前線で支え続けているインフラエンジニアの視点から、少し批評的な目線で言わせてください。
個人開発のコードで最も頻繁に見落とされ、そして最も致命的なバグや事故事象を引き起こすのが、「データの暗号化」と「同時アクセスによる数値の不整合」の2点です。
「自分しか使わないツールだから」という甘い見通しで、ユーザーの大切な外部APIキーをデータベースにそのままの状態で保存するような設計は、プロの世界では絶対に許されません。
今回のシステムでは、ユーザーが個別に入力した大切な外部APIキーをデータベースに保存する際、そのままではなく「XOR(排他的論理和)+ Base64」を施した簡易暗号化関数(encrypt_key)を必ず経由させてDBに書き込む「マスター設定」の設計思想を徹底しています。
これにより、万が一データベースの生データが第三者に盗見されるような事態が起きても、大切なAPIキーの漏洩を水際で防ぐことができます。
さらに、利用回数の加算(increment_usage_count)の実装において、私が本番環境にDynamoDBの「アトミック加算(ADD操作)」をわざわざ採用したのには、深い技術的理由(Why)があります。
一般的なプログラムでは、「1. 現在の回数をDBから読み出す」→「2. プログラム側で数値を+1する」→「3. 新しい数値をDBに書き戻す」という手順を踏みます(Read-Modify-Write)。
しかし、もしユーザーがスマートフォンスクリーンのボタンを連打したり、複数のタブから全く同時に記事生成リクエストを送信したりした場合、この手順では「読み出したタイミング」が完全に重なってしまい、2回生成したのにデータベース上は1回しか増えていない、というデータの不整合・崩壊が簡単に発生してしまいます。
DynamoDBのアトミック加算を使えば、データを一度プログラム側に読み出すことなく、データベースの内部だけで直接、一瞬にして安全に数値を+1処理(ADD)することができます。
「昨日の自分より一歩先へ」進み、市場価値を尖らせて圧倒的な成果を残すなら、インフラを低コストで抑えるだけでなく、こうした「見えない裏側の堅牢性」にまで徹底的にプロのこだわりを宿らせてください。
その細部へのアウトプットの姿勢こそが、有象無象のプレイヤーから頭一つ抜け出し、ビジネスを圧倒的な成功へと導くための強力な武器になります。
4. まとめと次のステップ(CTA)
■ 今回の振り返りと次回予告:次はいよいよコンテナ化とリアルなエラーに方に立ち向かう「インフラ自動化 & 突破編」へ
今回は、MonographクラウドSaaS化における「脳みそ」となるバックエンドのロジック構築手順を完全解説しました。
- 「DynamoDB ユーザー認証」によるデータ構造の外部化で、将来の複数ツールの横展開に備える
- 「環境変数 IS_AWS」をトリガーに、ローカル環境(JSON)と本番(DynamoDB)を自動で繋ぎ変える
- 拡張性のある「プラン判定 ロジック」を構築し、無料枠や高級モデルのアクセスを冷徹にガードする
安全な通信経路、洗練された操作画面、指示通りの判定ロジックという、Webサービスに必要なすべてのパーツが机の上に出揃いました。
次へのステップとして、この完成したPythonのプログラムを、24時間365日インターネット上で稼働し続ける「本番のインフラ環境」へ、人間の手を一切介さずに一撃でデプロイ(配備)するための自動化の仕組みを構築します。
次回(金曜日公開)は【第4回:インフラ自動化編】として、StreamlitアプリをheadlessモードでDockerコンテナ化する極秘のDockerfile記述、およびAWSの全インフラ資源を一発で自動生成するTerraform(main.tf)の設定を余すことなく解説します!
さらに、開発中に遭遇したリアルなエラー(st.form内だとパスワード強度チェッカーがリアルタイムで再レンダリングされないStreamlit固有の罠や、タイポによるNameErrorの原因など)の突破口を、「エラーメッセージのどこに注目すべきか」というプロの着眼点とともに、泥臭いデバッグストーリーとして完全公開します。どうぞお楽しみに!
💡 あなたのビジネスを一歩先へ進めるために
環境構築の煩わしさをすべて過去にし、ブラウザを開くだけで明日の作業効率を劇的に爆速化する『Monograph クラウドSaaS版』は、現在AWS上での最終テストに向けて熱量高く調整を進めています。公開のアナウンスを絶対に見逃さないよう、ぜひブログのブックマークをお願いします!
また、次回の記事を待てずに「今すぐ自分のパソコン環境でスクリプトを動かして、明日から生まれる自由な時間を数時間増やしたい」という圧倒的な成長意欲をお持ちの方は、私が実際のブログ運営や実務の効率化で運用し、圧倒的な実績を残している即戦力の自動化スクリプト群をBOOTHにて先行公開しています。
無料枠は月30回までじっくり試せる点も含め、将来提供するSaaSへの布石となるツール群です。導入するだけであなたのビジネスを加速させる強力な武器が揃っていますので、ぜひその目でチェックしてみてください。


コメント