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