I have a very simple question.
In Java code, I used to use Data Transfer Objects for Requests / Responses.
For example, in my Spring webapp I was creating some request dto, like
public class SaveOfficeRequest {
private String officeName;
private String officePhone;
private String officeAddress;
/* getters / setters */
}
After that i had controller with "mapped" method like
#ResponseBody
public SaveOfficeResponse saveOffice(#RequestBody SaveOfficeRequest) { ... }
.
Every request is json request. When some controller method was called i converted request dto to domain dto entities and do some business logic.
So!
Should I save the practice in my new scala project based on Play Framework?
Case classes can be used to represent the request and response objects. This helps make the API explicit, documented and type-safe, and isolate concerns, by avoiding to use domain objects directly in external interface.
For example, for a JSON endpoint, the controller action could use a pattern like this:
request.body.asJson.map { body =>
body.asOpt[CustomerInsertRequest] match {
case Some(req) => {
try {
val toInsert = req.toCustomer() // Convert request DTO to domain object
val inserted = CustomersService.insert(toInsert)
val dto = CustomerDTO.fromCustomer(inserted)) // Convert domain object to response DTO
val response = ... // Convert DTO to a JSON response
Ok(response)
} catch {
// Handle exception and return failure response
}
}
case None => BadRequest("A CustomerInsertRequest entity was expected in the body.")
}
}.getOrElse {
UnsupportedMediaType("Expecting application/json request body.")
}
Related
Say we have this:
Router router = Router.router(vertx);
router.put("/products/:productID").handler(this::handleAddProduct);
and this:
private void handleAddProduct(RoutingContext ctx) {
String productID = ctx.request().getParam("productID");
HttpServerResponse response = ctx.response();
JsonObject product = ctx.getBodyAsJson();
products.put(productID, product);
response.end();
}
my question is - how can we deserialize ctx.getBodyAsJson() to a specific Java class instead of the generic JsonObject class?
you can use JsonObject.mapTo(Class), e.g.:
JsonObject product = ctx.getBodyAsJson();
Product instance = product.mapTo(Product.class);
UPDATE
you can customize the (de)serialization behavior by manipulating the ObjectMapper instance(s) associated with the Json class. here are some examples:
// only serialize non-null values
Json.mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
// ignore values that don't map to a known field on the target type
Json.mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
keep in mind Json holds a reference to two different ObjectMappers:
mapper, and
prettyMapper
In my application, I have a Web API controller which should be adding an entity of type "Source" to my DB, then returning the created Source. Source contains a list/collection of "SourceFields", which are themselves made up of a number of fields. Source also inherits from a base entity which I use for change tracking, and which contains fields like date created, date modified, etc.
Using Postman, I've tested my API and verified that it properly returns when I post a Source, and it includes all of objects that are created. However, when I use Angular 2 HTTP with RxJS Observables to post, the value which is returned has dropped all of the base entity fields, as well as the sourcefields. I use a Source class in Angular 2 to do operations, but I don't use it anywhere in the calls, so I don't think it's being typecasted or coerced? The only way I've gotten the source response to return properly (i.e. including the base entity properties) is if I set sourcefields to null in the API POST method.
This dataservice does work perfectly with simple entities. For example, I have a client entity that doesn't contain a collection like sourcefields, and it returns the base entity fields just fine.
Any ideas?
My API POST method:
// POST api/Sources
[HttpPost]
public IActionResult Post([FromBody]Source source)
{
if (source == null)
{
return BadRequest();
}
dbContext.Sources.Add(source);
dbContext.SaveChanges();
//Hack to get source to return properly
//source.SourceFields = null;
return CreatedAtRoute("GetSource", new { id = source.SourceId }, source);
}
Angular 2 Dataservice method:
public Add (action: string, itemToAdd: any): Observable<any> {
var toAdd = JSON.stringify(itemToAdd);
return this._http.post(this.actionUrl + action, toAdd, { headers: this.headers })
.map(res => res.json())
.catch((error: any) => Observable.throw(error.json().error || 'Server Error'));
}
The place I'm subscribing to the dataservice:
this.addEditSource.sourceFields = this.sourceFields;
this._dataService.Add('Sources', this.addEditSource).subscribe(source => {
this.sources.push(source)
},
error => console.log(error));
If anyone wants to see my entity models or anything I can add them.
I am wondering whether Play framework controller can do automatically convert between JSON and Object (case class) without some boilerplate.
As Spring MVC and Twitter's Finatra can do that. Following is the code for Finatra framework.
#Singleton
class TweetsController #Inject()(
tweetsService: TweetsService)
extends Controller {
post("/tweet") { requestTweet: TweetPostRequest =>
// requestTweet is a case class mapping json request
tweetsService.save(requestTweet)
...
}
get("/tweet/:id") { request: TweetGetRequest =>
// case class mapping json response
tweetsService.getResponseTweet(request.id)
...
}
}
However, for Play framework, we need do JSON conversation manually. Can Play support a way without using implicit to do that?
Any reasons why Play can't support that or will it support in the future release?
We use the following utility class for this purpose
/**
* Framework method for handling a request that takes a Json body as a parameter. If the JSON body can be
* parsed as a valid instance of `A` , the resulting object is passed into the body which is expected
* to produce a Result.
*
*
* Note that it is not necessary to create the Action object in the body of the supplied handler; this is
* done for you.
*
* #tparam A A case class that the input JSON should be parsed into.
* #param body The body of the handler for this request. This must be a function that will take an instance of `A`
* and use it generate a `Result`.
*
*/
def handleJsonRequest[A : Reads](body: A => Result) = Action(parse.json) { request =>
request.body.validate[A].map {body}.recoverTotal {
errors: JsError =>
throw new ...(errors)
}
}
You can use this in your handler as
def handleGet() = handleJsonRequest[Body] {body =>
...
}
I am using Spring 4 + Jackson 2 and have written a fully functional POST method using #RequestBody on a custom class. This method has no trouble unmarshalling the object.
#ResponseBody
#RequestMapping(value="store", method = RequestMethod.POST)
public ServiceResponse store(#RequestBody CustomClass list) {
...
}
// Request: { code: "A", amount: 200 }
When I attempted to add another method to handle a collection of the same class instead, my POST requests were returning with the following error.
HTTP Status 400: The request sent by the client was syntactically incorrect.
I note that this error typically occurs when the JSON submitted does not match the entity class. However, all I am doing is submitting an array of the same object instead of the object itself, which has already proven to work.
#ResponseBody
#RequestMapping(value="store-bulk", method = RequestMethod.POST)
public ServiceResponse storeBulk(#RequestBody List<CustomClass> list) {
...
}
// Request: [{ code: "A", amount: 200 }, { code: "B", amount: 400 }]
Am I missing something here?
In Java, type information for generics is erased at runtime, so Spring sees your List<CustomClass> object as List<Object> object, thus it cannot understand how to parse it.
One of ways to solve it, you could capture the type information by creating a wrapper class for your list, like this:
public class CustomClassList extends ArrayList<CustomClass> {
}
Sergey is right that the issue is due to type erasure. Your easiest way out is to bind to an array, so
#ResponseBody
#RequestMapping(value="store-bulk", method = RequestMethod.POST)
public ServiceResponse storeBulk(#RequestBody CustomClass[] object) {
...
}
The answer is that Spring 4 doesn't actually get rid of type erasure, contrary to what some other solutions suggest. While experimenting on debugging via manual unmarshalling, I decided to just handle that step myself instead of an implicit cast that I have no control over. I do hope someone comes along and proves me wrong, demonstrating a more intuitive solution though.
#ResponseBody
#RequestMapping(value="store-bulk", method = RequestMethod.POST)
public ServiceResponse storeBulk(#RequestBody String json) {
try {
List<CustomClass> list = new ObjectMapper().readValue(json, new TypeReference<List<CustomClass>>() { });
...
} catch (Exception e) {
...
}
}
Bonus: Right after I got this working, I bumped into this exception:
IllegalStateException: Already had POJO for id
If anyone gets this, it's because the objects in the list happen to reference some object that another item in the list already references. I could work around this since that object was identical for my entire collection, so I just removed the reference from the JSON side from all but the first object. I then added the missing references back after the JSON was unmarshalled into the List object.
Two-liner for the Java 8 users (the User object reference was the issue in my case):
User user = list.get(0).getUser();
list.stream().filter(c -> c.getUser() == null).forEach(t -> t.setUser(user));
I would like to design a client that would talk to a REST API. I have implemented the bit that actually does call the HTTP methods on the server. I call this Layer, the API layer. Each operation the server exposes is encapsulated as one method in this layer. This method takes as input a ClientContext which contains all the needed information to make the HTTP method call on the server.
I'm now trying to set up the interface to this layer, let's call it ClientLayer. This interface will be the one any users of my client library should use to consume the services. When calling the interface, the user should create the ClientContext, set up the request parameters depending on the operation that he is willing to invoke. With the traditional Java approach, I would have a state on my ClientLayer object which represents the ClientContext:
For example:
public class ClientLayer {
private static final ClientContext;
...
}
I would then have some constructors that would set up my ClientContext. A sample call would look like below:
ClientLayer client = ClientLayer.getDefaultClient();
client.executeMyMethod(client.getClientContext, new MyMethodParameters(...))
Coming to Scala, any suggestions on how to have the same level of simplicity with respect to the ClientContext instantiation while avoiding having it as a state on the ClientLayer?
I would use factory pattern here:
object RestClient {
class ClientContext
class MyMethodParameters
trait Client {
def operation1(params: MyMethodParameters)
}
class MyClient(val context: ClientContext) extends Client {
def operation1(params: MyMethodParameters) = {
// do something here based on the context
}
}
object ClientFactory {
val defaultContext: ClientContext = // set it up here;
def build(context: ClientContext): Client = {
// builder logic here
// object caching can be used to avoid instantiation of duplicate objects
context match {
case _ => new MyClient(context)
}
}
def getDefaultClient = build(defaultContext)
}
def main(args: Array[String]) {
val client = ClientFactory.getDefaultClient
client.operation1(new MyMethodParameters())
}
}