Skip to content

backend: GET /api/tasks/{id} 4 層実装(UseCase + Controller + DTO + 例外) (N4 / B-2) #278

Description

@win2cot

B-1 (#273) で整えた基盤(Task POJO + TaskRepository ポート + TaskJpaEntity)を使って、GET /api/tasks/{id} endpoint を 4 層構造で実装する。

N4 (#86) を 7 分割した B-2 スコープ:

規約準拠の前提

規約 / ADR 内容
コーディング規約 §11 (v1.2、PR #255) @Transactional 境界明示、readOnly 属性必須
ADR-0005 visibility / admin bypass

やること

1. task/usecase/GetTaskUseCase.java 新規

@Service
@Transactional(readOnly = true)
public class GetTaskUseCase {
  private final TaskRepository taskRepository;
  private final TaskAuthorizationDomainService taskAuthorizationDomainService;

  public Task execute(Long taskId, Long tenantId, Long userId, Role role) {
    Task task = taskRepository.findByIdAndTenantId(taskId, tenantId)
        .orElseThrow(() -> new TaskNotFoundException(taskId));
    if (!taskAuthorizationDomainService.canView(task, userId, role)) {
      throw new TaskNotViewableException(taskId);
    }
    return task;
  }
}

@Transactional(readOnly = true) をクラスレベルに明示(規約 §11.1)。visibility チェックは TaskAuthorizationDomainService.canView に委譲(既存利用、無ければ B-2 で追加)。

2. task/domain/TaskNotFoundException.java 新規

public class TaskNotFoundException extends DomainException {
  public TaskNotFoundException(Long taskId) {
    super("Task not found: " + taskId);
  }
}

DomainException(B-1 で導入)継承、@ResponseStatus 不使用(TaskExceptionHandler で 404 マッピング)。

3. task/domain/TaskNotViewableException.java 新規

TaskNotFoundException と同じ構造、メッセージのみ違う(404 として返す、情報漏洩防止のため Visibility 違反でも 404 統一)。

4. task/adapter/web/TaskController.java 新規

@RestController
@RequestMapping("/api/tasks")
public class TaskController {
  private final GetTaskUseCase getTaskUseCase;

  @GetMapping("/{id}")
  public TaskResponse get(@PathVariable Long id, TasksAuthenticationToken token) {
    Task task = getTaskUseCase.execute(id, token.getTenantId(), token.getUserId(), token.getRole());
    return TaskResponse.from(task);
  }
}

TasksAuthenticationToken(A-2 で security/adapter/web/ に移動済)から tenantId / userId / role を取得。

5. task/adapter/web/TaskExceptionHandler.java 新規

@RestControllerAdvice(assignableTypes = TaskController.class)
public class TaskExceptionHandler {
  @ExceptionHandler({TaskNotFoundException.class, TaskNotViewableException.class})
  @ResponseStatus(HttpStatus.NOT_FOUND)
  public Map<String, String> handleNotFound(DomainException ex) {
    return Map.of("error", ex.getMessage());
  }
}

assignableTypes = TaskController.class で scope を絞る。404 マッピング、Body は {"error": "..."} 形式。

6. task/adapter/web/dto/TaskResponse.java + package-info.java 新規

  • record TaskResponse(Long id, Long tenantId, String title, String description, Priority priority, TaskStatus status, Visibility visibility, Long assigneeId, LocalDate dueDate, OffsetDateTime createdAt, OffsetDateTime updatedAt)
  • static factory from(Task domain) で Domain → Response 変換、LocalDateTimeOffsetDateTime は JST 統一(2026-05-26 確定方針)に従う
  • package-info.java@NullMarked

7. テスト

GetTaskUseCaseTest (単体、新規):

  • TaskRepository + TaskAuthorizationDomainService@Mock@ExtendWith(MockitoExtension.class)
  • ケース: (a) 正常取得、(b) findByIdAndTenantId empty → TaskNotFoundException、(c) canView false → TaskNotViewableException、(d) PRIVATE visibility で本人(assignee)アクセス OK

TaskControllerWebMvcTest (WebMvc slice、新規):

  • @WebMvcTest(TaskController.class) + @Import(TaskExceptionHandler.class)
  • GetTaskUseCase@MockBeanTasksAuthenticationToken の mock は security/adapter/web/SecurityConfigTest を参考
  • ケース: (a) 200 OK + JSON、(b) TaskNotFoundException throw → 404、(c) TaskNotViewableException throw → 404

やらないこと

受け入れ条件

  • GetTaskUseCase @Transactional(readOnly = true) クラスレベル明示
  • TaskController + TaskExceptionHandler + TaskResponse + dto/package-info.java 配置
  • TaskNotFoundException + TaskNotViewableExceptionDomainException 継承
  • GetTaskUseCaseTest 全 4 ケース green
  • TaskControllerWebMvcTest 全 3 ケース green
  • ./gradlew :webapi:test green、ApplicationModules.verify() green
  • CI green、UT カバレッジ 80%+

参照

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/backendJava / Spring バックエンド実装変更priority/p1High。現スプリント内で完了必須

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions