или POST /users/42 {"name": 'Pupkin'}
OK, а теперь выяснится, что юзер не может быть переименован, если он заблокирован:
PHP:
final class User
{
public function rename(Name $newName) {
Assertion::false($this->banned, 'Banned user cannot be renamed');
$this->name = $newName;
}
}
Что твой метод должен вернуть при запросе POST /users/42 {"name": "Blah", "banned": false}? С точки зрения клиента может показаться, что ошибка о блокировке неуместна, т.к. одновременно с переименованием я посылаю флаг разблокировки, но каким образом внутри мы можем понять в каком порядке нужно выполнять соответствующие бизнес-действия, т.е. что сначала нужно выполнить UnbanUser, а лишь затем — RenameUser?
Далее, теперь представь, что мы отправляем юзера на перемодерацию, если он сменил имя:
PHP:
final class User
{
public function rename(Name $newName) {
Assertion::false($this->banned, 'Banned user cannot be renamed');
$this->name = $newName;
$this->status = Status::moderation();
}
}
Сам по себе метод /users/42 будет возвращать скорее всего и name, и status. При этом при переименовании ты меняешь одно свойство, но в результате меняются 2. Более того, нужно каким-то образом дать понять клиенту, что поля неравнозначные и он не может напрямую менять status, т.е. это read-only поле.
Т.е. возникает leaky abstraction: якобы ты можешь работать с ресурсом, как с пачкой свойств, но в реальности всё сложнее.
Далее возникнут проблемы документирования: если для одних действий могут быть одни ошибки, для других — другие, то метод POST /users/42 будет содержать кучу возможных errorCode'ов, в то время как на, допустим, то же переименование может быть только 2 errorCode'а, но клиенту придётся поддерживать все 10, т.к. неочевидно какие из них когда могут «выстрелить». Один большой жирный анемичный метод, который делает хрен сколько всего. Не круто.
но и параллельно POST /users/42/ban ничего не мешает сделать
Это неконсистентность. Такой API труднее понимать. То так, то эдак.