I have about 7 REST web services to implement. Some of these web services have a standard (identical) response, while some have different responses.
The requests for these web services are different but some requests and some of the responses have the same underlying data objects.
I am not sure if I have to build separate request/response classes for each web service or reuse a standard one. I would like to know if there is a design pattern to model the request objects and response objects for these web services.
Ok, say Account and Book are two rest resources my web services will be working on.
class Account {
String username;
String id;
}
class Book {
String title;
String isbn;
}
So my web services look like this:
MYAPI/CreateAccountandBook
MYAPI/Account/Create
MYAPI/Book/Create
MYAPI/Book/Update/{isbn}
MYAPI/Account/Update/{id}
MYAPI/Account/getInfo/{id}
and so on.
Now CreateAccountandBook
request will take an account object and a list of books in the payload.
Also the response object for MYAPI/Account/getInfo/{id}
has an account object and a list of books associated with that account. But the response object also includes a statusCode
and Description
.
Now I would like to create classes for these request and response objects in the best possible way.
Okay for a start.
I have two abstract classes StandardRequest
and StandardResponse
.
All requests classes will extend the Standard Request class and customize accordingly. All response classes will extend the Standard response class and customize accordingly.
But these requests and response can be way different from each other but still re-use the same entity objects.
For instance:
createAccountandBook
request object looks like this:
class CreateAccountAndBookRequest {
Account account;
List<Book> books;
}
while the response for the getInfo
web service is:
class GetInfoResponse {
Account account;
List<Book> books;
String statusCode;
String description;
}
so there is overlap across request and response classes. I can create two (req/res) classes for each web service. But would like to know if there is a better way to model these classes.
The big problem I see in all the answers so far including the question is that they all violate the principal of separation of concerns, information hiding and encapsulation. In all answers request (and response) classes are tightly coupled to model classes. That is a more serious issue and raises a question more important than the relationship between the requests and responses...
What should be the relationship between the request/response classes and the model classes?
Since the request class (e.g. CreateBookRequest) and the model class Book have mostly the same data properties, you could do any of the following:
A. Put all your data/getters/setters into the Book class and have the CreateBookRequest extend from the class
B. Have your CreateBookRequest contain a Book as a member (as in the question and answers given by ekostadinov, Juan Henao, . The generic usage given by dasm80x86 is also a special case of this)
C. Put data/getters/setters in BookBase and have both Book and CreateBookRequest extend from BookBase
D. Put all/some data/getters/setters in BookStuff and have both Book and CreateBookRequest contain a BookStuff
E. Put all the data/getters/setters in both Book and CreateBookRequest. (you can copy-paste).
The correct answer is E. We are all so trained and eager to "re-use" that this is the least intuitive answer.
The request class CreateBookRequest (as well as the response class CreateBookResponse) and the model class Book, should NOT be be in same class hierarchy (other than both having Object as a top most parent) (A,C). Also the CreateBookRequest should not refer/contain to the model Book or to any of the composite classes that are members in the Book class (B,D)
The reasons for this are as follows:
You want to modify the model object or the request object independent of each other. If your request refers to your model (as in A-D) any change in the model will be reflected in the interface, and therefore break your API. Your customers are going to write clients according to the API dictated by your request/response classes and they don't want to change those clients whenever you make a change to your model classes. you want the request/response and the model to vary independently.
Separation of concerns. Your request class CreateBookRequest may contain all kinds of interface/protocol related annotations and members (e.g. validation annotations that the JAX-RS implementation knows how to enforce). These interface-related annotations should not be in the model object. (as in A)
from an OO perspective CreateBookRequest is not a Book (not IS_A) nor does it contain a book.
The flow of control should be as follows:
The interface/control layer (the one that receives the Rest-API calls) should use as its methods parameters Request/Response classes defined specifically for that layer (e.g. CreateBookRequest). Let the container/infrastructure create those from the REST/HTTP/whatever request.
The methods in the interface/control layer should create in some way an instance of a model class object, and copy values from the request classes into the model class object,
The methods in the interface/control layer should call a BO/Manager/Whatever (in the model layer... which is responsible for business logic) passing to it the model class object and not the interface class/method parameter class object (in other words, NOT as Luiggi Mendoza has shown in his answer)
The model/BO method would return some model class object or some "primitive".
Now the interface method (the caller) should create an interface class response object, and copy values into it from the model class object returned by the model/BO. (Just like Luiggi Mendoza as shown in his answer)
The container/infrastructure would then create the JSON/XML/whatever response from the response class object.
Now to the question asked... What should be the relationship between the requests and response classes?
Request classes should extend from request classes and not extend nor contain response classes, and vice versa. (as was also suggested by question asker).
Usually you have a very basic BaseRequest class, extended by something like CreateRequest, UpdateRequest, etc... where properties common to all create requests are in CreateRequest which is then extended by more specific request classes such as CreateBookRequest...
Similarly, but parallel to it, the is the Response class hierarchy.
The question asker also asked whether it's ok for both CreateBookRequest and CreateBookResponse to contain the same member such as (Never a model class though!) BookStuffInRequestAndResponse which properties common to both the request and response?
This is not as severe an issue as having the request or response refer to a class that is also referred to by the model. The problem with this is that if you need to make a change to your API request and make it in BookStuffInRequestAndResponse, it immediately affects your response (and vice versa).
It's not so bad because 1) if your customer needs to fix their client code because you change the request parameters, they may as well fix handle/fix the changed response and 2) most likely changes in the request would require change to the response any way (for example adding a new attribute), however, that may not always be the case.
I had a similar dilemma; I went in the generic direction and am liking the results; haven't looked back since.
If I had a GetAccounts
API method the signature might look like.
public final Response<Account[]> getAccounts()
Naturally the same principle may be applied to requests.
public final Response<Account[]> rebalanceAccounts(Request<Account[]>) { ... }
In my opinion; decoupling the individual entities from requests and responses yields a neater domain and object graph.
Below is an example of what such a generic response object might look like. In my case; I'd built the server to have a generic response for all requests to enhance error handling and lower coupling between domain objects and response objects.
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;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With