安全に壊れるための設計原則
安全に壊れるための設計原則
Section titled “安全に壊れるための設計原則”「正常に動く」よりも「異常時に安全に壊れる」ことを優先する設計原則を詳しく解説します。
境界防御 (Boundary Defense)
Section titled “境界防御 (Boundary Defense)”外部(API・DB・ユーザ入力)からのデータは常に汚染されていると仮定し、型・形式・範囲を検査してからロジックに渡す。
# ❌ 悪い例: 無防備な入力受付class UsersController < ApplicationController def create # 問題: 型チェックなし、バリデーションなし @user = User.create(params[:user]) render json: @user endend
# ✅ 良い例: 境界防御の実装class UsersController < ApplicationController def create @user = User.new(user_params)
if @user.save render json: @user, status: :created else render json: { errors: @user.errors }, status: :unprocessable_entity end end
private
def user_params # Strong Parametersで境界を定義 params.require(:user).permit(:name, :age, :email) endend
# モデルでバリデーションclass User < ApplicationRecord validates :name, presence: true, length: { minimum: 1, maximum: 100 } validates :age, presence: true, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 150 } validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }endなぜ重要か:
- 型安全性: Strong Parametersで型チェック
- バリデーション: モデルで形式・範囲を検査
- セキュリティ: SQLインジェクション、XSSなどの攻撃を防止
副作用の局所化
Section titled “副作用の局所化”DB更新・通知・外部呼出などの副作用をロジックの末尾に集約し、それ以前を状態を持たない純粋処理として保つ。
# ❌ 悪い例: 副作用が散在class OrderService def create_order(order_data) # 副作用1: DB更新 order = Order.create!(order_data)
# ビジネスロジック(副作用が混在) if order.amount > 10000 # 副作用2: 外部API呼び出し NotificationService.new.send_email(order.user_id) end
# 副作用3: 別のDB更新 AuditLog.create!(event: 'ORDER_CREATED', order_id: order.id)
order endend
# ✅ 良い例: 副作用の局所化class OrderService def create_order(order_data) # 1. 純粋処理: ビジネスロジック(副作用なし) order = validate_and_create_order(order_data)
# 2. 副作用の集約: すべての副作用を末尾に persist_order(order) notify_if_needed(order) audit_order_creation(order)
order end
private
# 純粋関数: 副作用なし def validate_and_create_order(order_data) # バリデーションとオブジェクト作成のみ raise ArgumentError, 'Invalid amount' if order_data[:amount] <= 0 Order.new(order_data) end
# 副作用: DB更新 def persist_order(order) order.save! end
# 副作用: 通知 def notify_if_needed(order) NotificationService.new.send_email(order.user_id) if order.amount > 10000 end
# 副作用: 監査ログ def audit_order_creation(order) AuditLog.create!(event: 'ORDER_CREATED', order_id: order.id) endendなぜ重要か:
- テスト容易性: 純粋関数は単体テストが容易
- 可読性: 副作用が明確に分離される
- デバッグ容易性: 副作用の発生箇所が明確
ビジネスロジックが特定ライブラリやORMの仕様に依存しないよう、インターフェース層で抽象化する。
# ❌ 悪い例: ActiveRecordに直接依存class OrderService def find_order(id) # ActiveRecordの仕様に依存 Order.find(id) endend
# ✅ 良い例: インターフェースで抽象化# ドメイン層のインターフェースmodule OrderRepository def find_by_id(id) raise NotImplementedError end
def save(order) raise NotImplementedError endend
# インフラ層の実装class ActiveRecordOrderRepository include OrderRepository
def find_by_id(id) Order.find(id) rescue ActiveRecord::RecordNotFound nil end
def save(order) order.save! endend
# サービス層: ドメイン層のインターフェースに依存class OrderService def initialize(repository: ActiveRecordOrderRepository.new) @repository = repository end
def find_order(id) order = @repository.find_by_id(id) raise OrderNotFoundError, id unless order order endendなぜ重要か:
- 交換容易性: ORMを変更してもビジネスロジックは変更不要
- テスト容易性: モックで簡単にテスト可能
- 保守性: フレームワークの変更に強い
安全に壊れるための設計原則のポイント:
- 境界防御: 外部データは常に汚染されていると仮定し、型・形式・範囲を検査
- 副作用の局所化: 副作用をロジックの末尾に集約し、純粋処理と分離
- 依存の隔離: ビジネスロジックが特定ライブラリに依存しないよう抽象化
これらの原則により、「異常時に安全に壊れる」堅牢なシステムを構築できます。