Skip to content

安全に壊れるための設計原則

「正常に動く」よりも「異常時に安全に壊れる」ことを優先する設計原則を詳しく解説します。

外部(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などの攻撃を防止

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を変更してもビジネスロジックは変更不要
  • テスト容易性: モックで簡単にテスト可能
  • 保守性: フレームワークの変更に強い

安全に壊れるための設計原則のポイント:

  • 境界防御: 外部データは常に汚染されていると仮定し、型・形式・範囲を検査
  • 副作用の局所化: 副作用をロジックの末尾に集約し、純粋処理と分離
  • 依存の隔離: ビジネスロジックが特定ライブラリに依存しないよう抽象化

これらの原則により、「異常時に安全に壊れる」堅牢なシステムを構築できます。