使用一个控制器处理多种资源的利弊

Dmi*_*ich 1 java rest model-view-controller design-patterns spring-mvc

我们有一个 Spring MVC 应用程序,大多数 REST 操作是每个资源唯一的 GET 操作。因此,目前我们有许多控制器,其唯一的 GET 方法彼此没有区别(即使在 url、内容类型、参数等方面)。

为了消除这种重复,我们的团队成员提供了一个控制器,其中只有一个 GET 操作和一个带有服务的映射(资源名称 -> 资源服务)。

但是我们看到了诸如Spring注入调优更复杂,没有机会添加对内容类型,参数的一些限制 - 一言以蔽之的自定义操作等缺点。此外,还有多个资源驻留在单独的控制器中。此外,我不希望至少有一种简单的方法可以以多种方式记录 Swagger 中的唯一方法(有不同的描述)。

所以对我来说,一方面是代码较少,但另一方面是操作定制、架构混合、缺乏适当的文档或至少复杂的配置的机会有限。我认为在这里制定一种方法不是一个好方法。

我对吗?如果是我怎么证明。如果不是为什么?感谢您的时间和想法!

小智 6

你是对的。简而言之,根据单一职责原则,每个控制器应该只做一项工作(只处理一个 URL)。

您完美地描述了通用控制器将处理的问题。还要考虑一下,如果某个控制器现在完全符合通用规则,但下个月需要特定的东西怎么办?您必须复制粘贴代码,然后添加新代码。所以一段时间后,你会被庞大而复杂的通用控制器重复的代码弄得一团糟。没有人可以预测它的速度有多快,因为开发团队可能会意外地添加业务需求。

另一方面,您的队友减少重复代码的愿望是正确的。至少不是所有的开发人员都想把时间花在让代码更干净上。而大多数人需要获得认可(确保他们的意见有价值)。所以不要把他送走:)

我可以推荐什么:引入抽象父级并为类似的控制器使用继承和模板模式

/** Interface mainly works as a marker. 
  At first look, interface isn't necessary but it'll improve maintainability.
  Next year you say 'thank you' to yourself  */
interface IController { 
  //some methods which will implement EACH controller even the most specific
  public void doGet(params)
}
abstract class AbstractController implements IController {
 /** Class implements default behavior for controllers.
     Implementation written so child classes could adopt behaviour easily*/
  @Override
  public void doGet(params) {
    // use Template pattern
    doLog(params);
    prepareStuff();
    process();
  }
  // common stuff which should be done at first
  protected void doLog(params) { // your favorite logger here}
  // extension point for inherited classes
  abstract protected void prepareStuff();
  // here is the real processing for default controller
  public void process() {
    //implement common processing for GET request
  }
  // Prefer to use protected method instead of field
  protected String getURL() { return DEFAULT_URL;}
}
// usual controller has nothing special
class Controller1 extends AbstractController {
  @Override
  protected String getURL() { return "url1";}
  @Override
  protected prepareStuff() {// do nothing}
}
// do some specific preparation/checks
class Controller2 extends AbstractController {
  @Override
  protected prepareStuff() {//preparation/checks here }
  /** Note I 'forget' to override getURL() so it'll process DEFAULT_URL.
   It could be useful if AbstractController calculates url dynamically and
   you don't want to write boilerplate strings "/myApp/section7".
   Also you could write abstract getURL()
  */
}
/** custom controller but you want to re-use some common code. 
In fact I would not recommend this way as usual approach */
class Controller3 extends AbstractController {
  /** Overriding this method totally discards the Template pattern. 
      It could (and will) lead to confusing and errors*/
  @Override
  public void doGet(params) { // new implementation }
  @Override
  protected prepareStuff() {
    // you don't need it but you have to override since it abstract
  }
}
// totally custom controller. Implements interface just as a marker
class SpecificController implements Controller {
  // In order to support legacy code just call method wich has been already written. You even no need to rename it.
  @Override
  public void doGet(params) { specificMethod();}
  // lagacy method which probably is used somewhere else
  public void specificMethod() {  // the actual logic here}
}
Run Code Online (Sandbox Code Playgroud)

事实上,我在一个项目中假设了类似的解决方案。使用诸如“引入方法”和“移至父级”之类的 IDE 功能,我在一天内重构了数十个类。

希望你或你的队友能在几天内实现和比较这样的想法