Menu

REST-решения по find

2018-10-09
2018-10-12
  • Alexander Lapygin

    Коллеги, всем привет!
    По поводу решений по find.
    Больше не хотел по этому поводу ничего говорить, но не получилось).

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

    Напоминаю, что корень проблемы в том, что мы не можем наш find корректно положить на REST, поскольку в общем случае наш find может иметь число параметров, не укладывающееся в ограничение IE по размеру URL (max 2000 символов),
    и поэтому мы не можем реализовать find через GET.
    Можно сказать по-другому:
    Мы можем корректно(через GET) положить наш find на все браузеры, кроме IE (поскольку - см. выше).

    Все любят простые и красивые решения.
    Но в нашем случае идеального решения нет и приходится выбирать из нескольких зол меньшее.

    Насколько я помню, изначально в качестве решения (с разной степенью эмоций и количеством слов) для find рассматривались варианты (в таком хронологическом, как я это вижу, порядке):
    Для всех браузеров использовать POST
    Для всех браузеров использовать POST + GETs (для единобразия толерантно равняемся на самого кривого;))
    * Для нормальных браузеров использовать GET, для "кривого" - всё, что угодно, - не так важно, посколько решение будет временным (для IE пишется отдельный код и на клиентской, и на серверной стороне).

    На мой взгляд, первый вариант имеет минус, поскольку, во-первых, я согласен с тезисом: "One of the core features of the web: you can link to something that can be retrieved using GET" -
    в нашем случае - на всё, что получено посредством find нужно иметь возможность делать ссылки (что невозможно для POST), а во-вторых это не рестОво ;).

    Если же мы принимаем второй вариант, то мы, как минимум, надолго остаёмся с "толерантным" решением.
    Представим, как будет обидно, когда мы примем решение, внедрим его, опубликуем, завяжем на него большое количество наших и не только наших приложений. А потом (я уверен, что это рано или поздно произойдёт) самый кривой браузер естественным образом упразднится.
    Мы останемся с некрасивым решением и тяжёлым наследием зависимых от него приложений.

    Третий вариант на мой взгляд лучший, поскольку самый оптимистичный:). Рано или поздно "кривизна" так или иначе уйдёт, и всё, что нам после этого будет нужно сделать - удалить код, который её поддерживал.

    Что думаете ?

     

    Last edit: Alexander Lapygin 2018-10-10
  • Roman Zakharov

    Roman Zakharov - 2018-10-09

    Мне импонирует ход Сашиных мыслей, обидно делать менее лаконичное решение только из-за IE.

    Тем более на клиенте код для IE будет отличаться только в методе отправки данных на сервер.

    Также есть забытое обсуждение - https://sourceforge.net/p/javaenterpriseplatform/discussion/general/thread/3c88d90d/

     
  • ViTr

    ViTr - 2018-10-10

    Коллеги, приветствую.
    Мне кажется нашлось "идеальное решение" для сложившейся проблемы. Прежде чем предлагать свои варианты и выбирать среди имеющихся, решил обратиться к экспертизе "великих и могучих" умов IT-сообщества. Как выяснилось с этой проблемой сталкиваемся не только мы впервые)) И есть Google-решение в данном подходе: использовние POST-метода в связке с кастомным заголовком запроса X-HTTP-Method-Override: GET. Кому важны подробности - читаем дальше. Итак подробности:
    1. На первый взгляд кажется, что POST для этой задачи не подходит и выглядит не рест-ориентированно, однако (как выяснилось - https://stackoverflow.com/questions/14202257/design-restful-query-api-with-a-long-list-of-query-parameters) в спецификации RFC 2616 об этом нет ни одного противоречия:

    9.5 POST
    The POST method is used to request that the origin server accept the entity enclosed in the request as a new subordinate of the resource identified by the Request-URI in the Request-Line. POST is designed to allow a uniform method to cover the following functions:

    ...
    Providing a block of data, such as the result of submitting a form, to a data-handling process;
    ...
    ...

    The action performed by the POST method might not result in a resource that can be identified by a URI. In this case, either 200 (OK) or 204 (No Content) is the appropriate response status, depending on whether or not the response includes an entity that describes the result.

    If a resource has been created on the origin server, the response SHOULD be 201 (Created)...

    Чтобы улучшить это решение предлагается Google'м (пример https://cloud.google.com/translate/docs/translating-text#Translate) использовать дополнительный заголовок, чтобы однозначно с ним работать в стиле GET-запроса:

    For inspiration, I just looked at Google Translate's API v2, which is "using the RESTful calling style."

    Naturally, texts to be translated can be quite long. And so Google optionally allows sending a request with POST, but with a twist:

    To use POST, you must use the X-HTTP-Method-Override header to tell the Translate API to treat the request as a GET (use X-HTTP-Method-Override:
    GET).

    So it is possible to semantically transform a POST request into a GET request.

    Более того, спускаясь до реализации Jersey это будет выглядеть так в коде:

    @Provider
    @PreMatching
    public class OverrideHttpMethodFilter implements ContainerRequestFilter {
    @Override
    public void filter(ContainerRequestContext requestContext)
            throws IOException {        
        String receivedMethod = requestContext.getMethod();
        String methodFromHeader = requestContext.getHeaderString("X-HTTP-Method-Override");
        if (receivedMethod != null && !receivedMethod.equals(methodFromHeader)) {
            requestContext.setMethod(methodFromHeader);
        }
      }
    }
    

    IMHO - ideal solution, nothing to say more..just use this approach!))

     

    Last edit: ViTr 2018-10-10
  • Max touch

    Max touch - 2018-10-10

    Мое мнение должен быть только POST - POST это создание запроса на поиск это "единичные"\уникальные запросы, обычно с уникальным набором параметров. Их не надо, например, кешировать, они не должны попадать в лог веб сервера.

    При этом не стоить путать с "поиском" по GET, с использованием ID ресурса (возможно нескольких ID), SHORT_NAME и тд

     
  • Romanov

    Romanov - 2018-10-10

    Саша, спасибо за труд по созданию треда и затравки.

    Позволь подискутировать на тему корня проблемы. Решение POST+GETs было предложено (по крайней мере, тогда, летом -- с моей стороны) не потому, что существует ограничение по длине урла, а потому что это решение лучше подходит для постраничного листания. (На момент предложения, да и сейчас, целесообразность постраничного серверного листания принимается как должное).

    Раз уж пошёл разговор об этой архитектуре, выскажусь материалом моей "презентации", запланированной на 16 октября. (Заодно, быть может, частично обсуждение темы здесь позволит сэкономить время там)

    Согласен с Альмиром в одном: первичный POST с параметрами создаёт вполне конкретный объект -- запрос на поиск (если угодно -- создаёт короткий псевдоним -- ID -- для набора поисковых параметров).
    POST: /api/feature/search; возвращает searchId=8a069f6d31
    На сервере это может быть реализовано как угодно -- хоть stateful с помощью сессий, хоть stateless с помощью стороннего глобального кеша либо базы данных.
    Ставя в соответствие набору поисковых параметров searchId мы во-первых получаем возможность не передавать весь набор параметров в каждом последующем запросе с аналогичными параметрами, а воспользоваться этим самым алиасом.

    Во-вторых, после создания запроса на поиск, мы можем постранично обозревать результаты поиска GET-запросами, в лучших традициях реста и навигации по URL:

    GET: /api/feature/search/8a069f6d31/results/paged/1
    GET: /api/feature/search/8a069f6d31/results/paged/2
    GET: /api/feature/search/8a069f6d31/results/paged/3
    

    В-третьих, передавая параметры в теле запроса структурой (а не линейно, как в параметрах), получаем возможность более гибкого поиска (то есть поисковые параметры не ограничиваются пересечением пар "ключ-значение", а могут представлять собой хоть синтаксис graphQL, хоть логические выражения со связками И, ИЛИ. К примеру:

    POST {
        name: "John",
        age: 20,
        city: "New York"
    }
    -- эквивалентно name=John&age=20&city=NewYork ,
    
    POST {
        _and {
            name: "John",
            _or: {
                age: 20,
                city: "New York"
            }
        }
    }
    -- эквивалентно name=John&(age=20|city=NewYork)
    

    Не знаю, насколько последний упомянутый функционал нам нужен, но в защиту концепции того, что POST+GETs это полноценное и продуманное, а не обходное решение -- подойдёт.

     

    Last edit: Romanov 2018-10-10
    • Alexander Lapygin

      "Решение POST+GETs было предложено (по крайней мере, тогда, летом -- с моей стороны) не потому, что существует ограничение по длине урла, а потому что это решение лучше подходит для постраничного листания."
      Лёш, спасибо, этот мотив предложения прошёл как-то мимо меня. Я слышал много разговоров именно об ограничении по длине URL.

      "целесообразность постраничного серверного листания принимается как должное"
      Я не вижу безусловного единогласия по этому вопросу.
      Например, лично я выступаю за "клиентское листание" с предварительным получением небольшого, но полного набора данных. Если нужны подробности, как этого добиться, обращайтесь (хотя многие и так уже в курсе).
      Плюсов очень много:
      Во-первых, таким образом зримо увеличивается скорость и, соответственно, удобство работы пользователя (напоминаю, набор данных небольшой)
      Во-вторых, существенно снижается трафик (не все работают с хорошей московской сетью)
      В-третьих, существенно повышается качество работы в offline
      В-четвёртых, снижается нагрузка на сервер
      В-пятых, существенно упрощается серверное решение

       

      Last edit: Alexander Lapygin 2018-10-10
  • Roman Zakharov

    Roman Zakharov - 2018-10-10

    Леш, а если пользователь сделает два одинаковых поиска, сколько будет алиасов? два или один?

    Параметр page можно добавить и как обычный GET параметр.

     
  • Romanov

    Romanov - 2018-10-10

    если пользователь сделает два одинаковых поиска, то в случае со stateful сессионным механизмом будут созданы разные объекты, и соотвтетственно два ID, а в случае со stateless кешем/DB это будет один ID.

    можно ли добавить Параметр page как обычный GET параметр?
    Как раз для навигации (в том числе по страницам) и создан url, а параметры созданы для уточнения запроса. Листание страниц -- это навигация в чистом виде (а сортировка -- это уточнение).

    Более того, линейность get-параметров как раз и не позволяет отделить разнородные вещи друг от друга.
    Например, разнородными являются:
    1) поисковые параметры name=John&age=20 (пользовательские данные поиска),
    2) параметры сорировки sortField=name&sortOrder=asc (пользовательские данные сортировки),
    3) параметр operatorId=132846 (авторизационный токен)
    4) параметр maxRowCount=100 (характеристика клиента)
    5) параметры page, pageSize (параметры навигации)
    Лепить всё это в одну строку я считаю очень пложим решением.
    Организация структур в теле запроса как раз позволяет разделить эти вещи

     

    Last edit: Romanov 2018-10-12
  • ViTr

    ViTr - 2018-10-10

    POST + GET избыточен для работы с нашим REST API. Вся информация, которая отработает в первом пост-запросе уже знает о всей необходимой информации. Фиктивно вызывать информацию, которая уже есть GET-методом похоже на лишнее телодвижение и однозначно направляет нас на путь Statefull-ности, которая не есть хорошо (отказавшись в перспективе от ее поддержки мы будем вынуждены менять логику вызова на клиенте). Используя один пост (крайне настаиваю с кастомным заголовком, чтобы отделять от других поступающих POST-запросов на создание сущностей), мы контракт взаимодействия не поменяем, а лишь изменится внутренний механизм нашей логики.

     
    • Alexander Lapygin

      однозначно направляет нас на путь Statefull-ности
      Виталь, "не всё так однозначно" ;). Рома Т. вчера и Лёша Р. уже здесь упоминали о глобальном кэше, в котором можно размещать результаты запросов. Это не statefull-ность и такую кэш не нужно реплицировать по кластеру (в отличие от сессионного хранилища), то есть масштабируемость не теряется.

       

      Last edit: Alexander Lapygin 2018-10-10
  • ViTr

    ViTr - 2018-10-10

    Более того, подготовительный POST может вернуть нам не одну запись, как подсказывает Леша, а сразу 100500. В дальйшем просить клиент сделать еще 100500 GET-запросов точно вызывает сомнения.

     
    • Alexander Lapygin

      Виталь, мне кажется тоже интересное решение. Точно стоит присмотреться.

       
  • Roman Zakharov

    Roman Zakharov - 2018-10-11

    У поиска по GET есть киллер фича - возможность поделиться ссылкой или добавить ссылку на поиск в закладки.

     
    • Max touch

      Max touch - 2018-10-12

      Оооочень редко используемый кейс, кроме как в поисковиках сложно придумать применение

       
    • Romanov

      Romanov - 2018-10-12

      Так вот же ссылка на поиск из моего примера:
      /api/feature/search/8a069f6d31
      Правда, она имеет место только в случае с глобальным кешем

       

      Last edit: Romanov 2018-10-12
  • ViTr

    ViTr - 2018-10-12

    Исходя из совместных обсуждений, я так понимаю мы должны отталкиваться от выстроенной на текущий момент инфраструктуры серверов, которые функционируют в продакшне (Httpd + Tomcat). Добавление дополнительных звеньев в виде кэш-серверов, MOM-серверов и т.д. на текущем этапе не берется в рассмотрение. Поэтому полагаться на такие решения при закладывании обсуждаемой REST-архитектуры точно не стоит (хотя лично мне идея глобального кэша нравится, но реализовать ее действительно дело скрупулезное и нелегкое). Что касается удобства использования GET-запросов передачей ссылки и добавления в избранное, точно не вопрос работы с API. Закреплять ссылку нужно не до целевого сервиса, а до клиента, использующего внутри себя этот сервис. Что я имею ввиду, представим есть замечательный сайт https://www.rusfinancebank.ru, на одной из страниц которого идет обращение к API https://online-auto.rusfinance.ru/OnlineApproval/api/address/region, выдающего json-объект в виде массива других json-объектов. Все фильтры, которые могут быть натравлены на этот сервис идут со страницы сайта по логике POST-запроса. Сами фильтрующие параметры могут в данном случае быть параметрами GET-запроса к той самой странице сайта, например так,
    https://www.rusfinancebank.ru/ru/network.html?regionName=Самара, а последующее обращение к сервису region будет уже осуществляться с нужными параметрами. Так, что проблема с GET-ом также снимается (клиент не должен работать напрямую с сервисом, а если в качестве клиента выступает другой сервис или разработчик, то для этих сторон - обращение по такому формату явно не должно быть проблемой).

     

Log in to post a comment.

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.