【问题标题】:Spring REST API architectureSpring REST API 架构
【发布时间】: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


    【解决方案1】:
    should I still use services in each controller method?
    

    始终建议我们需要使用服务来处理业务逻辑。此时,您可以认为不需要它,因为您只有有限的 API 调用,但请考虑,如果您有任何新要求需要使用相同的 Rest Controller 完成,并且您正在管理控制器类本身中的所有内容,这将是看起来很乱,不推荐。

    我们需要使用服务来使控制器类更轻、更好。但是您不需要为每个控制器类提供服务。(至少在我看来)

    我希望这会有所帮助:)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-12-16
      • 1970-01-01
      • 1970-01-01
      • 2016-07-07
      • 2021-12-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多