В API Java REST, используя PATCH vs PUT для обновления сущности

Я собираюсь начать разработку нового REST api на Java. Мой вопрос об использовании патча-почему?

допустим, у нас есть объект с именем 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)
}

теперь, если вы запросите таблицу, не будет ли мой адрес ?

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

как видно, вышеуказанные обновления приводит к неправильному значению для line2. Это связано с тем, что в java все переменные экземпляра в классе Address инициализируются в null (или начальные значения по умолчанию, если они являются примитивами) при создании экземпляра класса. Таким образом, нет способа отличить line2 от значения по умолчанию, изменяемого на null.

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


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

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

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

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


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


если исходить из того, что GET всегда должен быть сделан перед выполнением каких-либо обновлений, почему бы не использовать put over PATCH? Я что-то упускаю?

в сторону

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

1 ответов


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

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

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

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

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