OData REST API в JAVA с проекцией

1

В JAVA можно сделать что-то подобное? Если да, то как? (С образцом)

Рассмотрите возможность использования http://olingo.apache.org/ или odata4j

Я делаю образец в.NET, который предоставляет REST API в формате OData (чтобы дать моим потребителям возможность фильтровать, выбирать, сортировать и т.д. С использованием стандартизованного протокола).

Рассмотрение: Я выставляю Модели, отличные от моделей из ORM (избегая разоблачения БД и предоставляя возможность контролировать/ограничивать потребительский запрос). Два класса пользователя: Models.User и DAL.User

Образец:

public IHttpActionResult GetUsers(ODataQueryOptions<Models.User> queryOptions)
{
    //Get the IQueryable from DB/Repository
    DAL.UsersDAO usersDAO = new DAL.UsersDAO();
    IQueryable<DAL.User> usersQueryable = usersDAO.GetUsers();

    //Make the projection from the 'User of Project DAL' to the 'User of ODataProject'
    IQueryable<Models.User> usersProjected = usersQueryable.Select(user => new Models.User()
    {
        Id = user.Id,
        Name = user.Name,
        Gender = user.Gender
    });

    //At this point, the query was not executed yet. And with that, it is possible to add new Filters
    //like the ones send by the client or even some rules from Business Logic
    //(ex: user only see other users from his country, etc)

    //Appling the queryOptions requested by the consumer
    IQueryable usersWithOptionsApplied = queryOptions.ApplyTo(usersProjected);
    IQueryable<Models.User> usersToExecute = usersWithOptionsApplied as IQueryable<Models.User>;

    //Execute the Query against the Database/Repository/Whatever with all the filters
    IEnumerable<Models.User> usersExecuted = usersToExecute.ToList();

    return Ok(usersExecuted);
}

Ключевым моментом является:

1 - Возможность построения запроса (получить построитель из базы данных/репозитория/независимо от того,

2 - Запросите запрос к выставленной модели (а не к ORM)

3 - Применить отправку фильтров от пользователя к API OData REST (queryOptions)

Образец (в.NET), который я загрузил здесь: http://multiupload.biz/2meagoxw2boa

Я действительно приветствую любое желание сделать это. Доказательство концепции при попытке использовать OData в качестве стандартного способа межплатформенных технологий.

Теги:
rest
odata
projection

1 ответ

4
Лучший ответ

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

Я использую Olingo для моего доказательства концепции и ElasticSearch для моего back-end, но имею в виду открытое решение для любых бэкэнд (как SQL, так и noSQL).

Есть две основные части:

  • Конфигурация метаданных. Olingo предоставляет объект EdmProvider который отвечает за предоставление метаданных управляемых объектов библиотеке. Этот вызов вызывается во время обработки запроса, чтобы перенаправить запрос на правильную обработку элемента.

    На этом уровне есть два случая. Либо вы вручную настроите этот элемент, либо попробуете автоконфигурировать путем автоматического определения базовых структур. Для первого нам нужно расширить класс EdmProvider который является абстрактным. Я представляю промежуточные метаданные, на которых пользовательский EdmProvider будет основываться, поскольку для определения правильного пути реализации запроса (например, с помощью ElasticSearch, отношений родителя/ребенка,...) требуются некоторые подсказки. Ниже приведен пример примера настройки промежуточных метаданных вручную:

    MetadataBuilder builder = new MetadataBuilder();
    builder.setNamespaces(Arrays.asList(new String[] { "odata" }));
    builder.setValidator(validator);
    
    TargetEntityType personDetailsAddressType
            = builder.addTargetComplexType("odata", 
                                 "personDetailsAddress");
    personDetailsAddressType.addField("street", "Edm.String");
    personDetailsAddressType.addField("city", "Edm.String");
    personDetailsAddressType.addField("state", "Edm.String");
    personDetailsAddressType.addField("zipCode", "Edm.String");
    personDetailsAddressType.addField("country", "Edm.String");
    
    TargetEntityType personDetailsType
            = builder.addTargetEntityType(
                               "odata", "personDetails");
    personDetailsType.addPkField("personId", "Edm.Int32");
    personDetailsType.addField("age", "Edm.Int32");
    personDetailsType.addField("gender", "Edm.Boolean");
    personDetailsType.addField("phone", "Edm.String");
    personDetailsType.addField(
                 "address", "odata.personDetailsAddress");
    

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

    Теперь у нас есть это, мы можем сосредоточиться на обработке запросов.

  • Обработка запросов Olingo позволяет обрабатывать запросы на основе процессоров. Фактически, библиотека будет направлять запросы процессору типа, который может обрабатывать запрос. Например, если вы хотите сделать что - то на объект, процессор, который реализует EntityCollectionProcessor, CountEntityCollectionProcessor и/или EntityProcessor будут выбраны и использованы. Тогда будет вызываться правильный метод интерфейсов. Это то же самое для свойств,...

    Поэтому нам нужно реализовать процессоры, которые будут адаптировать запросы и взаимодействовать с целевым бэкэнд. На этом уровне существует много сантехники (с использованием сериализатора/десериализатора Olingo, компоновки контекста контекста, в конечном счете извлечения параметров...), и хороший подход, похоже, реализует общий уровень для базы. Последний отвечает за выполнение операций на бэкэнд (чтение, запись, запрос и т.д.), А также обработку преобразования между типами Olingo (Entity, Property) и элементами, используемыми бэкэнд-драйвером (в случае ElasticSearch, необработанные объекты, хиты - см. Http://www.elastic.co/guide/en/elasticsearch/client/java-api/current/client.html).

Поэтому, если нам нужен уровень косвенности между открытой моделью OData и базовой схемой, нам необходимо реализовать сопоставление между ними как по метаданным, так и по уровням запросов, описанным выше. Это позволяет не иметь одинаковых имен для объектов и их свойств.

Что касается фильтров, мы можем легко получить к ним доступ в Olingo, используя класс UriInfo (см. Методы getFilterOption, getSelectOption, getExpandOption, getOrderByOption, getSkipOption и getTopOption), как описано ниже в процессоре:

@Override
public void readEntityCollection(final ODataRequest request,
              ODataResponse response, final UriInfo uriInfo,
              final ContentType requestedContentType)
              throws ODataApplicationException, SerializerException {
    (...)
    EntitySet entitySet = dataProvider.readEntitySet(edmEntitySet,
                uriInfo.getFilterOption(), uriInfo.getSelectOption(),
                uriInfo.getExpandOption(), uriInfo.getOrderByOption(),
                uriInfo.getSkipOption(), uriInfo.getTopOption());

    (...)
}

Все подсказки могут быть переданы элементу, ответственному за создание запросов на бэкэнд. Вот пример в случае ElasticSearch (обратите внимание, что класс QueryBuilder является фабрикой ElasticSearch Java для создания запросов):

QueryBuilder queryBuilder = QueryBuilders.matchAllQuery();
if (topOption!=null && skipOption!=null) {
    requestBuilder.setFrom(skipOption.getValue())
                  .setSize(topOption.getValue());
}

Запросы с параметром запроса $filter немного утомительны, так как нам нужно перевести исходный запрос в один из целевого бэкэнда. В классе FilterOption мы имеем доступ к экземпляру Expression который позволяет посещать выражение. Следующий код описывает упрощенный способ построения запроса ElasticSearch на основе этого класса:

Expression expression = filterOption.getExpression();
QueryBuilder queryBuilder
           = expression.accept(new ExpressionVisitor() {
    (...)

    @Override
    public Object visitBinaryOperator(
              BinaryOperatorKind operator, Object left,
              Object right) throws ExpressionVisitException,
                                  ODataApplicationException {
        String fieldName = (String)left;
        // Simplification but not really clean code ;-)
        String value = ((String)right).substring(
                          1, right.length() - 1);
        return QueryBuilders.termQuery((String) left, right);
    }

    @Override
    public Object visitLiteral(String literal)
                throws ExpressionVisitException,
                ODataApplicationException {
        return literal;
    }

    @Override
    public Object visitMember(UriInfoResource member)
                throws ExpressionVisitException,
                ODataApplicationException {
        UriResourcePrimitiveProperty property
                   = (UriResourcePrimitiveProperty)
                          member.getUriResourceParts().get(0);
        return property.getProperty().getName();
    }
}

Если мы используем description eq 'test' значения description eq 'test' в параметре $filter запроса, у нас будет что-то вроде этого:

>> visitMember - member = [description]
>> visitLiteral - literal = 'test'
>> visitBinaryOperator - operator = eq, left = description, right = 'test'

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

Не стесняйтесь спрашивать меня, если что-то неясно или/или если вы хотите получить более подробную информацию.

Надеюсь, это поможет вам, Тьерри

Ещё вопросы

Сообщество Overcoder
Наверх
Меню