2016-10-01 2 views
6

Я собираюсь приступить к разработке нового api для отдыха в Java. Мой вопрос об использовании PATCH - Почему?В java REST API, используя PATCH vs PUT для обновления объекта

Допустим, у нас есть объект с именем Address.java

public class Address { 

    @Id 
    private Long id 

    @NotNull 
    private String line1; 

    private String line2;  //optional 

    @NotNull 
    private String city; 

    @NotNull 
    private String state; 
} 

Чтобы создать новый адрес, я хотел бы сделать этот запрос HTTP:

POST http://localhost:8080/addresses 

со следующей просьбой:

{ 
    "line1" : "mandatory Address line 1", 
    "line2" : "optional Address line 2", 
    "city" : "mandatory City", 
    "state" : "cd" 
} 

Предположим, что запись создана с идентификатором 1

Соответствующий @RestController AddressResource.java будет иметь этот метод:

@PostMapping(value = "/addresses") 
public ResponseEntity<Address> create(@valid Address newAddress) { 
    addressRepo.save(newAddress); 
} 

@valid обеспечит предприятие является действительным до сохранения данных в таблицу.

Теперь предположим, что я перехожу из своей квартиры наверх в дом по улице. Если я использую заплату, он становится

PATCH http://localhost:8080/addresses/1 

с запросом полезной нагрузки:

{ 
    "line1" : "1234 NewAddressDownTheStreet ST", 
    "line2" : null 
} 

Соответствующий метод @RestController будет:

@PatchMapping(value = "/addresses/{id}") 
public ResponseEntity<Address> patchAddress(@PathVariable Long id, Address partialAddress) 
{ 
    Address dbAddress = addressRepo.findOne(id); 
    if (partialAddress.getLine1() != null) { 
     dbAddress.setLine1(partialAddress.getLine1()); 
    } 
    if (partialAddress.getLine2() != null) { 
     dbAddress.setLine2(partialAddress.getLine2()); 
    } 
    if (partialAddress.getCity() != null) { 
     dbAddress.setCity(partialAddress.getCity()); 
    } 
    if (partialAddress.getState() != null) { 
     dbAddress.setState(partialAddress.getState()); 
    } 

    addressRepo.save(dbAddress) 
} 

Теперь, если вы запрашиваете таблицу, выиграл» t мой адрес будет?

"line1" : "1234 NewAddressDownTheStreet ST", 
"line2" : "optional Address line 2",  <-- INCORRECT. Should be null. 
"city" : "mandatory City", 
"state" : "cd" 

Как видно, приведенное выше обновление приводит к неправильному значению для строки2. Это потому, что в Java все переменные экземпляра в классе Address обнуляются (или по умолчанию начальные значения, если они примитивы) при создании экземпляра класса. Таким образом, нет возможности различать между изменением строки 2 на нуль от значения по умолчанию.

Вопрос 1) Есть ли стандартный способ обойти это?


Другой недостаток заключается в том, что я не могу использовать @Valid аннотации для подтверждения запроса на точке входа - сог это лишь частично. Таким образом, недопустимые данные могут попасть в систему.

Например, представьте, там было дополнительное поле со следующим определением:

@Min(0) 
@Max(100) 
private Integer lengthOfResidencyInYears, 

И пользователь случайно набрал 190 (если они на самом деле имел в виду 19 лет), она не подведет.

Вместо PATCH, если бы я использовал PUT, клиенту необходимо было бы отправить полный адресный объект. Это имеет то преимущество, что я могу использовать @Valid, чтобы убедиться, что адрес действительно действует


Если один делает предпосылку, что GET должен всегда быть сделано, прежде чем делать какие-либо обновления, почему бы один использовать PUT над PATCH? Я что-то упустил?

Помимо

Мой вывод состоит в том, что разработчики, использующие динамически типизированных языков являются сторонниками использования PATCH, как я не вижу никакой пользы для его использования из статически типизированный язык строки Java или C#. Похоже, что это еще больше усложняет.

+2

похоже, что статус действителен, после запроса PATCH ваш запрос db должен показывать строку: «дополнительная строка адреса 2», так как вы проверяете, partialAddress.getLine2()! = Null. – kuhajeyan

+0

Патч-запрос должен содержать инструкции, рассчитанные клиентом, который сервер может использовать для преобразования состояния A некоторого ресурса в состояние B и является не только упрощенным частичным обновлением. Дальнейшее чтение: [SO documentation] (http://stackoverflow.com/documentation/http/3423/http-for-apis/11812/edit-a-resource#t=201610031245323472567) и [хорошее сообщение в блоге] (http://williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot/) –

+0

Это хороший вопрос. Что касается нулевой строки в строке2, то именно потому, что вы вручную сопоставляете свои поля и проверяете ненулевые значения, чтобы перезаписать. В настоящее время я работаю над проектом, в котором мы используем [Dozer] (http://dozer.sourceforge.net/documentation/usage.html), и вы можете выбрать отображение нулей. В любом случае мы используем POST для добавления/создания ресурсов и PUT для их изменения, однако с помощью этого mapper он может выполнять частичные сопоставления. Поэтому я обсуждаю, является ли это предпочтительным способом, или я должен использовать POST (создание), PUT (полное обновление), PATCH (частичное обновление). –

ответ

7

Использование PATCH для загрузки измененной версии существующего объекта почти всегда проблематично именно по той причине, что вы наметили. Если вы хотите использовать PATCH с JSON, я сильно предлагаю вам следовать либо , либо RFC 7396. Я не буду говорить с 7396, потому что я не знаком с ним, но чтобы следовать за 6902, вы бы определили отдельный ресурс для операций PATCH. В этом примере вы дали, это будет выглядеть так:

PATCH http://localhost:8080/addresses/1 
[ 
    { "op": "replace", "path": "/line1", "value": "1234 NewAddressDownTheStreet ST" }, 
    { "op": "remove", "path": "/line2" } 
] 

Вы бы затем обработать это, делая новый объект сущности, которая началась в текущем состоянии сервера и прикладные изменения в PATCH. Запустить проверку на новый объект объекта. Если он пройдет, нажмите на слой данных. Если это не удается, верните код ошибки.

Если PUT не добавляет слишком много накладных расходов, это хорошая идея. Идемпотентность - хорошая вещь. Компромисс в том, что вы нажимаете больше данных по проводу. Если ваш ресурс невелик и часто не доступен, возможно, это не такая уж большая проблема. Если ваш ресурс большой и к нему часто обращаются, это может привести к значительным накладным расходам. Конечно, мы не можем сказать вам о переломном моменте.

Вы также, кажется, полностью привязали свою модель ресурсов к своей модели базы данных. Хороший дизайн таблицы базы данных и хороший дизайн ресурсов часто выглядят совсем по-другому для нетривиальных проектов. Я понимаю, что многие фреймворки приводят вас в этом направлении, но вы, если серьезно не считаете их развязыванием, можете захотеть.

+0

Пример, который я видел с SpringDataRest, заставил меня подумать, что это простая полезная информация JSON, но формат запроса, который вы указали, имеет больше смысла для меня. Если я использую свой @service в качестве границы транзакции, то этот адрес dbData = getId() и внесение изменений сверху, вероятно, должен произойти в ServiceImpl (а не в контроллере). Интересный ответ. Но сейчас этот вопрос останется открытым, чтобы увидеть дополнительные предложения. – SGB

 Смежные вопросы

  • Нет связанных вопросов^_^