DDD (domain driven design) is a design technique that helps to manage complexity by using object oriented design and language and principals of the domain(what is being implemented). I highly recommend the book on the topic: Implementing Domain-Driven Design by Vaughn Vernon, even though book is offering a lot of good advice, one should be careful of which ones to pick.
I stumbled upon a problem in a low level design trying to use some of the principles in an application that is exposed via REST interface. Using Spring and Hibernate (and JPA) it is possible to deserialize a json document directly in JPA Entity and store it. Note: I always suggest pragmatic approach, if entity(or surrounding logic) is simple I use entity directly, if it gets more complex I refactor the code to use some sort of DTO.
So for example a simple entity:
I stumbled upon a problem in a low level design trying to use some of the principles in an application that is exposed via REST interface. Using Spring and Hibernate (and JPA) it is possible to deserialize a json document directly in JPA Entity and store it. Note: I always suggest pragmatic approach, if entity(or surrounding logic) is simple I use entity directly, if it gets more complex I refactor the code to use some sort of DTO.
So for example a simple entity:
package com.magdicgoran.molecule;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class Molecule {
@Id
@GeneratedValue
private Long id;
private String name;
private String formula;
public Long getId() {
return id;
}
public String getName() {
return name;
}
public String getFormula() {
return formula;
}
}
And rest API:
GET molecules - returns all molecules
GET molecules/{id} - returns one molecule by id
POST molecules - adds a new one
PUT molecules/{id} - updates molecule with id
DELETE molecules/{id} - deletes molecule with id
Implementation for POST method:
@RequestMapping(path = "molecules", method = POST)
@ResponseStatus(HttpStatus.CREATE)
public Molecule add(@RequestBody Molecule molecule) {
return moleculeRepository.save(molecule);
}
where moleculeRepository is just:
public interface MoleculeRepository extends CrudRepository {
}
It looks nice and simple, but has a bug. Since it is a rest interface, user can send any json:
{
"id": 1,
"name": "water"
}
And instead of adding a new entry, code above will update the entry with id 1. Why? Jackson, the library used for json serialization is setup by spring boot to discover fields in classes using getters and setters (id has a getter), and once it discovers them it will be able to set them (even though it discovered just getter) unless you tell it otherwise using annotations. Spring data repository save method will add new if there is no id, and update if there is an id.
We can add @JsonIgnore, but that will prevent serialization as well. So to prevent deserialization only, we can use something like this:
@Id
@GeneratedValue
@JsonIgnore
private Long id;
@JsonProperty
public Long getId() {
return id;
}
That looks good, in all the interfaces above, id does not need to be set in entity, POST method has it in the URL. It seems to be a common mistake in different tutorials, POST methods with ids in both URL-s and deserialized entities. Authors use the one from the entity, so you can write whatever you want in the URL of the request and still update the entity identified by the id in the body of the request.The code above allows only ids in the URLs to be set.After that, I stumbled upon a bit harder problem. What if this entity is just a part of the aggregate (another entity), and you need to reference it? Whatever JSON you post, this entity will end up without id and new one will be added if you just save aggregate. I will address that in the next post
No comments:
Post a Comment