简体   繁体   中英

Design Pattern: Domain class and Http Response Class

I have a request that creates a payment. A payment has a form of payment that can be credit card or ticket.

When I return the HTTP response I would like to not have to verify what form of payment was used. The way I did this was by having my model class know how to create the form of payment response.

My question is: Is code smells considered the domain class know how to create the response class of an HTTP request? If yes, what's the best way to handle with this situation?

Example for a better understanding of the question:

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

Is code smells considered the domain class know how to create the response class of an HTTP request?

Probably yes. It will be harder to test as your unit tests will now need to be aware of HTTP. It also limits the usability of your domain class. If you end up needing to use another endpoint technology other than HTTP (SOAP, web sockets, etc.) you will have a problem. If you end up returning a redirect response then it spreads your URL mapping throughout your application making it harder to keep a handle on that.

Now, if I were to inherit your code base I wouldn't necessarily jump in and change it right away. I wouldn't say it is a fatal flaw but it isn't something I would do in a new project.

If yes, what's the best way to handle with this situation?

This question is probably overly broad and you haven't given a lot of details so here is a generic answer.

The simplest solution I've seen is to have a layer on the outermost of your application whose job is to convert from HTTP to domain logic and back. These types are often called controllers but that word is used so much I prefer the word endpoints . Forgive my pseudocode here, it's been a while since I've used the Java HTTP types.

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

}

However, that can lead to a lot of boilerplate code. Most HTTP libraries now have methods for doing the domain->HTTP conversion in filters. For example, JAX-RS has annotations you can use to specify which method to use if the content type is X and which to use if the content type is Y and annotations to populate method parameters from form fields or JSON content.

JAX-RS also allows you to return POJOs from your endpoints and it will handle the conversion to a response in its own logic. For example, if you return an object, it will use Jackson to serialize the object to JSON and set the response status to 200. If you return void then it will set the response status to 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);
  }

}

Now, I'm not entirely sure what your asking, but it almost seems like you might need to serialize a ticket payment to HTTP very differently than the way you would serialize a credit card payment to HTTP. If they are simply different JSON objects you can use the example above. However, if you need to set certain HTTP headers in one response and not the other then you might have to do something more fancy.

You could just shove the logic in the endpoint:

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

However, if you have several endpoints that have to repeat this same logic then it can quickly become boilerplate code. Then you usually extend your filter layer to control the serialization. For example, in JAX-RS there is the concept of a MessageBodyWriter . You can give the JAX-RS framework a custom message body writer which tells it how to convert a card payment into an HTTP response. Although if you were just setting HTTP headers you could probably use a ContainerResponseFilter instead.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM