首页 > 解决方案 > @Service 类是否应该与@Controller 类具有相同的接口?

问题描述

我们是一个小团队,开始一个新项目,并使用 Spring-Boot 框架开发后端。

后端项目将具有以下 Spring-Boot 应用程序的标准层:

我们还有一个实体包,代表数据库实体的所有类都位于其中。我们还使用库来映射实体与 Api 模型。

它并没有持续多久,我们在团队中发生了第一次辩论。所以我认为在外面寻求建议是个好主意。

我的其他伙伴认为控制器应该包含尽可能少的代码行。理想情况下只包含一行代码,调用相应的服务,其余的在服务中处理。

我反对这种方法,因为如果我们这样做,Service 和 Controller 最终会拥有相同的界面,这感觉不对。

但是,我同意控制器应具有尽可能少的代码行的原则。我认为控制器方法应该只接受和返回 Api 模型,并且控制器中不应该存在实体的概念。

同时,我认为 services 方法应该接受和返回实体,而不是与 ApiModels 一起使用。

我们是否应该在 Controller 和 Service 之间引入一个新层?是否有一些我不知道的与该主题相关的标准?

我将用两种方法(我的和我的合作伙伴的)给你一个具体的例子:

第一种方法:

public class UserController implements UsersApi {

  @Autowired
  private UserService userService;

  @Override
  public ResponseEntity<UserAPIModel> createUser(@Valid UserAPIModel body) {
    User createdUser = userService.save(UserMapper.INSTANCE.toUserEntity(body));
    return ResponseEntity.ok(UserMapper.INSTANCE.toUserAPIModel(createdUser));
  }
}

和服务:

public class UserService {

  @Autowired
  private UserRepository repository;

  public User save(User entity) {
    return repository.save(entity);
  }

}

第二种方法:

public class UserController implements UsersApi {

  @Autowired
  private UserService userService;

  @Override
  public ResponseEntity<UserAPIModel> createUser(@Valid UserAPIModel body) {
    return ResponseEntity.ok(userService.createUser(body));
  }
}

和服务:

public class UserService {

  @Autowired
  private UserRepository repository;

  public UserAPIModel createUser(UserAPIModel body) {
    User user = repository.save(UserMapper.INSTANCE.toUserEntity(body));
    return UserMapper.INSTANCE.toUserAPIModel(user);
  }

}

正如您在第一种方法中看到的,从实体到 Api 模型的映射(反之亦然)是在 Controller 中完成的。在第二种方法中,这种映射是在服务中完成的。

你会推荐哪一个?还是您认为在 Controller 和 Service 之间引入一个新层更好?

对这个简单的例子做出决定,可以帮助我们创建一个更通用的规则,并为将来的类似问题设定一个标准。

标签: javaspring-bootobject-oriented-analysis

解决方案


首先,您的问题非常广泛,在一篇文章中包含了许多问题,您不应该在本网站上这样做。将来,请尝试提出更集中的问题。

TL;博士:

  1. @Controller是一个Web-Layer,即负责处理 HTTP 请求响应责任的层,您的 Java 代码应该在语义上谈论web,即您的控制器的责任应该是接受 HTTP 请求,并用 HTTP 响应进行响应。无论你是否在两者之间做一些逻辑,不应该是控制器的纯粹责任,它应该站在其依赖项的肩膀上(@Service,在大多数情况下)。

  2. @Service是一个业务层,即负责处理业务相关任务的层,所有主要的业务逻辑、事务、文件处理、批处理等都可能在其中发生。

您可以混合搭配这两者,但这不是一个好主意。而是遵循明确的关注点分离模式,@Controller 将负责 Web 层,@Service - 负责业务逻辑。


你的观点:

我的其他伙伴认为控制器应该包含尽可能少的代码行

不一定,要看情况。控制器可能会做一些工作,这取决于给定的情况,并且没有特定于控制器的清洁代码实践。Clean Code 告诉你,一般来说,你的方法不应该太长和太乱(有一个有趣的规则,当水平放置时,任何方法都不应该超过你 5 个手指的高度),但是同样,这是非常通用的和有争议的规则;

理想情况下只包含一行代码

请勿将此作为一项规则。这很少是真实的,除非你的控制器除了调用一些服务 API(比如)之外什么都不做return findAll();

调用相应服务的位置,其余在服务中处理

您的合作伙伴是对的,如果他/她的意思是,与业务相关的(即事务性、数据库访问器、批处理或任何其他类型的流程,不属于 Web 层责任)应该更好地在服务层中处理;

@Service 组件被定位为充当 业务服务门面;

我反对这种方法,因为如果我们这样做,Service 和 Controller 最终会拥有相同的界面,这感觉不对。

不,他们不会。@Controller 不需要任何 Java 接口,在这个短语的经典意义上(例如public interface Foo{..}),它是一个 Spring 托管组件,你不需要注入它的实现,当我们谈论 @Service 组件时,情况并非如此;

但是,我同意控制器应具有尽可能少的代码行的原则。

不要在控制器上对此进行概念化。方法,一般来说,应该尽可能的清晰,如果控制器有点长也没什么问题。这与控制器无关,因此,它与你的方法设计有关,你是否可以提取/抽象一些东西让你的代码更干净。但是,尽可能应用YAGNI 原则,不要急于对控制器的代码行进行过早的优化。不要走太久......但不要在这里为数线而烦恼;

我认为控制器方法应该只接受和返回 API 模型,并且控制器中不应该存在实体的概念。

错误的。@Controller 的职责不直接绑定到 API 模型,它的职责是接受 HTTP 消息,用它做一些事情(在@Service、UtilityClass 等的帮助下)并用 HTTP 响应响应。在某些情况下,您可以使您的控制器直接依赖于数据访问层,但同样 - 这取决于。

无论您是否有接受模型,这也取决于您,您可以查看我的其他答案来解释您是否应该使用 DTO;

我们是否应该在 Controller 和 Service 之间引入一个新层?

NO,除非有必要。控制器应该是您的请求结束的第一层;它通常应该依赖于服务,而服务最终将依赖于数据持久层(Spring Data、JDBC、集成或任何其他东西)。


推荐阅读