Chuyển đến nội dung chính
友田 陽大
B2B SaaS & DX strategy
B2B SaaS
アーキテクチャ設計
AWS
Cognito
Terraform
Python
セキュリティ
Stripe

経済産業大臣賞 B2B SaaS のアーキテクチャ徹底解剖:マルチテナント認可・冪等な決済・4ラウンドのセキュリティ監査

木材サプライチェーンのDXを実現したB2B SaaSを、実コードを唯一の真実源として解剖します。業種ベースのマルチテナント認可、Cognito RS256とJWKSキャッシュ、Stripe Connectの二層冪等性、ThreadPoolExecutorによる帳票並列生成、4ラウンドのセキュリティ監査、Slackアラーム偽装対策、コスト最適化までを実装レベルで解説。

Published
Reading time
20 min read
Author
友田 陽大
Chia sẻ

過去2本の記事では、木材流通業界のDXで得た「7つの教訓」と「技術選定フレームワーク」という意思決定の話を書きました。本記事はその続編にあたり、視点を「何を学んだか」から「実際にどう作り、どう本番運用に耐える品質まで引き上げたか」へ移します。

題材は、経済産業大臣賞を受賞し、京都府認定も取得した B2B サブスクリプション SaaS。林業・市場・製材所・プレカット・工務店・メーカーという多段の商流をまたいで、企業同士が受発注・帳票・決済・評価を行うマーケットプレイス型のプロダクトです。

この記事のルールはひとつ。唯一の真実源は実コードです。アーキテクチャ図やスライドではなく、実際に動いているバックエンド(Python / Flask)、フロントエンド(React / TypeScript)、インフラ(Terraform / AWS)から、エンタープライズが「この人に任せて大丈夫だ」と判断する材料になる設計判断だけを抜き出して解説します。

数字の前提: 本文中の定量値(221 エンドポイント、204 マイグレーション、2,153 テスト、48 FK インデックス、17 Terraform モジュール、12 Lambda など)はすべてリポジトリから機械的にカウントした実測値です。一方で「事業ROI(工数削減%・コスト削減額)」はクライアントの実データが必要なため、本記事では扱いません。捏造はしない、という方針です。


1. システム全体像:本番のリクエストパス

まず全体像です。よくある「CloudFront → ALB → サーバ」よりも一段深い構成になっています。

[ブラウザ] ── CloudFront ──> S3(React SPA / 不変アセット)

[ブラウザ] ── API Gateway(Cognito オーソライザー)
                  │  ※ 認証は VPC に入る前に終端
                  └─ VPC Link ─> NLB ─> ALB(internal)─> ECS Fargate ─> RDS PostgreSQL 16

[Stripe] ──> HTTP API ─> Lambda(webhook ×3)─> RDS / DynamoDB
[S3 アップロード] ──> Lambda(Excel/CSV 取り込み)─> RDS
[EventBridge 定期] ──> Lambda(課金アウトボックス送信 / 整合化)
[CloudWatch アラーム] ──> SNS ─> Lambda(Slack 通知)

なぜ API Gateway → NLB → ALB と LB を 2 段重ねるのか。これは「冗長」ではなく AWS の制約から導かれた必然です。

  • API Gateway のプライベート統合(VPC Link v1)は NLB しか指せない。 だから入口に NLB が必要。
  • L7 ルーティング・セキュリティグループによる ingress 制御・desync 対策が必要なので、その後ろに ALB を置く。ALB は desync_mitigation_mode = "defensive"drop_invalid_header_fields = true で運用。
  • Cognito オーソライザーを API Gateway のエッジに置くことで、認証は VPC に入る前に終端し、未認証リクエストは ECS まで到達しません。

重い処理・非同期処理は すべて Lambda 側に分離しています。Webhook、Excel 取り込み、課金調整、Slack 通知——これらを Flask アプリ本体から切り離すことで、API サーバは「同期リクエストの処理」という単一責任に集中でき、デプロイ単位も独立します(SRP)。


2. マルチテナント認可:業種ベースのアクセス制御

このプロダクトの設計上いちばん難しいのは「ユーザーが多種多様」という点です。林業 / 市場 / 製材所 / プレカット / 製材所兼プレカット / 工務店 / メーカー の 7 業種に、閲覧 管理 ロールが加わります。業種ごとに「実行できる操作」「閲覧できる情報」がまったく異なります。

業種は IntEnum、認可は frozenset のホワイトリスト

業種を文字列で散らすと、タイプミスや判定漏れが事故になります。そこで業種は IntEnum で一元定義し、機能ごとの許可業種を frozenset のホワイトリストとして宣言的に持ちます。

class IndustryCode(IntEnum):
    FORESTRY = 0        # 林業
    MARKET = 1          # 市場
    SAWMILL = 2         # 製材所
    PRECUT = 3          # プレカット
    SAWMILL_PRECUT = 4  # 製材所兼プレカット
    BUILDER = 5         # 工務店
    MANUFACTURER = 6    # メーカー
    VIEWER = 99
    ADMINISTRATOR = 100

# 「丸太を発注できるのは誰か」を frozenset で宣言的に定義する
WOOD_ORDER_INDUSTRIES = frozenset({IndustryCode.PRECUT, IndustryCode.SAWMILL_PRECUT})
LOG_RECEIVER_INDUSTRIES = frozenset({IndustryCode.FORESTRY, IndustryCode.MARKET})


def check_industry(user: User, allowed: frozenset[IndustryCode]) -> None:
    # 管理者は常に通す。それ以外は許可業種外なら 403。
    if user.industry == IndustryCode.ADMINISTRATOR:
        return
    if user.industry not in allowed:
        # 404 ではなく 403 を返す(リソース存在の有無を漏らさない=列挙攻撃対策)
        raise Forbidden()

ポイントは 3 つです。

  1. 認可判定はルーター層に一元化する。UseCase / Repository は「すでに認可済みの User」を受け取る前提で書くので、ビジネスロジックに認可の if 文が散らばりません。
  2. 不一致は 403404 ではない)。これは「IDを総当たりして存在を推測する」列挙攻撃を防ぐための明示的な設計判断です。
  3. 管理者バイパスは一箇所だけ。例外を一点に閉じ込めることで、認可の抜け道が増殖しません。

PII を漏らさない「二層スキーマ境界」

企業をまたいで取引相手を探すマーケットプレイスでは、「相手企業の概要は見せたいが、メール・電話・法人番号は見せたくない」という要件が生まれます。これをスキーマの分離で構造的に解いています。

class UserDumpSchema(BaseSchema):
    """相互に取引関係がある相手にだけ使う。PII を含む完全な表現。"""
    email = fields.Email()
    phone_number = fields.String()
    corporate_number = fields.String()
    # ... PII を含む全フィールド

class UserPublicSchema(BaseSchema):
    """企業横断の検索・閲覧で使う公開スキーマ。PII は『許可リスト方式』で構造的に除外。"""
    user_id = fields.UUID()
    company_name = fields.String()
    industry = fields.Integer()
    prefecture = fields.String()
    average_rate = fields.Float()      # 0〜5 の企業評価
    evaluation_count = fields.Integer()
    # email / phone_number / corporate_number は『定義していない』ので絶対に出ない

ブラックリスト(「これを隠す」)ではなく**ホワイトリスト(「これだけ出す」)**で実装しているのが肝です。新しい PII フィールドを User に足しても、UserPublicSchema に明示追加しない限り公開経路には絶対に出てきません。後述するペネトレーションテストで、まさにここの取り違え(横断 API が UserDumpSchema を使っていた)を検出し、即日修正しています。

フロントエンド:多段ゲートの ProtectedRoute

バックエンドの認可は最後の砦ですが、UX のためにフロントでも段階的にゲートします。React 側は ProtectedRoute が「認証 → プロフィール完備 → 管理者 → サブスク有効 → 業種」の順に判定し、いずれかで弾かれたら適切なリダイレクトを返します。

function ProtectedRoute({
  requiredIndustries,
  requiresAdmin,
  children,
}: {
  requiredIndustries?: ReadonlySet<Industry>;
  requiresAdmin?: boolean;
  children: React.ReactNode;
}) {
  const { user, isLoading } = useAuth();

  if (isLoading) return <Loading />;
  if (!user) return <Navigate to="/login" replace />;
  if (isProfileIncomplete(user)) return <Navigate to="/onboarding" replace />;
  if (requiresAdmin && !user.is_admin) return <Navigate to="/" replace />;
  if (IS_PROD && user.subscription_status !== "active")
    return <Navigate to="/subscription" replace />;
  if (requiredIndustries && !requiredIndustries.has(user.industry))
    return <Navigate to="/" replace />;

  return <UserContext value={user}>{children}</UserContext>;
}

業種ごとの機能境界(市場ユーザー向け、製材所向け、直送送り手向け…)は、この ProtectedRoute を薄くラップしたルートで表現します。「許可業種の集合」をデータとして渡すので、機能とロールの対応を一覧で見渡せ、変更も局所化されます(ETC)。


3. 認証基盤:Cognito JWT(RS256)と JWKS キャッシュ

認証は Amazon Cognito。バックエンドは渡ってきた JWT を RS256 で検証します。ここで「ライブラリに任せて終わり」にしないのが本番品質の分かれ目です。

def verify_token(token: str) -> dict:
    signing_key = get_jwks_client().get_signing_key_from_jwt(token)
    claims = jwt.decode(
        token,
        signing_key.key,
        algorithms=["RS256"],
        audience=COGNITO_CLIENT_ID,
        issuer=COGNITO_ISSUER,
        # exp / iat / iss / aud / token_use の存在を必須化する
        options={"require": ["exp", "iat", "iss", "aud", "token_use"]},
    )
    # access トークンの誤用を防ぐ:id トークン以外は拒否する
    if claims.get("token_use") != "id":
        raise Unauthorized("invalid token_use")
    return claims

algorithms=["RS256"] を明示しているので、古典的な alg=none 攻撃や HS256 へのダウングレードは成立しません。token_use == "id" の検証は、access トークンを使って id トークン用エンドポイントを叩く混同を塞ぐためのものです。

JWKS は「二重チェックロックのシングルトン」でキャッシュ

JWKS(公開鍵セット)をリクエストごとに取りに行くと、レイテンシも増えるし Cognito へ無駄に負荷をかけます。一方で Fargate はマルチワーカーなので、ナイーブなグローバル変数は競合します。そこでダブルチェックロッキングでクライアントを 1 つだけ生成します。

_jwks_lock = threading.Lock()

def get_jwks_client() -> PyJWKClient:
    client = current_app.config.get("COGNITO_JWKS_CLIENT")
    if client is not None:           # 1st check(ロックなしの高速パス)
        return client
    with _jwks_lock:
        client = current_app.config.get("COGNITO_JWKS_CLIENT")
        if client is None:           # 2nd check(ロック内で再確認)
            client = PyJWKClient(COGNITO_JWKS_URI)
            current_app.config["COGNITO_JWKS_CLIENT"] = client
        return client

JWKS の更新間隔は当初 24 時間でしたが、セキュリティ監査の指摘を受けて 6 時間へ短縮し、起動時にプリウォーム(同期的に 1 回取得)しています。

なお、ステートレス JWT のトレードオフは正直に文書化しています。Cognito の GlobalSignOut は refresh トークンを失効させますが、id/access トークンは exp(約 60 分)まで有効です。これを即時失効させるには denylist(ElastiCache)が要りますが、コスト・レイテンシ・SPOF に見合わないと判断し、「短い TTL + リクエストログ + セキュリティヘッダ」で受容しています。受容したリスクを台帳に残すのは、エンタープライズ向けでは「隠す」より遥かに信頼されます。


4. 冪等な決済:Stripe Connect の二層冪等性

このプロダクトは「継続課金(サブスク)」と「取引ごとの精算」が同居するため、Stripe Connect を採用しています。決済では、ネットワーク断やリトライ下でも 二重課金 取りこぼし を絶対に起こさないことが要件です。

サブスク状態は User に置き、DB の CHECK 制約で形式検証

class User(Base):
    stripe_customer_id: Mapped[str | None]
    stripe_subscription_id: Mapped[str | None]
    stripe_connect_account_id: Mapped[str | None]
    subscription_status: Mapped[SubscriptionStatus]

    __table_args__ = (
        # Stripe ID の形式を DB レベルで検証(偽造・不正値の混入を防ぐ)
        CheckConstraint("stripe_customer_id LIKE 'cus_%'", name="ck_stripe_customer_id"),
        CheckConstraint("stripe_subscription_id LIKE 'sub_%'", name="ck_stripe_subscription_id"),
        CheckConstraint("stripe_connect_account_id LIKE 'acct_%'", name="ck_stripe_account_id"),
    )

金額は常にサーバ側で解決します。クライアントから渡ってきた amount を Stripe にそのまま流すのは、初期の監査で検出された典型的な「金額改ざん」脆弱性でした。現在は注文内容から金額を再計算する AmountResolver を通します。

第 1 層:コンテンツアドレス方式の冪等キー

Stripe API 呼び出しには冪等キーを付けますが、キーをランダムにすると「同じ操作のリトライ」と「内容が変わった再操作」を区別できません。そこで内容のハッシュをキーに織り込みます。

def idempotency_key(adjustment_id: str, scope: str, params: dict) -> str:
    digest = hashlib.sha256(canonical_json(params).encode()).hexdigest()[:12]
    # 同じ内容 → 同じキー(安全に再送できる)
    # 内容が変わる → 別キー(24h の IdempotencyKeyConflict を踏まない)
    return f"adj_{adjustment_id}_{scope}_{digest}"

第 2 層:DynamoDB の条件付き書き込みで Webhook を重複排除

Stripe Webhook は「少なくとも 1 回」配信されます。つまり同じイベントが複数回飛んできます。これを DynamoDB の条件付き PutItem で冪等化します。

def already_processed(event_id: str) -> bool:
    try:
        table.put_item(
            Item={"event_id": event_id, "ttl": now() + THIRTY_DAYS},
            ConditionExpression="attribute_not_exists(event_id)",
        )
        return False  # 初回 → 処理する
    except ClientError as e:
        if e.response["Error"]["Code"] == "ConditionalCheckFailedException":
            return True   # 既処理 → スキップ
        # DynamoDB 側の障害は fail-open(Stripe が再送するので最終的整合に倒す)
        log.warning("idempotency check degraded", exc_info=True)
        return False

ここには「障害時にどちらへ倒すか」という明確な判断が 2 つあります。

  • テーブル名の環境変数が未設定なら、import 時点で RuntimeError を投げて起動を止める(fail-closed)。 冪等性の仕組みが黙って無効化される事故を防ぎます。
  • DynamoDB 自体が落ちているときは fail-open(処理を進める)。Stripe が再送してくれるので最終的整合に倒すほうが、決済を止めるより害が小さい、という判断です。

さらに課金調整は トランザクショナル・アウトボックスで実装しています。業務トランザクションと同じ DB トランザクションで outbox 行を書き、別 Lambda(EventBridge で 5 分ごと起動)が未送信分を Stripe へ送る。送信失敗しても行が残るので、確実に届きます。1 時間ごとの整合化 Lambda が突き合わせを行い、監査ログ用テーブルに記録します。


5. 重い処理の並列化と非同期化

「見積書・納品書・請求書」の Excel/PDF 生成と「既存 Excel の DB 化」は、このプロダクトでもっとも重い処理です。ここを素朴に同期実行すると、管理画面が固まります。

帳票生成:ThreadPoolExecutor でスレッド並列

1 つの注文に対して注文書・納品書・請求書を同時に生成します。Flask のアプリコンテキストはスレッドローカルなので、各スレッドで明示的に張り直すのが要点です。

def parallel_create_documents(app, order_id: str) -> None:
    tasks = [create_order_form, create_delivery_note, create_invoice]

    def run(task):
        with app.app_context():                      # スレッドごとにコンテキストを張る
            doc = (
                Document.query
                .options(selectinload(Document.lines))  # N+1 を選択ロードで回避
                .filter_by(order_id=order_id)
                .with_for_update()                       # 行ロックで競合生成を防ぐ
                .one()
            )
            return task(doc)

    with ThreadPoolExecutor(max_workers=len(tasks)) as pool:
        futures = [pool.submit(run, t) for t in tasks]
        for f in as_completed(futures):
            f.result()  # 最初の例外を伝播させる

Excel は openpyxl、PDF は LibreOffice のヘッドレス変換を使います(Celery や asyncio ではなく、CPU/IO バウンドな帳票生成にはスレッドプールが素直で十分、という判断です)。

Excel/CSV 取り込み:S3 イベント駆動 Lambda

業界の共通言語である Excel を一括取り込みする処理は、API サーバから切り離して S3 アップロードをトリガーにした Lambda にしています。openpyxlread_only=True で開き、psycopg2execute_values で一括 INSERT します。ここでも防御を 2 つ入れています。

  • アップロードは 50MB 上限HeadObject で事前チェックし、OOM を防ぐ)。
  • CSV/Excel の数式インジェクション無害化(CWE-1236)= + @ - で始まる値は先頭に ' を付けて、開いた先の表計算ソフトで数式として実行されるのを防ぐ。

フロントエンド:指数バックオフ+Page Visibility 対応のポーリング

重い処理の完了をフロントで待つとき、固定間隔の setInterval は背面タブでも API を叩き続けてコストを無駄にします。そこで指数バックオフ+画面の可視状態連動のポーリングフックを実装しています。

usePollingWithBackoff({
  baseIntervalMs: 1000,
  maxIntervalMs: 30_000,           // 1 → 2 → 4 → … → 30s と伸ばす
  onTick: async () => {
    const next = await refetch();
    // 全件が success / failure に達したら停止
    return { shouldStop: next.every(isTerminal) };
  },
});
// document.hidden の間はリクエストを抑止し、再表示で base に戻す

背面タブで API を浪費しないので、ユーザー体験とクラウドコストの両方に効きます。


6. データベースの効率と信頼性

PostgreSQL 16 上で、地味だが効く改善を積んでいます。

改善内容
FK インデックス 48 本を無停止追加不足していた外部キーのインデックスを CREATE INDEX CONCURRENTLYIF NOT EXISTS で追加。本番で ACCESS EXCLUSIVE ロックを取らない
コネクションプールの予算化pool_size=5 / max_overflow=5 / pool_recycle=1800 / pool_pre_ping=True(5+5)×8タスク = 80 < db.t4g.micro の上限。スケールアウト時の接続枯渇を予防
日報の N+1 解消サイト数に比例して flush していた upsert を add_all + 単一 flush に。flush 回数をサイト数によらず定数化
テストのセーブポイント分離テストごとに savepoint を張ってロールバック。全件を約 11 秒で実行でき、CI が高速

CREATE INDEX CONCURRENTLYautocommit_block の中で実行し、命名規約を SQLAlchemy の自動生成(ix_<table>_<column>)に揃えることで、マイグレーションの drift をゼロに保っています。マイグレーションは Alembic で 204 世代を版管理しており、既存マイグレーションは決して編集しない運用です。


7. 4ラウンドのセキュリティ監査

ここがエンタープライズの信頼を勝ち取る核心です。このプロダクトは4 ラウンドのセキュリティ監査と資格情報ローテーションを経ています。

ラウンド手法主な所見と対応
R1静的ソース監査40 件(Critical 4 / High 17 / Medium 14 / Low 5)。決済整合性が中心:クライアント指定 amount の改ざん、テスト課金バイパス、管理セッション Cookie の httponly=False。Critical を 4/4 クローズ
R3ライブ・ステージング診断(約250リクエスト)Critical 2:Lambda 環境変数の平文資格情報(→ Secrets Manager 移行)、Webhook 冪等性の fail-open(→ fail-closed)。パストラバーサル・JWT 偽造(alg=none/改ざん aud)・CORS リフレクション・Webhook 署名検証はすべて防御成功
R4ブラックボックス+ホワイトボックス・ペネトレ実在 15 ロールの Cognito ユーザーで全 221 エンドポイントを走査。認証欠落 0 件。High 1 件(横断 API のクロステナント PII 露出)を当日に修正・再診断で 0 件確認
R5カテゴリ網羅(SSRF/CSRF/XXE/IDOR/SSTI 等 14 種)新規所見 0 件。RDS の rds.force_ssl=1 強制、通知のレート制限などを追加実装

R4 の「全 221 エンドポイントで認証欠落 0 件」は、すべてのルートが API Gateway の Cognito オーソライザーで守られていることを、実際の攻撃者視点で実証した結果です。/health すら未認証で素通りしません。

セキュリティヘッダも整備済みです。

Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Content-Security-Policy: default-src 'none'; frame-ancestors 'none'

そして、「直しきれない/直さないと決めた」残存リスクは台帳に明記しています(前述の JWT 失効トレードオフ、Cognito の SignUp によるユーザー存在の推測可能性など)。これは ADR(設計判断の記録)に近い運用で、第三者が「何を分かったうえで何を選んだか」を追跡できます。


8. 可観測性と回復性

Slack アラーム偽装(ログインジェクション)への対策

運用アラートは Slack に流していますが、当初の「失敗マーカー」はログの先頭トークンを見る位置依存の文字列でした。CloudWatch は行頭の空白を削るため、改行を仕込んだ入力で偽の運用アラームを発火できてしまう——古典的なログインジェクション(CWE-117)です。

対策として、マーカーを単一行のトップレベル JSON に変えました。

# 偽装可能だった旧実装(位置依存の文字列):  "[w1=..., FAILED]"
# 偽装不能な新実装(構造化):
log.error(json.dumps({"marker": "SLACK_DELIVERY_FAILED", "reason": reason}))

CloudWatch のメトリクスフィルタは { $.marker = "SLACK_DELIVERY_FAILED" } で照合するので、本文に何を書かれても marker キーは偽造できません。

Slack が落ちても気づける「帯域外」エスカレーション

通知経路自体が壊れたら本末転倒です。そこで Slack 配信失敗を上記マーカーで検知し、CloudWatch メトリクスフィルタ → SNS メールという、Slack に依存しない経路へエスカレーションします。さらにマーカー文字列がバックエンド/Lambda/Terraform の 3 箇所でズレないよう、契約テストで機械的に同期を保証しています。

ERROR ログの Slack 通知ハンドラ自体も、レスポンスを「恒久失敗(4xx)」と「一時失敗」に分類し、一時失敗だけ指数バックオフでリトライ、恒久失敗は即中止して上記マーカーを出します。スレッド/グリーンレット混在環境のため、requests.Session を共有しスレッドセーフに扱っています。


9. コスト最適化

「世界最高峰の品質」はコスト無視を意味しません。むしろ個人〜小規模で本番 SaaS を回すには、コスト設計こそ実力が出ます。

  • environment_active 一発で課金リソースをゼロに畳む。 停止期間は NAT・ALB/NLB・VPC Link・RDS(count=0)・ECS(desired=0)・Secrets Manager の VPC エンドポイントがすべて消える。再開もコードから。
  • Graviton(ARM / t4g) を RDS とバスティオンに採用し、x86 比でコスト効率を改善。
  • ステージングは Fargate Spot 100%(中断許容、約 70% 削減)。本番はオンデマンド。
  • NAT は単一(冗長を捨ててコスト最適)。VPC エンドポイントは本番のみ。
  • スケジュールスケーリング:本番の ECS を平日業務時間は min2/max8、夜間は min1/max4 に。
  • Terraform state は S3 ネイティブロックuse_lockfile = true)で、DynamoDB ロックテーブルのコストを排除。
  • S3 ライフサイクル(本番のみ):Standard → IA(30d) → Glacier(90d) → 削除(365d)。
  • CMK は本番のみ、ステージングは AWS マネージドキー。

「平常時は安く、必要なときだけスケールし、使わない期間はゼロに畳める」——この弾力性をコードで宣言しているのが要点です。


10. CI/CD と品質ゲート

最後に、これらの品質を人手のレビューに頼らず維持する仕組みです。

デプロイは GitHub Actions の OIDC(長期 AWS キーを一切持たない)で実行します。バックエンドは docker build → ECR push → ecs update-service --force-new-deployment、フロントは S3 sync(不変アセットは immutable、ルートは no-cache)→ CloudFront 無効化

Terraform も 3 本のパイプラインで自動化しています。

  • plan:PR で fmt-check / validate / tfsec を実行し、plan をコメント。
  • applymain → staging、production ブランチ → 本番。**apply ロールには権限境界(permissions boundary)**を付け、組織乗っ取りや監査基盤の停止を構造的に禁止。
  • drift:平日朝に定期実行し、ドリフトを検知したら GitHub Issue を自動起票・復旧で自動クローズ。

品質ゲートは二段構えです。pre-commit(変更ファイルのみ、数秒)と pre-push(CI のフルミラー)で、以下を回します。

フォーマットLintセキュリティテスト
BackendRuffRuff / Bandit / Vulture / deptrymypypip-auditpytest(Docker, 2,153 件)
FrontendPrettierESLinttsc --noEmitnpm auditVitest
Infraterraform fmtterraform validatetfsecterraform plan

さらに gitleaks(秘密情報スキャン)、Trivy(イメージ CVE)、hadolintDependabot(6 エコシステムを継続更新)。Conventional Commits を必須化し、--no-verify と main への force push は禁止です。


まとめ

経済産業大臣賞という結果の裏側には、派手な機能ではなく、地味で一貫した設計判断の積み重ねがあります。

  • マルチテナント認可frozenset ホワイトリスト+ルーター層一元化+PII ホワイトリストスキーマで、構造的に漏れない形に。
  • 決済は「サーバ側金額解決+コンテンツアドレス冪等キー+DynamoDB 重複排除+アウトボックス」で、リトライ下でも二重課金しない。
  • 重い処理はスレッド並列とイベント駆動 Lambda へ分離し、フロントは可視状態連動のバックオフポーリングで待つ。
  • 信頼性は、4 ラウンドの監査・221 API の認証欠落 0 件・ログインジェクション対策・帯域外エスカレーション・受容リスク台帳という形で実証可能に。
  • コストenvironment_active・Graviton・Spot・単一 NAT で、弾力的かつ安価に。

「動くものを作る」と「本番運用に耐え、第三者の攻撃と監査に耐える SaaS を作る」の差は、まさにこういう一つひとつの判断にあります。レガシー産業の DX や、B2B SaaS の新規開発・立て直しをご検討中でしたら、要件定義からインフラ・セキュリティ・運用まで、この水準でワンストップにお引き受けします。

友田

友田 陽大

Người phát triển sản phẩm đoạt Giải thưởng Bộ trưởng (METI). Với TypeScript + Python + AWS, tôi một mình triển khai trọn gói SaaS, chuyển đổi số công nghiệp và AI tạo sinh (RAG) sẵn sàng cho production — từ phân tích yêu cầu đến hạ tầng và vận hành.

Not sure where to start digitizing Excel/fax workflows?

Free DX assessment (30 min) — from mapping the current state to a rough cost and order of attack

Drawing on hands-on experience solo-building a METI-Minister's-Award-winning product, I spend 30 minutes mapping which of your workflows pay off most when digitized, then send an assessment memo (priorities, rough cost range, recommended steps). Free, no sales pitch — you're welcome to take just the assessment.

Nhận cả dự án trọn gói lẫn vai trò cố vấn kỹ thuật. Hãy bắt đầu bằng buổi tư vấn miễn phí 30 phút.

最短ルート:カレンダーから直接予約

相談内容が固まっている方は、フォーム送信よりその場で日程を確定する方がスムーズです。下記から空き時間をお選びください。

  • 30分のオンライン無料相談
  • Google Meet / Zoom / Microsoft Teams
  • NDA 商談前締結可・無理な営業はいたしません
無料相談の空き枠を予約する

Cũng đáng đọc