【发布时间】:2020-09-04 20:45:26
【问题描述】:
我最近开始学习 REST API 并且已经遇到了架构问题。为了不吓到很多代码,我将尝试简要描述一下。我有这样的域对象结构:User -> TaskCard -> Task。即用户有一个任务卡列表,每个任务卡都有一个任务列表。所以,我对控制器的第一个疑问。在这里有必要澄清我使用 Spring Security 并且在控制器的每个方法中我都会获得一个用户来验证它。我也使用 Spring Data JPA。即,例如,为了防止一个用户试图改变另一个用户的任务卡的事实。这是一个TaskCardController:
@RestController
@RequestMapping("taskcards")
public class TaskCardController {
private TaskCardService taskCardService;
@Autowired
public TaskCardController(TaskCardService taskCardService) {
this.taskCardService = taskCardService;
}
@GetMapping
public List<TaskCardDto> getAllTaskCards(@CurrentUser User user) {
return user.getTaskCards()
.stream()
.map(TaskCardMapper.INSTANCE::toDto)
.collect(Collectors.toList());
}
@GetMapping("{id}")
public TaskCardDto getTaskCard(@CurrentUser User user, @PathVariable Long id) {
return TaskCardMapper.INSTANCE.toDto(user.getTaskCard(id));
}
@PostMapping
public TaskCardDto createTaskCard(@CurrentUser User user, @RequestBody TaskCardDto taskCardDto) {
TaskCard taskCard = TaskCardMapper.INSTANCE.toEntity(taskCardDto);
return TaskCardMapper.INSTANCE.toDto(taskCardService.saveTaskCard(taskCard, user));
}
@PutMapping("{id}")
public TaskCardDto updateTaskCard(@CurrentUser User user, @PathVariable("id") Long id, @RequestBody TaskCardDto replacementDto) {
TaskCard existing = user.getTaskCard(id);
TaskCard replacement = TaskCardMapper.INSTANCE.toEntity(replacementDto);
return TaskCardMapper.INSTANCE.toDto(taskCardService.updateTaskCard(existing, replacement));
}
@DeleteMapping("{id}")
public void deleteTaskCard(@CurrentUser User user, @PathVariable("id") Long id) {
taskCardService.deleteTaskCard(user.getTaskCard(id));
}
}
(@CurrentUser 是@AuthenticationPrincipal 的包装器。也就是说,这里 spring security 向我发送了导致此请求的用户)。
如您所见,我不使用该服务来获取用户卡,而是使用真实的用户实例。那么,这样做不好吗?我不确定我在做什么正确,因为每个人都在到处使用服务,但我只在两种方法中使用它。我这样做是因为在我看来它更好,因为如果我通过服务来做,我将不得不创建新的方法,如findAllForUser() 或findAllByIdAndUserIs()(这是用于获取 id)。所以,问题是,我是在做正确的事,还是应该在每个控制器方法中仍然使用服务?这里还应该注意的是,如果控制器使用服务,则更容易测试,因为我可以模拟服务,而对用户来说会很困难。但是在使用服务时,还有另一个问题:与任务交互。现在要获得任务,我有以下控制器:
@RestController
@RequestMapping("taskcards/{cardId}/tasks")
public class TaskController {
private TaskService taskService;
@Autowired
public TaskController(TaskService taskService) {
this.taskService = taskService;
}
@GetMapping
public List<TaskDto> getAllTasks(@CurrentUser User user, @PathVariable("cardId") Long cardId) {
return user.getTaskCard(cardId)
.getTasks()
.stream()
.map(TaskMapper.INSTANCE::toDto)
.collect(Collectors.toList());
}
@GetMapping("{id}")
public TaskDto getTask(@CurrentUser User user, @PathVariable("cardId") Long cardId, @PathVariable Long id) {
Task task = user.getTaskCard(cardId).getTask(id);
return TaskMapper.INSTANCE.toDto(task);
}
@PostMapping
public TaskDto createTask(@CurrentUser User user, @PathVariable("cardId") Long cardId, @RequestBody TaskDto taskDTO) {
Task task = TaskMapper.INSTANCE.toEntity(taskDTO);
TaskCard taskCard = user.getTaskCard(cardId);
return TaskMapper.INSTANCE.toDto(taskService.saveTask(task, taskCard));
}
@PutMapping("{id}")
public TaskDto updateTask(@CurrentUser User user, @PathVariable("cardId") Long cardId,
@PathVariable("id") Long id, @RequestBody TaskDto replacementDto) {
Task existing = user.getTaskCard(cardId).getTask(id);
Task replacement = TaskMapper.INSTANCE.toEntity(replacementDto);
return TaskMapper.INSTANCE.toDto(taskService.updateTask(existing, replacement));
}
@DeleteMapping("{id}")
public void deleteTask(@CurrentUser User user, @PathVariable("cardId") Long cardId, @PathVariable("id") Long id) {
taskService.deleteTask(user.getTaskCard(cardId).getTask(id));
}
}
正如您在此处看到的,我的工作原理相同,即我从真实的用户实例中获取数据。而这里添加服务真的很麻烦,因为这种情况下首先需要在数据库中找到任务卡,检查是否属于这个用户,然后再找到需要的任务。例如,对于带有 id 的 GET 方法,我必须为服务和存储库 findByIdAndTaskCardIdAndTaskCarduser() 创建以下方法。这太糟糕了,我必须向数据库发出额外的请求,并为存储库和服务创建额外的方法。总的来说,请告诉我如何制作更好的架构?我还不太擅长 REST API,所以我不知道如何正确地做到这一点
【问题讨论】:
标签: java spring rest spring-security spring-data-jpa