设计模式:域类和Http响应类

时间:2018-03-20 01:29:03

标签: java spring rest web-services design-patterns

我有一个创建付款的请求。付款的付款方式可以是信用卡或机票。

当我返回HTTP响应时,我不想验证使用何种付款方式。我这样做的方法是让我的模型类知道如何创建付款响应形式。

我的问题是:代码味道是否认为域类知道如何创建HTTP请求的响应类?如果是,那么处理这种情况的最佳方法是什么?

更好地理解问题的示例:

class Payment {

    private BigDecimal value;
    private PaymentMethod method;
}

abstract class PaymentMethod {

    public abstract String getResponse();
}

class Card extends PaymentMethod {

    //attributes
    public Response getResponse() {
        return new CardResponse();
    }
}

class Ticket extends PaymentMethod {

    //attributes
    public Response getResponse() {
        return new TicketResponse();
    }
}

1 个答案:

答案 0 :(得分:0)

代码味道是否认为域类知道如何创建HTTP请求的响应类?

可能是的。测试将更加困难,因为您的单元测试现在需要了解HTTP。它还限制了您的域类的可用性。如果您最终需要使用HTTP以外的其他端点技术(SOAP,Web套接字等),则会出现问题。如果您最终返回重定向响应,那么它会在您的应用程序中传播您的URL映射,这使得更难以对此进行处理。

现在,如果我要继承您的代码库,我不一定会跳进去并立即更改它。我不会说这是一个致命的缺陷,但这不是我在新项目中会做的事情。

如果是,那么处理这种情况的最佳方法是什么?

这个问题可能过于广泛,你没有提供很多细节,所以这里是一个通用的答案。

我见过的最简单的解决方案是在应用程序的最外层设置一个层,其作用是从HTTP转换为域逻辑并返回。这些类型通常被称为控制器,但是这个词被使用了很多,我更喜欢单词 endpoints 。请原谅我的伪代码,因为我使用了Java HTTP类型已经有一段时间了。

// Crude example
public class PaymentEndpoint {

  public Response handlePayment(HttpRequest request) {
    if (request.getContentType() == "application/json") {
      Ticket ticket = deserializeTicket(request);
      Payment payment = this.paymentService.processTicket(ticket);
      return serializeTicketPayment(Payment);
    } else if (request.getContentType() == "application/x-www-form-urlencoded") {
      CCInfo ccInfo = getCcInfoFromForm(request);
      Payment payment = this.paymentService.processPayment(ccInfo);
      return serializeCcPayment(payment);
    }
  }

}

然而,这可能导致许多样板代码。现在,大多数HTTP库都有在过滤器中进行domain->HTTP转换的方法。例如,JAX-RS具有注释,可用于指定在内容类型为X时使用哪种方法,以及在内容类型为Y时使用哪种方法以及从表单字段或JSON内容填充方法参数的注释。

JAX-RS还允许您从端点返回POJO,它将以自己的逻辑处理转换为响应。例如,如果返回一个对象,它将使用Jackson将对象序列化为JSON并将响应状态设置为200.如果返回void,则将响应状态设置为204。

// Crude example
public class PaymentEndpoint {

  @Consumes("application/x-www-form-urlencoded")
  public Payment handleCcPayment(@FormParam("name") String name) {
    CCInfo ccInfo = new CCInfo(name);
    return this.paymentService.processPayment(ccInfo);
  }

  @Consumes("application/json")
  public Payment handleTicketPayment(Ticket ticket) {
    return this.paymentService.processTicket(ticket);
  }

}

现在,我并不完全确定你的要求,但似乎你可能需要将票证付款序列化为非常不同于将信用卡付款序列化为HTTP的方式。如果它们只是不同的JSON对象,您可以使用上面的示例。但是,如果您需要在一个响应中设置某些HTTP标头而不在另一个响应中,那么您可能需要做一些更加花哨的事情。

你可以推断端点中的逻辑:

if (payment.getPaymentMethod() instanceof Card) {
  //Set special HTTP headers
}

但是,如果您有多个端点必须重复相同的逻辑,那么它很快就会成为样板代码。然后,您通常会扩展过滤器层以控制序列化。例如,在JAX-RS中有MessageBodyWriter的概念。您可以为JAX-RS框架提供一个自定义消息体编写器,告诉它如何将卡支付转换为HTTP响应。虽然如果您只是设置HTTP标头,则可以改为使用ContainerResponseFilter