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 変換、LocalDateTime → OffsetDateTime は 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 を @MockBean、TasksAuthenticationToken の mock は security/adapter/web/SecurityConfigTest を参考
- ケース: (a) 200 OK + JSON、(b)
TaskNotFoundException throw → 404、(c) TaskNotViewableException throw → 404
やらないこと
受け入れ条件
参照
B-1 (#273) で整えた基盤(
TaskPOJO +TaskRepositoryポート +TaskJpaEntity)を使って、GET /api/tasks/{id}endpoint を 4 層構造で実装する。N4 (#86) を 7 分割した B-2 スコープ:
規約準拠の前提
@Transactional境界明示、readOnly属性必須やること
1.
task/usecase/GetTaskUseCase.java新規@Transactional(readOnly = true)をクラスレベルに明示(規約 §11.1)。visibility チェックはTaskAuthorizationDomainService.canViewに委譲(既存利用、無ければ B-2 で追加)。2.
task/domain/TaskNotFoundException.java新規DomainException(B-1 で導入)継承、@ResponseStatus不使用(TaskExceptionHandlerで 404 マッピング)。3.
task/domain/TaskNotViewableException.java新規TaskNotFoundExceptionと同じ構造、メッセージのみ違う(404 として返す、情報漏洩防止のため Visibility 違反でも 404 統一)。4.
task/adapter/web/TaskController.java新規TasksAuthenticationToken(A-2 でsecurity/adapter/web/に移動済)から tenantId / userId / role を取得。5.
task/adapter/web/TaskExceptionHandler.java新規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)from(Task domain)で Domain → Response 変換、LocalDateTime→OffsetDateTimeは JST 統一(2026-05-26 確定方針)に従うpackage-info.javaは@NullMarked7. テスト
GetTaskUseCaseTest(単体、新規):TaskRepository+TaskAuthorizationDomainServiceを@Mock、@ExtendWith(MockitoExtension.class)findByIdAndTenantIdempty →TaskNotFoundException、(c)canViewfalse →TaskNotViewableException、(d) PRIVATE visibility で本人(assignee)アクセス OKTaskControllerWebMvcTest(WebMvc slice、新規):@WebMvcTest(TaskController.class)+@Import(TaskExceptionHandler.class)GetTaskUseCaseを@MockBean、TasksAuthenticationTokenの mock はsecurity/adapter/web/SecurityConfigTestを参考TaskNotFoundExceptionthrow → 404、(c)TaskNotViewableExceptionthrow → 404やらないこと
created_by/updated_by列追加 → B-3 (backend: DB migration created_by/updated_by + 統合 IT + AuthZ test 更新 (N4 / B-3) #279)TaskCrossTenantIT→ B-3 (backend: DB migration created_by/updated_by + 統合 IT + AuthZ test 更新 (N4 / B-3) #279)TaskAuthorizationDomainServiceTestadmin bypass 更新 → B-3 (backend: DB migration created_by/updated_by + 統合 IT + AuthZ test 更新 (N4 / B-3) #279)受け入れ条件
GetTaskUseCase@Transactional(readOnly = true)クラスレベル明示TaskController+TaskExceptionHandler+TaskResponse+ dto/package-info.java 配置TaskNotFoundException+TaskNotViewableExceptionはDomainException継承GetTaskUseCaseTest全 4 ケース greenTaskControllerWebMvcTest全 3 ケース green./gradlew :webapi:testgreen、ApplicationModules.verify()green参照