java - @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 之间引入一个新层更好?
对这个简单的例子做出决定,可以帮助我们创建一个更通用的规则,并为将来的类似问题设定一个标准。
解决方案
首先,您的问题非常广泛,在一篇文章中包含了许多问题,您不应该在本网站上这样做。将来,请尝试提出更集中的问题。
TL;博士:
@Controller
是一个Web-Layer,即负责处理 HTTP 请求响应责任的层,您的 Java 代码应该在语义上谈论web,即您的控制器的责任应该是接受 HTTP 请求,并用 HTTP 响应进行响应。无论你是否在两者之间做一些逻辑,不应该是控制器的纯粹责任,它应该站在其依赖项的肩膀上(@Service,在大多数情况下)。@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、集成或任何其他东西)。