mcpField
L
layered-architecture

layered-architecture

github.com/rrezartprebreza開発328120 回インストール6 日前に更新
リポジトリ

layered-architecture について

Spring アプリケーション開発者向けのレイヤードアーキテクチャガイドです。コントローラー、サービス、リポジトリ、DTO の責務を定め、マッピングや設定、横断的関心事の扱いも説明します。

主な機能

  1. 01コントローラー・サービス・リポジトリの責務
  2. 02リクエスト DTO とレスポンス DTO の分離
  3. 03専用マッパーと静的ファクトリーメソッド
  4. 04設定レイヤーの構成規則
  5. 05トランザクション・検証・例外処理の規則

活用シーン

  1. 01Spring アプリのレイヤー責務の設計
  2. 02コントローラーから業務ロジックを分離
  3. 03エンティティからレスポンス DTO へのマッピング設計
  4. 04トランザクションと例外処理の配置整理

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/rrezartprebreza/spring-boot-skills --skill layered-architecture

インストール後、Claude Code で/layered-architectureを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

agents/
openai.yaml
examples/
bad-service.java
good-service.java
templates/
CreateOrderRequest.java
OrderMapper.java
OrderResponse.java

詳細

レイヤードアーキテクチャ

レイヤーのルール

@RestController        ← HTTP only. No business logic. No JPA entities in responses.
      ↓ DTOs
@Service               ← All business logic lives here. Orchestrates repositories.
      ↓ Domain objects / Entities
@Repository            ← Data access only. No business logic. Returns entities or projections.
      ↓ JPA / JDBC
Database

コントローラーレイヤー

  • HTTPを処理します。リクエストの解析、入力の検証(@Valid)、レスポンスの返却を行います
  • エンドポイントごとに呼び出すサービスメソッドは1つだけです。コントローラーでオーケストレーションを行いません
  • @Entity クラスを直接返しません。必ずレスポンスDTOにマッピングします
  • @Repository を注入しません。必ず @Service を経由します
  • 例外処理には @ControllerAdvice を使用します。コントローラー内で try/catch を使用しません
// ✅ GOOD
@PostMapping("/orders")
public ResponseEntity<OrderResponse> createOrder(@Valid @RequestBody CreateOrderRequest request) {
    Order order = orderService.createOrder(request);
    return ResponseEntity.status(HttpStatus.CREATED).body(OrderResponse.from(order));
}

// ❌ BAD — business logic in controller
@PostMapping("/orders")
public ResponseEntity<Order> createOrder(@RequestBody CreateOrderRequest request) {
    if (request.getItems().isEmpty()) throw new RuntimeException("No items");
    Order order = orderRepository.save(new Order(request)); // direct repo access
    return ResponseEntity.ok(order); // returning entity
}

サービスレイヤー

  • すべてのビジネスロジック、検証ルール、オーケストレーションを含みます
  • @Transactional はここに配置し、コントローラーやリポジトリには配置しません
  • コンストラクターインジェクションのみを使用し、@Autowired によるフィールドインジェクションは使用しません
  • 集約ルートごとにサービスを1つ作成します(OrderAndPaymentService ではなく OrderService)
  • ドメインオブジェクトまたはDTOを返し、HttpServletRequest / HttpServletResponse は返しません
// ✅ GOOD
@Service
@RequiredArgsConstructor
public class OrderService {
    private final OrderRepository orderRepository;
    private final InventoryService inventoryService;

    @Transactional
    public Order createOrder(CreateOrderRequest request) {
        inventoryService.reserve(request.items()); // records use accessor names, not getX()
        Order order = Order.from(request);
        return orderRepository.save(order);
    }
}

// ❌ BAD — field injection, HTTP concern in service
@Service
public class OrderService {
    @Autowired private OrderRepository orderRepository;

    public ResponseEntity<Order> createOrder(...) { ... } // HTTP type in service
}

リポジトリレイヤー

  • JpaRepository<Entity, ID> または CrudRepository を継承します
  • カスタムクエリには @Query またはクエリ派生を使用します。やむを得ない場合を除き、生のSQLは使用しません
  • エンティティまたはSpring Data Projectionを返し、生の Object[] は返しません
  • ビジネスロジックは含めず、データアクセスのみを行います

DTO

  • Request DTOとResponse DTOは分け、同じクラスを両方に使用しません
  • 検証アノテーション(@NotNull、@Size など)はRequest DTOのみに付けます
  • マッピングには、静的ファクトリーメソッド ResponseDto.from(Entity entity) を使用します
  • 不変DTOにはレコードを使用します(Java 16以降)
// ✅ GOOD
public record OrderResponse(UUID id, String status, List<LineItemResponse> items, Instant createdAt) {
    public static OrderResponse from(Order order) {
        return new OrderResponse(order.getId(), order.getStatus().name(),
            order.getItems().stream().map(LineItemResponse::from).toList(),
            order.getCreatedAt());
    }
}

マッパーパターン

  • マッピングロジックをコントローラーやサービスに含めず、専用のマッパークラスまたは静的ファクトリーメソッドを使用します
  • マッパーは通常のクラスまたはユーティリティとし、依存関係の注入が必要でない限り、Spring Beanにはしません
  • Entity → Response DTO:レスポンスDTOの静的メソッド(OrderResponse.from(order))を使用します
  • Request DTO → Entity:エンティティの静的ファクトリー(Order.from(request))またはマッパークラスを使用します
  • コレクションのマッピングには .stream().map(OrderResponse::from).toList() を使用し、手動ループは使用しません
// ✅ GOOD — dedicated mapper for complex mappings
public class OrderMapper {

    public static OrderResponse toResponse(Order order) {
        return new OrderResponse(
            order.getId(),
            order.getStatus().name(),
            order.getItems().stream().map(OrderMapper::toLineItem).toList(),
            order.getCreatedAt()
        );
    }

    public static Order toEntity(CreateOrderRequest request, User user) {
        Order order = Order.create(request.customerEmail(), user);
        request.items().forEach(item ->
            order.addItem(item.productId(), item.quantity()));
        return order;
    }

    private static LineItemResponse toLineItem(OrderItem item) {
        return new LineItemResponse(item.getProductId(), item.getQuantity(), item.getPrice());
    }
}

設定レイヤー

  • @Configuration クラスは config/ パッケージに配置し、service/ や controller/ には配置しません
  • 設定クラスからサービスクラスやコントローラークラスをインポートしません
  • 型安全な設定には @ConfigurationProperties を使用し、関連する設定のグループに生の @Value を使用しません
  • Bean定義はインフラストラクチャ関連のものに限定します(RestTemplate、ObjectMapper、SecurityFilterChain)

横断的関心事

  • ロギングには @Slf4j を使用し、System.out.println は使用しません
  • バリデーション:コントローラーパラメーターに @Valid を付け、カスタムバリデーターには @Component を付けます
  • 例外処理:@RestControllerAdvice クラスは1つにし、コントローラー内で try/catch を使用しません
  • 監査:@EnableJpaAuditing とともに @CreatedDate / @LastModifiedDate を使用します

注意点

  • エージェントは @Transactional をコントローラーに付けがちです。サービスに移動してください
  • エージェントは @Autowired によるフィールドインジェクションを使用します。必ずコンストラクターインジェクション(@RequiredArgsConstructor)を使用してください
  • エージェントはコントローラーから List<Entity> を返します。必ず List<ResponseDto> にマッピングしてください
  • エージェントは OrderAndInventoryService のような巨大なクラスを作成します。集約ごとに分割してください
  • エージェントはコントローラー内にマッピングロジックを記述します。マッパークラスまたはDTOのファクトリーメソッドに切り出してください
  • エージェントは @Service Beanに依存する @Configuration クラスを作成します。設定クラスではインフラストラクチャのみを配線してください

コメント