用于对 Web 服务的请求和响应对象进行建模的设计模式

我有大约7个REST Web服务要实现。其中一些Web服务具有标准(相同)响应,而有些则具有不同的响应。

对这些 Web 服务的请求是不同的,但某些请求和某些响应具有相同的基础数据对象。

我不确定我是否必须为每个Web服务构建单独的请求/响应类或重用标准类。我想知道是否有设计模式来对这些Web服务的请求对象和响应对象进行建模。

好吧,假设帐户和书籍是我的Web服务将要处理的两个休息资源。

class Account {
    String username;
    String id;
}


class Book {
    String title;
    String isbn;
}

所以我的Web服务看起来像这样:

MYAPI/CreateAccountandBook
MYAPI/Account/Create
MYAPI/Book/Create
MYAPI/Book/Update/{isbn}
MYAPI/Account/Update/{id}
MYAPI/Account/getInfo/{id} 

等等。

现在,请求将获取有效负载中的帐户对象和书籍列表。此外,的响应对象具有一个帐户对象以及与该帐户关联的书籍列表。但响应对象还包括一个 和 。CreateAccountandBookMYAPI/Account/getInfo/{id}statusCodeDescription

现在,我想以最佳方式为这些请求和响应对象创建类。

好的,开始吧。

我有两个抽象类和.StandardRequestStandardResponse

所有请求类都将扩展标准请求类并相应地进行自定义。所有响应类都将扩展标准响应类并相应地进行自定义。

但是这些请求和响应可以彼此不同,但仍然会重复使用相同的实体对象。

例如:

createAccountandBook请求对象如下所示:

class CreateAccountAndBookRequest {
   Account account;
   List<Book> books;
}

而 Web 服务的响应是:getInfo

class GetInfoResponse {
   Account account;
   List<Book> books;
   String statusCode;
   String description;
}

因此,请求类和响应类之间存在重叠。我可以为每个 Web 服务创建两个 (req/res) 类。但想知道是否有更好的方法来对这些类进行建模。


答案 1

到目前为止,我在包括问题在内的所有答案中看到的一个大问题是,它们都违反了关注点分离,信息隐藏和封装的原则。在所有答案中,请求(和响应)类都与模型类紧密耦合。这是一个更严重的问题,并提出了一个比请求和响应之间的关系更重要的问题......

请求/响应类和模型类之间的关系应该是什么?

由于请求类(例如 CreateBookRequest)和模型类 Book 具有几乎相同的数据属性,因此您可以执行以下任何操作:

A. 将所有数据/获取者/setter 放入 Book 类,并让 CreateBookRequest 从该类扩展

B.让您的CreateBookRequest包含一本书作为成员(如ekostadinov,Juan Henao,.dasm80x86给出的通用用法也是这种情况的一个特例)

C. 将数据/获取器/setters 放在 BookBase 中,并让 Book 和 CreateBookRequest 都从 BookBase 扩展而来

D. 将所有/部分数据/获取者/setters 放入 BookStuff 中,并让 Book 和 CreateBookRequest 都包含 BookStuff

E. 将所有数据/获取者/setters 放在 Book 和 CreateBookRequest 中。(您可以复制粘贴)。

正确答案是E。我们都训练有素,渴望“重用”,这是最不直观的答案。

请求类 CreateBookRequest(以及响应类 CreateBookResponse)和模型类 Book 不应位于同一类层次结构中(除了两者都将 Object 作为最上面的父级)(A,C)。此外,CreateBookRequest 不应引用/包含模型 Book 或作为 Book 类 (B,D) 中成员的任何复合类。

其原因如下:

  1. 您希望彼此独立地修改模型对象或请求对象。如果您的请求引用了您的模型(如在A-D中),则模型中的任何更改都将反映在接口中,因此会破坏您的API。您的客户将根据请求/响应类指定的 API 编写客户端,并且他们不希望在您更改模型类时更改这些客户端。您希望请求/响应和模型独立变化。

  2. 关注点分离。您的请求类 CreateBookRequest 可能包含各种与接口/协议相关的注释和成员(例如,JAX-RS 实现知道如何强制实施的验证注释)。这些与接口相关的注释不应位于模型对象中。(如 A 中所示)

  3. 从OO的角度来看,CreateBookRequest不是一本书(不是IS_A),也不包含一本书。

控制流程应如下所示:

  1. 接口/控制层(接收 Rest-API 调用的接口/控制层)应使用专门为该层定义的 Request/Response 类作为其方法参数(例如 CreateBookRequest)。让容器/基础设施从 REST/HTTP/任何请求创建这些请求。

  2. 接口/控制层中的方法应以某种方式创建模型类对象的实例,并将值从请求类复制到模型类对象中,

  3. 接口/控制层中的方法应调用 BO/管理器/任何内容(在模型层中...它负责业务逻辑)传递给它模型类对象,而不是接口类/方法参数类对象(换句话说,不是Luiggi Mendoza在他的答案中显示的)

  4. model/BO 方法将返回一些模型类对象或一些“基元”。

  5. 现在,接口方法(调用方)应该创建一个接口类响应对象,并从模型/BO 返回的模型类对象中将值复制到其中。(就像路易吉·门多萨在他的答案中所示)

  6. 然后,容器/基础结构将从响应类对象创建 JSON/XML/任何响应。

现在来问这个问题...请求和响应类之间的关系应该是什么?

请求类应从请求类扩展,而不应扩展或包含响应类,反之亦然。(正如提问者所建议的那样)。通常你有一个非常基本的BaseRequest类,由CreateRequest,UpdateRequest等扩展。其中,所有创建请求共有的属性都在 CreateRequest 中,然后由更具体的请求类(如 CreateBookRequest)扩展...
类似地,但与它平行的是响应类层次结构。

提问者还询问CreateBookRequest和CreateBookResponse是否可以包含相同的成员,例如(尽管永远不要使用模型类!BookStuffInRequestAndResponse 哪些属性对请求和响应都是通用的?

这并不像让请求或响应引用模型也引用的类那样严重。这样做的问题是,如果您需要对API请求进行更改并在BookStuffInRequestAndResponse中进行更改,它会立即影响您的响应(反之亦然)。

这并不是那么糟糕,因为1)如果你的客户需要修复他们的客户端代码,因为你更改了请求参数,他们也可以修复句柄/修复更改的响应,2)请求中的更改很可能需要以任何方式更改响应(例如添加新属性),但是,情况可能并不总是如此。


答案 2

我也有类似的困境。我朝着一般的方向走,喜欢结果;从那以后就没有回头看过。

如果我有一个API方法,签名可能看起来像这样。GetAccounts

public final Response<Account[]> getAccounts()

当然,同样的原则可以应用于请求。

public final Response<Account[]> rebalanceAccounts(Request<Account[]>) { ... }

在我看来;将各个实体与请求和响应分离,将生成更整洁的域和对象图。

下面是此类通用响应对象可能的外观示例。在我的情况下;我构建了服务器,以便对所有请求都有一个通用响应,以增强错误处理并降低域对象和响应对象之间的耦合。

public class Response<T> {

  private static final String R_MSG_EMPTY = "";
  private static final String R_CODE_OK = "OK";

  private final String responseCode;
  private final Date execDt;
  private final String message;

  private T response;

  /**
   * A Creates a new instance of Response
   *
   * @param code
   * @param message
   * @param execDt
   */
  public Response(final String code, final String message, final Date execDt) {

    this.execDt = execDt == null ? Calendar.getInstance().getTime() : execDt;
    this.message = message == null ? Response.R_MSG_EMPTY : message;
    this.responseCode = code == null ? Response.R_CODE_OK : code;
    this.response = null;
  }

  /**
   * @return the execDt
   */
  public Date getExecDt() {

    return this.execDt;
  }

  /**
   * @return the message
   */
  public String getMessage() {

    return this.message;
  }

  /**
   * @return the response
   */
  public T getResponse() {

    return this.response;
  }

  /**
   * @return the responseCode
   */
  public String getResponseCode() {

    return this.responseCode;
  }

  /**
   * sets the response object
   *
   * @param obj
   * @return
   */
  public Response<T> setResponse(final T obj) {

    this.response = obj;
    return this;
  }
}

推荐