安全に壊れるための設計原則
安全に壊れるための設計原則
Section titled “安全に壊れるための設計原則”「正常に動く」よりも「異常時に安全に壊れる」ことを優先する設計原則を詳しく解説します。
境界防御 (Boundary Defense)
Section titled “境界防御 (Boundary Defense)”外部(API・DB・ユーザ入力)からのデータは常に汚染されていると仮定し、型・形式・範囲を検査してからロジックに渡す。
// ❌ 悪い例: 無防備な入力受付class UserController extends Controller{ public function create(Request $request) { // 問題: 型チェックなし、バリデーションなし $user = User::create($request->all()); return response()->json($user); }}
// ✅ 良い例: 境界防御の実装class UserController extends Controller{ public function create(Request $request) { $validated = $request->validate([ 'name' => 'required|string|min:1|max:100', 'age' => 'required|integer|min:0|max:150', 'email' => 'required|email', ]);
$user = User::create($validated); return response()->json($user, 201); }}
// モデルでバリデーションclass User extends Model{ protected $fillable = ['name', 'age', 'email'];
protected $rules = [ 'name' => 'required|string|min:1|max:100', 'age' => 'required|integer|min:0|max:150', 'email' => 'required|email', ];}なぜ重要か:
- 型安全性: Laravelのバリデーションで型チェック
- バリデーション: 形式・範囲を検査
- セキュリティ: SQLインジェクション、XSSなどの攻撃を防止
副作用の局所化
Section titled “副作用の局所化”DB更新・通知・外部呼出などの副作用をロジックの末尾に集約し、それ以前を状態を持たない純粋処理として保つ。
// ❌ 悪い例: 副作用が散在class OrderService{ public function createOrder($orderData) { // 副作用1: DB更新 $order = Order::create($orderData);
// ビジネスロジック(副作用が混在) if ($order->amount > 10000) { // 副作用2: 外部API呼び出し NotificationService::sendEmail($order->user_id); }
// 副作用3: 別のDB更新 AuditLog::create(['event' => 'ORDER_CREATED', 'order_id' => $order->id]);
return $order; }}
// ✅ 良い例: 副作用の局所化class OrderService{ public function createOrder($orderData) { // 1. 純粋処理: ビジネスロジック(副作用なし) $order = $this->validateAndCreateOrder($orderData);
// 2. 副作用の集約: すべての副作用を末尾に $this->persistOrder($order); $this->notifyIfNeeded($order); $this->auditOrderCreation($order);
return $order; }
private function validateAndCreateOrder($orderData) { // バリデーションとオブジェクト作成のみ if ($orderData['amount'] <= 0) { throw new InvalidArgumentException('Invalid amount'); } return new Order($orderData); }
private function persistOrder($order) { $order->save(); }
private function notifyIfNeeded($order) { if ($order->amount > 10000) { NotificationService::sendEmail($order->user_id); } }
private function auditOrderCreation($order) { AuditLog::create(['event' => 'ORDER_CREATED', 'order_id' => $order->id]); }}なぜ重要か:
- テスト容易性: 純粋関数は単体テストが容易
- 可読性: 副作用が明確に分離される
- デバッグ容易性: 副作用の発生箇所が明確
ビジネスロジックが特定ライブラリやORMの仕様に依存しないよう、インターフェース層で抽象化する。
// ❌ 悪い例: Eloquentに直接依存class OrderService{ public function findOrder($id) { // Eloquentの仕様に依存 return Order::find($id); }}
// ✅ 良い例: インターフェースで抽象化interface OrderRepositoryInterface{ public function findById($id); public function save(Order $order);}
class EloquentOrderRepository implements OrderRepositoryInterface{ public function findById($id) { return Order::find($id); }
public function save(Order $order) { $order->save(); return $order; }}
// サービス層: インターフェースに依存class OrderService{ public function __construct(OrderRepositoryInterface $repository) { $this->repository = $repository; }
public function findOrder($id) { $order = $this->repository->findById($id); if (!$order) { throw new OrderNotFoundException($id); } return $order; }}なぜ重要か:
- 交換容易性: ORMを変更してもビジネスロジックは変更不要
- テスト容易性: モックで簡単にテスト可能
- 保守性: フレームワークの変更に強い
安全に壊れるための設計原則のポイント:
- 境界防御: 外部データは常に汚染されていると仮定し、型・形式・範囲を検査
- 副作用の局所化: 副作用をロジックの末尾に集約し、純粋処理と分離
- 依存の隔離: ビジネスロジックが特定ライブラリに依存しないよう抽象化
これらの原則により、「異常時に安全に壊れる」堅牢なシステムを構築できます。