Skip to content

過剰設計にならないための判断基準

過剰設計にならないための判断基準

Section titled “過剰設計にならないための判断基準”

過剰設計を避け、適切な設計を行うための判断基準を詳しく解説します。

判断基準1: トランザクション境界の明確化

Section titled “判断基準1: トランザクション境界の明確化”

条件:

  • 単一のデータベース操作のみ
  • 外部API呼び出しがない
  • 処理時間が短い(1秒以内)

実装:

// ✅ シンプルな実装で十分
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional
public User createUser(UserData userData) {
// 単一のデータベース操作のみ
return userRepository.save(new User(userData));
}
}

判断基準:

  • トランザクションが必要: データの整合性を保つ必要がある
  • Outboxパターンは不要: 外部API呼び出しがない
  • 非同期処理は不要: 処理時間が短い

条件:

  • 複数のデータベース操作
  • 外部API呼び出しがある
  • 処理時間が長い(5秒以上)

実装:

// ✅ 適切な設計が必要
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private OutboxRepository outboxRepository;
@Transactional
public Order createOrder(OrderData orderData) {
// トランザクション内でのデータベース操作のみ
Order order = orderRepository.save(new Order(orderData));
// Outboxパターンを使用
OutboxEvent event = new OutboxEvent();
event.setEventType("PAYMENT_CHARGE");
event.setAggregateId(order.getId());
event.setPayload(JSON.toJSONString(Map.of(
"orderId", order.getId(),
"amount", orderData.getAmount()
)));
event.setStatus("PENDING");
outboxRepository.save(event);
return order;
}
}

判断基準:

  • Outboxパターンが必要: 外部API呼び出しがある
  • 非同期処理が必要: 処理時間が長い

判断基準2: Serverless環境での設計

Section titled “判断基準2: Serverless環境での設計”

条件:

  • 実行時間が短い(10秒以内)
  • メモリ使用量が少ない(512MB以内)
  • ステートレスな処理

実装:

// ✅ Vercel環境に適した実装
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
// 短時間の同期処理
User user = userService.findById(id);
return ResponseEntity.ok(user);
}
}

判断基準:

  • 同期処理で十分: 実行時間が短い
  • Outboxパターンは不要: 外部API呼び出しがない

条件:

  • 実行時間が長い(5分以上)
  • メモリ使用量が多い(1GB以上)
  • ステートフルな処理

実装:

// ✅ Render.com環境に適した実装
@RestController
public class ReportController {
@Autowired
private ReportService reportService;
@PostMapping("/reports/generate")
public ResponseEntity<ReportResponse> generateReport(@RequestBody ReportRequest request) {
// 長時間実行される処理は非同期に
String reportId = UUID.randomUUID().toString();
reportService.generateReportAsync(reportId, request);
return ResponseEntity.accepted()
.body(new ReportResponse(reportId, "PROCESSING"));
}
}

判断基準:

  • 非同期処理が必要: 実行時間が長い
  • 常駐プロセスが必要: 長時間実行される処理

判断基準3: エラーハンドリングのレベル

Section titled “判断基準3: エラーハンドリングのレベル”

シンプルなエラーハンドリング

Section titled “シンプルなエラーハンドリング”

条件:

  • エラーが発生する可能性が低い
  • エラーの影響が小さい
  • リトライが不要

実装:

// ✅ シンプルなエラーハンドリングで十分
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User getUser(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));
}
}

判断基準:

  • 基本的なエラーハンドリングで十分: エラーの影響が小さい
  • リトライは不要: エラーが発生する可能性が低い

条件:

  • エラーが発生する可能性が高い
  • エラーの影響が大きい
  • リトライが必要

実装:

// ✅ 複雑なエラーハンドリングが必要
@Service
public class PaymentService {
@Autowired
private PaymentApiClient paymentApiClient;
@Retryable(maxAttempts = 3, backoff = @Backoff(delay = 1000))
public PaymentResult chargePayment(Long orderId, BigDecimal amount) {
try {
return paymentApiClient.chargePayment(orderId, amount);
} catch (TimeoutException e) {
// タイムアウトエラーはリトライ
throw new RetryableException("Payment timeout", e);
} catch (PaymentException e) {
// 決済エラーはリトライしない
throw new NonRetryableException("Payment failed", e);
}
}
}

判断基準:

  • リトライが必要: エラーが発生する可能性が高い
  • エラーの分類が必要: リトライ可能なエラーと不可能なエラーを区別

条件:

  • データの更新頻度が高い
  • データの整合性が重要
  • キャッシュの効果が小さい

実装:

// ✅ キャッシュは不要
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
public Order getOrder(Long id) {
// キャッシュなしで直接データベースから取得
return orderRepository.findById(id)
.orElseThrow(() -> new OrderNotFoundException(id));
}
}

判断基準:

  • キャッシュは不要: データの更新頻度が高い
  • 直接データベースアクセス: データの整合性が重要

条件:

  • データの更新頻度が低い
  • 読み取り頻度が高い
  • キャッシュの効果が大きい

実装:

// ✅ キャッシュが必要
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
@Cacheable(value = "products", key = "#id")
public Product getProduct(Long id) {
// キャッシュから取得、なければデータベースから取得
return productRepository.findById(id)
.orElseThrow(() -> new ProductNotFoundException(id));
}
@CacheEvict(value = "products", key = "#product.id")
@Transactional
public Product updateProduct(Product product) {
// 更新時にキャッシュを無効化
return productRepository.save(product);
}
}

判断基準:

  • キャッシュが必要: 読み取り頻度が高い
  • キャッシュ無効化が必要: 更新時にキャッシュを無効化

判断基準5: マイクロサービス化

Section titled “判断基準5: マイクロサービス化”

条件:

  • チームサイズが小規模(10人以下)
  • 開発速度が最重要
  • シンプルなアプリケーション

判断基準:

  • モノリスで十分: チームサイズが小規模
  • マイクロサービス化は不要: 開発速度が最重要

マイクロサービス化が必要なケース

Section titled “マイクロサービス化が必要なケース”

条件:

  • チームサイズが大規模(10人以上)
  • 独立したデプロイが必要
  • 異なる技術スタックが必要

判断基準:

  • マイクロサービス化が必要: チームサイズが大規模
  • 独立したデプロイが必要: サービスごとに独立してデプロイ

過剰設計にならないための判断基準のポイント:

  • トランザクション境界: シンプルなケースではシンプルな実装で十分
  • Serverless環境: Vercel環境では同期処理、Render.com環境では非同期処理
  • エラーハンドリング: エラーの影響に応じて適切なレベルを選択
  • キャッシュ: データの特性に応じてキャッシュを使用
  • マイクロサービス化: チームサイズと要件に応じて判断

適切な判断基準により、過剰設計を避け、効率的なシステムを構築できます。