Designing SaaS Commerce with State Machines [Part 11]

What You Will Learn Why “string status” breaks down in SaaS commerce Implementation patterns for embedding state machines into Go domain models Design techniques for coordinating multiple state machines Handling edge cases: partial payments, expiration, and optimistic locking The Status Column from Hell When building web applications, you will almost certainly encounter “status” columns. Order status, invoice status, user account status. What starts as a simple active / inactive boolean grows into pending, processing, completed, cancelled, refunded… as the service evolves. ...

February 23, 2026 · 13 分 · ko-chan

SaaSの商取引をステートマシンで設計する [第11回]

この記事で得られること SaaSの商取引で「文字列ステータス」が破綻する理由 Goでステートマシンをドメインモデルに組み込む実装パターン 複数のステートマシンが連携するときの設計手法 部分支払い・期限切れ・楽観的ロックなどのエッジケース対処 statusカラムの地獄 Webアプリケーションを作っていると、ほぼ確実に「ステータス」カラムに出会う。注文の状態、請求書の状態、ユーザーアカウントの状態。最初は active / inactive の2値で済んでいたものが、サービスの成長とともに pending, processing, completed, cancelled, refunded… と増殖していく。 筆者が開発しているSaaS(マルチテナント型サブスクリプション管理システム)では、見積(Quote)、注文(Order)、請求書(Invoice)、決済(Payment)のそれぞれにステータスがある。 最初はシンプルだ。 1 2 3 4 5 CREATE TABLE invoices ( id UUID PRIMARY KEY, status VARCHAR(20) NOT NULL DEFAULT 'draft', ... ); アプリケーション側ではこうなる。 1 2 3 4 5 6 // よくある実装 func (s *InvoiceService) MarkPaid(id uuid.UUID) error { invoice, _ := s.repo.Get(id) invoice.Status = "paid" // ← 文字列を直接代入 return s.repo.Update(invoice) } これは動く。しかし、サービスが成長するにつれて問題が出る。 ...

February 23, 2026 · 7 分 · ko-chan

PostgreSQL RLS for Multi-Tenant Isolation: Protecting 4-Tier Data as a Solo Developer [Part 4]

What You’ll Learn Comparison of data isolation patterns for multi-tenant SaaS Practical usage of PostgreSQL Row-Level Security (RLS) RLS policy design for 4-tier hierarchy (System/Provider/Reseller/Consumer) Setting RLS context with Go + pgx Detecting RLS leaks through testing Introduction As introduced in Part 1, Saru is a multi-tenant SaaS with a 4-tier account structure. System Admin (manages the entire SMS platform) └── Provider (offers services) ├── Reseller (sells services) │ └── Consumer (purchases/manages) └── Consumer (direct sales) In this structure, data isolation is critical. ...

January 15, 2026 · 11 分 · ko-chan

PostgreSQL RLSでマルチテナント分離:4階層のデータを1人で守る【第4回】

この記事で得られること マルチテナントSaaSにおけるデータ分離パターンの比較 PostgreSQL Row-Level Security(RLS)の実践的な使い方 4階層(System/Provider/Reseller/Consumer)のRLSポリシー設計 Go + pgx でRLSコンテキストを設定する方法 テストでRLS漏れを検知する手法 はじめに 第1回で紹介した通り、Saruは4階層のアカウント構造を持つマルチテナントSaaSだ。 System Admin(SMSプラットフォーム全体を管理) └── Provider(サービスを提供) ├── Reseller(サービスを販売) │ └── Consumer(購入・管理) └── Consumer(直販) この構造では、データの分離が極めて重要になる。 Provider A の顧客データを Provider B が見てはいけない Reseller A の販売実績を Reseller B が見てはいけない Consumer A のサブスクリプション情報を Consumer B が見てはいけない これをアプリケーション層で完全に防ぐのは難しい。WHERE句の書き忘れや権限チェックの漏れは、ソロ開発では特に起こりやすい。 そこで PostgreSQL Row-Level Security(RLS) を採用した。 1. マルチテナントの分離パターン比較 マルチテナントのデータ分離には主に3つのアプローチがある。 パターン比較表 パターン 分離レベル 実装コスト 運用コスト スケーラビリティ Database per Tenant 最高(物理分離) 高い 高い 性能分離◎、運用面△ Schema per Tenant 高い(論理分離) 中程度 中程度(自動化必須) 中程度 Shared Schema + RLS 高い(設計次第) 低い 低い 高い(設計次第) Database per Tenant テナントごとに独立したデータベースを持つ。 ...

January 15, 2026 · 7 分 · ko-chan

Next.js + Go Monorepo: Managing 4 Portals × 4 APIs as a Solo Developer [Part 3]

What You’ll Learn Next.js + Go monorepo architecture patterns Practical use of pnpm workspace + Turborepo Sharing UI components across 4 portals Package splitting strategies that don’t break down in solo development Introduction As introduced in Part 1, Saru is a multi-tenant SaaS with a 4-tier account structure. To implement this, I adopted an architecture of 4 frontends + 4 backend APIs. Normally, this would mean managing 8 repositories. For solo development, that would be unsustainable. ...

January 14, 2026 · 8 分 · ko-chan

Next.js × Go モノレポ構成:4ポータル × 4APIを1人で保守する設計【第3回】

この記事で得られること Next.js + Go のモノレポ構成パターン pnpm workspace + Turborepo の実践的な使い方 4ポータルで共通UIを使い回す設計 ソロ開発で破綻しないパッケージ分割の考え方 はじめに 第1回で紹介した通り、Saruは4階層のアカウント構造を持つマルチテナントSaaSだ。これを実現するために、4つのフロントエンド + 4つのバックエンドAPIという構成を採用している。 普通に考えると、8つのリポジトリを管理することになる。ソロ開発では破綻する。 そこでモノレポを採用した。本記事では、その構成と設計判断を解説する。 1. なぜNext.js × Go なのか 技術選定の理由 領域 技術 選定理由 Frontend Next.js 14 App Router、RSC、豊富なエコシステム Backend Go + Echo シンプル、高速、型安全、デプロイが楽 DB PostgreSQL RLSによるマルチテナント分離 なぜフルスタックフレームワーク(Next.js API Routes)を使わないのか? 関心の分離: フロントエンドとバックエンドのデプロイサイクルを分けたい 言語の強み: Goの方が複雑なビジネスロジックを書きやすい(個人の感想) スケーラビリティ: 将来的にAPIだけスケールさせる可能性 認証フローは以下の分担:Keycloakがユーザー認証、NextAuthがOAuth/セッション管理、Go APIはKeycloakのアクセストークン(JWT)を検証して権限チェックを行う。 2. プロジェクト構成 saru/ ├── apps/ # 6つのNext.jsアプリ │ ├── system/ # System Portal (管理者) │ ├── provider/ # Provider Portal (サービス提供者) │ ├── reseller/ # Reseller Portal (販売代理) │ ├── consumer/ # Consumer Portal (利用者) │ ├── customer/ # Customer Portal (レガシー名、consumerと統合予定) │ └── landing/ # ランディングページ │ ├── packages/ # 共有パッケージ │ ├── types/ # TypeScript型定義 │ ├── ui/ # 共通UIコンポーネント │ ├── api-client/ # APIクライアント + React Query hooks │ ├── auth/ # NextAuth設定 │ ├── config/ # ESLint, TypeScript設定 │ └── env-validator/ # 環境変数バリデーション │ ├── backend/ # Go バックエンド │ ├── cmd/ │ │ ├── system-api/ # System API (port 8080) │ │ ├── provider-api/ # Provider API (port 8081) │ │ ├── reseller-api/ # Reseller API (port 8082) │ │ ├── consumer-api/ # Consumer API (port 8083) │ │ └── migrate/ # マイグレーションCLI │ └── internal/ # 共通ロジック │ ├── e2e/ # Playwright E2Eテスト ├── pnpm-workspace.yaml # pnpm workspace設定 └── turbo.json # Turborepo設定 なぜ4つのAPIに分けているのか 「1つのAPIで全部まかなえばいいのでは?」という疑問があるかもしれない。 ...

January 14, 2026 · 5 分 · ko-chan