@Service类应该和@Controller类有相同的接口吗?

huangapple go评论78阅读模式
英文:

Should @Service classes have the same interface as @Controller classes?

问题

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

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

  • 控制器(映射端点和处理)
  • 服务(处理业务逻辑)
  • 存储库(抽象和与数据库交互)

我们还有一个实体包,其中包含表示数据库实体的所有类。
我们还使用一个库将实体与API模型进行映射。

没有持续多久,我们团队内部进行了第一次讨论。因此我认为向外界寻求建议是个好主意。

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

我反对这种方法,因为如果我们这样做,服务和控制器将最终具有相同的接口,这感觉不对。

然而,我赞同控制器应尽可能少地包含代码的原则。
我认为控制器方法应仅接受并返回API模型,控制器中不应存在实体的概念。

与此同时,我认为服务方法应接受并返回实体,而不是与Api模型一起操作。

我们是否应该在控制器和服务之间引入一个新层?是否有一些标准与此相关,我不知道?

我将给您一个具体的例子,涉及两种方法(我的方法和我的合作伙伴的方法):

第一种方法:

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模型的映射以及反向映射都在控制器中完成。在第二种方法中,这种映射在服务中完成。

您会推荐哪一种?或者您认为在控制器和服务之间引入新的层更好?

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

英文:

We are a small team starting a new project, and developing the back-end using Spring-Boot framework.

The back-end project will have the following standard layers of a Spring-Boot application:

  • Controller (map the endpoints and handle)
  • Service (to handle business logic)
  • Repository (to abstract and interact with the database)

We also have an entity package where all the classes representing database entities are located.
We also use a library to map entities with Api models.

It did not last long and our first debate within the team occurred. So I thought it's a good idea to ask for advice out there.

My other partners think that the controllers should contain as few lines of code as possible.
Ideally containing only one line of code, where the respective Service is called, and the rest is handled in the Service.

I am against that approach, because if we would do that, the Service and the Controller would end up having the same interface, which feels wrong.

However, I agree with the principle that the controllers should have as few lines of code as possible.
I think that the controllers methods should accept and return only Api models, and no concept of entities should be present in the controllers.

At the same time, I think that the services method should accept and return entities, and not work with ApiModels.

Should we introduce a new layer between Controller and Service? Is there some standard, for which I am not aware, related to this topic?

I am going to give you a concrete example with two approaches (mine and my partners'):

First approach:

public class UserController implements UsersApi {

  @Autowired
  private UserService userService;

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

And the service:

public class UserService {

  @Autowired
  private UserRepository repository;

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

}

The second approach:

public class UserController implements UsersApi {

  @Autowired
  private UserService userService;

  @Override
  public ResponseEntity&lt;UserAPIModel&gt; createUser(@Valid UserAPIModel body) {
    return ResponseEntity.ok(userService.createUser(body));
  }
}

and the service:

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);
  }

}

As you can see in the first approach, the mapping from entity to Api Model and vice versa is done in the Controller. In the second approach this mapping is done in the Service.

Which one would you recommend? Or do you think it's better to introduce a new layer between Controller and Service?

Making a decision on this simple example, can help us to create a more general rule and set a standard for similar problems in the future.

答案1

得分: 2

首先,你的问题非常宽泛,将许多问题合并在一个帖子中,这在该网站上是不应该做的。将来,请尽量提出更加专注的问题。

总之:

  1. @ControllerWeb 层,即负责处理 HTTP 请求-响应的层,你的 Java 代码在这一层应该语义上与 web 打交道,也就是说,控制器的责任应该是接受 HTTP 请求,并以 HTTP 响应进行回应。是否在中间进行一些逻辑处理,不应该完全成为控制器的责任,它应该依赖于它的依赖项(大多数情况下是 @Service)。

  2. @Service业务层,即负责处理与业务相关的任务的层,所有主要的业务逻辑、事务、文件处理、批处理等都可能在这里进行。

你可以混合使用这两个,但这不是一个好主意。更好的做法是遵循清晰的关注点分离模式,其中 @Controller 负责 Web 层,@Service 负责业务逻辑。


你提到的几点:

> 我的其他合作伙伴认为控制器应该尽量少的包含代码行

不一定,这取决于情况。控制器可能会执行一些工作,具体取决于情况,没有控制器特定的“代码整洁之道”实践。《代码整洁之道》告诉你,一般来说,你的方法不应该太长、太乱(有一个有趣的规则,即没有一个方法的长度应该超过你的五个手指的宽度,水平放置时),但同样,这是非常普遍和有争议的规则;

> 理想情况下只有一行代码

不要将此视为规则。除非你的控制器除了调用某些服务 API(比如 return findAll();)之外什么都不做,否则很少会是真实情况;

> 在相应的 Service 被调用后,其余部分就在 Service 中处理了

你的合作伙伴是对的,如果他/她的意思是,与业务相关的(即事务、数据库访问、批处理或任何其他类型的过程,超出了 Web 层的责任范围)最好在 Service 层中处理;

@Service 组件的定位是充当
业务服务外观;

> 我反对那种方法,因为如果我们那样做,Service 和 Controller 最终会有相同的接口,感觉不对。

不会的。@Controller 不需要任何 Java 接口,在这个短语的经典意义上(例如 public interface Foo{..}),它是一个由 Spring 管理的组件,你不会注入它的实现,而这在谈论 @Service 组件时并不适用;

> 然而,我同意这个原则,即控制器应尽量少地包含代码行。

不要将这个概念应用在控制器上。方法一般来说应该尽可能清晰,如果控制器有点长也没有问题。这与控制器本身无关,关键是你方法的设计,无论你是否能够提取/抽象出一些内容,使你的代码更清晰。然而,在可能的情况下应用YAGNI 原则,不要过早优化控制器的代码行数。不要让代码变得太长,但是在这里不要过于计较行数;

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

不对。@Controller 的责任与 API 模型不直接相关,它的责任是接受 HTTP 消息,对其进行处理(可以借助 @Service、UtilityClass 等),并通过 HTTP 响应进行回应。在某些情况下,你可以让控制器直接依赖于数据访问层,但这同样取决于情况。

你是否应该接受模型等问题,也在很大程度上取决于情况,你可以看一下我关于是否应该使用 DTO 的另一个回答

> 我们应该在 Controller 和 Service 之间引入一个新的层吗?

不应该,除非有必要。控制器应该是请求最先到达的地方;通常它会依赖于 Service,而 Service 最终会依赖于数据持久化层(Spring Data、JDBC、集成或任何其他内容)。

英文:

First of all, your question is very broad, which incorporates many questions in one post, which you should not be doing on this site. In the future, try to have more focused questions instead.

TL;DR:

  1. @Controller is a Web-Layer, i.e. layer responsible to handle HTTP Request-Response responsibility, where your Java code should semantically talk web, i.e. responsibility of your controller should be to accept the HTTP Request, and respond with HTTP Response. Whether or not you do some logic in between, should not be much a pure responsibility of controller, it should stand on the shoulders of its dependencies (@Service, in most cases).

  2. @Service is a Business-Layer, i.e. layer responsible to process business-related tasks, where all the major business-logic, transactions, file processing, batch processing or etc. may be taking place.

You can mix-and-match these two, but it's not a good idea. Rather follow a clear separation of concerns pattern, where @Controller will be responsible for web layer, and @Service - for business logic.


Your points:

> My other partners think that the controllers should contain as few lines of code as possible

Not necessarily, it depends. Controller might do some work, depending what is the given circumstance and there is no controller-specific Clean Code practice. Clean Code instructs you, that generally, your methods should not be too long and messy (there is a funny rule, that no method should go longer than a height of your 5 fingers, when laid horizontally), but again, this is very generic and debatable rule;

> Ideally containing only one line of code

Do not follow this as a rule. This is rarely real, unless your controller does nothing except calling some service API (like return findAll(););

>where the respective Service is called, and the rest is handled in the Service

You partner is right, if he/she means, that business-related (that is transactional, DB accessor, Batch, or any other type of process, that is OUT OF the web-layer responsibility) should better be handled in a Service layer;

@Service components are oriented to be acting as
Business Service Facade;

>I am against that approach, because if we would do that, the Service and the Controller would end up having the same interface, which feels wrong.

No, they will not. @Controller does not need any Java Interface, in the classical sense of this phrase (e.g. public interface Foo{..}), it's a Spring managed component, and you don't inject its implementations, which is not the case when we're talking about @Service components;

>However, I agree with the principle that the controllers should have as few lines of code as possible.

Don't conceptualize this on controller. Method, generally, should be as clear as possible, and there is nothing wrong if controller is a bit long.. it's not about controller, as such, it's about your methods' design, whether or not you can extract/abstract something out and make your code cleaner. However, apply YAGNI principle when possible and don't rush with premature optimization with respect of the controller's code lines. Don't go too long.. but don't bother yourself much here on counting lines;

>I think that the controllers methods should accept and return only API models, and no concept of entities should be present in the controllers.

Wrong. @Controller's responsibility is not directly bound to the API model, it's responsibility is to accept HTTP Message, do something with it (either with the help of @Service, UtilityClass, or etc.) and respond back with HTTP Response. In some cases, you can make your controller be dependent on Data Access layer directly, but again - it depends.

Whether you'll have accepting models or not, it also very much depends and you can see my other answer explaining whether you should use DTO or not;

>Should we introduce a new layer between Controller and Service?

NO, unless it's necessary. Controller should be the first layer where your request ends up; it should usually rely on Service, and Service, eventually, will rely on Data Persistence layer (Spring Data, JDBC, Integrations, or any other thing).

答案2

得分: 0

大家好!
我几个月前也开始使用Spring Boot进行工作,我可以告诉你,从个人经验来看,最好在控制器(Controller)和服务(Service)之间创建一个映射器(Mapper),以便更好地进行操作。一般来说,尤其在这类项目中,务必要多样化,不要让控制器或服务执行其用途之外的任务。在这种情况下,控制器只需要启动一个命令,不应直接与存储库(Repo)有任何形式的直接连接。根据经验,我告诉你,在较大的项目中,这种思维方式会产生回报。

英文:

Ho guys!
I too started working on Springboot a few months ago, I can tell you that from personal experience it is better to create a Mapper between Controller and Service in order to proceed better. Generally, especially in this type of projects, it is imperative to diversify and not to make the Controller or the Service do tasks outside of their use. The controller in this case you only need to start a command, there shouldn't be any kind of direct connections to Repo either. from experience I tell you that in larger projects this mentality pays off

huangapple
  • 本文由 发表于 2020年10月16日 21:15:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/64389919.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定