Как стать автором
Обновить

Комментарии 23

НЛО прилетело и опубликовало эту надпись здесь

Ну это как раньше некоторые вместо "логика" всегда говорили "бизнес-логика", теперь вместо "сервис" говорят "микросервис".

НЛО прилетело и опубликовало эту надпись здесь
Если говорить, что мэппинг вызывается в контроллере, но сам код или в сервисах-конверторах или в библиотеках типа MapStruct, то холивара не получится

Полнстью согласен. когда РЕСТ ендпоинт вырождается в


@GetMapping
public OperationDto getOperationById(@PathVariable("id") Long id) {
    return operationService.getById(id);
}

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

REST сервис вообще должен выставлять данные как данные, а не что там дизайнеры понапридумывали в очередной версии аппликейшна. Если важно дать потребителям возможность уточнять "вот это мне надо, а вот это — нет", API должен позволять указывать всякие fields=id,name&filter=age>20.


А наделять API знанием о том, что бывают веб клиенты, а бывают — мобильные, это по-моему кривизна дизайна. Ну или можно не называть это словом REST просто.

> Как начать писать микросервис на Spring Boot

Лучше вообще не начинать
  1. Оставляем контроллеры тонкими


@GetMapping
public OperationDto getOperationById(@PathVariable("id") Long id) {
  return operationService.getById(id);
}
...
public OperationDto getById(Long id) {
    Optional<Operation> operationOptional = ... //логика получения operation
    return operationOptional
        .map(operation -> mapperFacade.map(operation, OperationDto.class))
        .orElse(EMPTY_OPERATION_DTO);
}

  1. Обычно контроллеру нужно больше контекста, чтобы дать хороший ответ. Нашлось там что запрашивали и есть доступ — 200, нашлось, но нет доступа — 403, не нашлось — 404.
  2. Многие REST API позволяют потребителю указать набор полей, которые он хочет получить: ?fields=id,name. Это знание можно использовать чтобы построить более оптимальный запрос для вытягивания данных из БД (не делать лишний join например).
  3. В случае ресурсов-коллекций конечно же потребитель хочет фильтровать: GET /orders?filter=date<2019-01-01, сортировать: GET /orders?sort=date, и паджинацию: GET /orders?skip=10&take=100. И конечно это всё вместе тоже GET /orders?filter=date<2019-01-01&sort=date&skip=10&take=100&fields=id,name.
  4. PUT (который "создать или обновить") тоже свои нюансы добавляет — там и тривиальная валидация ("name" не может быть пустым) — 400, и "глобальная" ("name" обязано быть уникальным) — 409, и разница между 201 created и 204 no content.
  5. Аж уж PATCH так вообще.

Моё мнение такое, что если делаешь "REST API фасад к сервису", совершенно всё равно как это делать — получится нечто жёсткое и нерасширяемое (но этого вполне может хватать). Если делаешь "REST API", то лучше с самого начала код писать в терминах моделей HTTP-ресурсов, маппингов между полями данных в API и полями/таблицами в БД.

Поддерживаю. REST это об общей архитектуре, а не об API. Если начинать делать HTTP API фасад к сервису, который архитектурно основан не на передаче представлений ресурсов, то лучше подумать почему этот HTTP API должен быть непременно REST и не лучше ли использовать JSON RPC или просто разумный набор HTTP ендпоинтов и принимаемых/отдаваемых данных, соответствующий юзкейсам, типа POST /contract/1/approve без тела, а не делать попыток представлять всё как ресурсы и передавать PATCH /contract/1 {status:"approved" } а потом парсить тело на конкретные значения чтобы определить approve метод сервиса вызывать или cancel, если сервис оперирует понятиями бизнес-логики, а не REST ресурсов.

Кконтроллер не должен выбрасывать 403, так как не авторизованный запрос должен быть остановлен фильтрами аутентификации в Spring Security.

В общем случае — нет. Spring Security — это скорее про аутентификацию и роли — когда хочется сказать "если не админ, то точно нет". Логика типа "если вы не друзья с Бобби и у вас нет общих друзей, то фотку Бобби мы вам 403" намного органичнее пишется "прямо по месту".

Не совсем понятно что вы подразумеваете под общим случаем. Есть фрэимворк и он отвечает за аутентификацию на уровне фильтров которые будут делегировать ее authenticationManager, а он в свою очеред выберет нужного authentificationProvider. Далее этот фрэимворк представляет нам возможности авторизации при входе в метод, с обширными возможностями SpEL и иерархией ролей. И мне не совсем понятно в каком случае, кроме "Hello World" мы не должны им пользоваться и делегировать это все нашему контроллеру.

Общий случай — это когда логика принятия решения "можно ли" требует знания тех же деталей, которые нужны для совершения самой операции.

А если там права на уровне записи надо проверить, типа что текущий пользователь является автором комментарий, который он пытается отредактировать?

НЛО прилетело и опубликовало эту надпись здесь

Можно-то можно, но не понятно зачем.


@PutMapping("/comments/{id}")
@PreAuthorize("canPutComment(commentId, userId)")
public ResponseEntity<?> putComment(@PathVariable("id") commentId) {
  ...
}

Т.е. canPutComment() должен будет уметь загружать коммент, т.е. там будет вся эта логика — если коммент есть, то сравниваю автора с текущим пользователем, если коммента нет, то разрешаю, потому что создаётся новый. Аналоничная логика также будет и в putComment() — попытаться найти коммент, если он есть то редактируем, если нет, то создаём. А добились-то чего этим разделением?

Можно просто не использовать Spring, и жить спокойно контролируя свой код самому.

Автор предлагает выключить open-in-view, но маппить в контроллере?

А в чем проблема? Сделайте ваш метод в сервисе транзакционным и сущность выйдет из него уже detached.

проблемы нет. просто за пределами транзакции придется писать дополнительно код, который тащит все lazyload связные сущности до закрытия транзакции(ну или после — с открытием новой).

Изначально не надо тащить такие связи, если вы знаете что они вам понадобятся, вы делаете заррос с Entity Graph и получаете их в один запрос, а не несколько. Если же они вам не нужны вы мапите сущность в дто где их уже не будет.

не, так не работает, мы ж передаем набор «полей», которые хотим получить.
Да и что делать с нескольими сущностями (one2many) и lazyload в List — придется все равно что-то свое писать и делать несколько запросов, иначе MultipleBagFetchException.
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.