Уточнить, как работают GWT RequestFactory и RequestContext.

Я пытаюсь внедрить RequestFactory и фреймворк Editor в свое приложение. Даже изучив форум, форум разработчиков Google и другие, я обнаружил, что есть что-то фундаментальное, чего я не понимаю в использовании RequestContext с RequestFactory. Вот мой сценарий:
У меня есть простой объект с тремя полями: идентификатор, версия и описание, который называется CmsObjectType. У меня есть соответствующий EntityProxy и CmsObjectTypeServiceDAO с моими операциями CRUD. Я также реализовал классы ServiceLocator и ObjectLocator. Весь этот код компилируется и запускается.

Я также создал простой тестовый пример для проверки операций CRUD, используя следующее:

public class RequestFactoryProvider {

public static CmsRequestFactory get() {
    SimpleEventBus eventBus = new SimpleEventBus();
    CmsRequestFactory requestFactory = RequestFactoryMagic.create(CmsRequestFactory.class);
    ServiceLayer serviceLayer = ServiceLayer.create();

    SimpleRequestProcessor processor = new SimpleRequestProcessor(
            serviceLayer);
    requestFactory.initialize(eventBus, new InProcessRequestTransport(
            processor));
    return requestFactory;
}

}

Тест:

public class TestCmsObjectTypeRequest extends Assert {

private static CmsRequestFactory requestFactory;
private static CmsObjectTypeRequestContext objectTypeRequest;
private Long newId;

@Before
public void setUp() {
    requestFactory = RequestFactoryProvider.get();
    objectTypeRequest = requestFactory.objectTypeRequest();
}

    @Test
public void testEdit() {
    final CmsObjectTypeProxy newType = objectTypeRequest
            .create(CmsObjectTypeProxy.class);
    newType.setDescription("NEW TYPE");
    objectTypeRequest.persist(newType).to(new Receiver<Long>() {

        @Override
        public void onSuccess(Long response) {
            if (response != null) {
                newId = response;
                assertTrue(true);
            } else {
                fail();
            }
        }

        @Override
        public void onFailure(ServerFailure error) {
            fail();
        }
    });

    // Edit the newly created object
    newType.setDescription("EDITED NEW TYPE");

        objectTypeRequest.update(newType).to(new Receiver<Boolean>() {

            @Override
            public void onSuccess(Boolean response) {
                assertTrue(response);
            }

            @Override
            public void onFailure(ServerFailure error) {
                fail();
            }
        });

        //Remove it when we're done..
        objectTypeRequest.delete(newType).to(new Receiver<Boolean>() {

        @Override
        public void onSuccess(Boolean response) {
            System.out.println("onSuccess from delete.");
            assertTrue(response);
        }

        @Override
        public void onFailure(ServerFailure error) {
            fail();
        }
    });
    objectTypeRequest.fire();
}
}

Когда я создаю новый контекст запроса и связываю вызовы методов для создания, обновления и удаления, а затем вызываю fire(), он работает без проблем в приведенном выше тесте. Однако, если я попытаюсь выполнить эти вызовы по отдельности, вызвав метод, а затем fire(), у меня возникнут проблемы. Я могу вызвать функцию create(), когда Receiver возвращает идентификатор вновь созданной сущности, а затем я использую этот идентификатор для вызова find(id) и возвращаю вновь созданную сущность. До этого момента все работает нормально. Однако именно здесь я запутался. Если я попытаюсь вызвать редактирование с текущим RequestContext в методе onSuccess() приемника из find (id), я получаю сообщение об ошибке, говорящее, что контекст уже выполняется. Если я создам локальную переменную для foundProxy, а затем попытаюсь использовать новый экземпляр RequestContext для вызова requestContext.edit(foundProxy) для вновь найденного объекта, а затем вызову update(), я чаще всего получаю ошибку сервера: Ошибка сервера: Запрошенный объект недоступен на сервере. Если я не создам новый экземпляр контекста запроса, я получу исключение IllegalStateException, говорящее, что запрос уже выполняется. Вот пример теста, который, надеюсь, прояснит ситуацию:

@Test
public void testEditWOChaining() {
    final CmsObjectTypeProxy newType = objectTypeRequest
            .create(CmsObjectTypeProxy.class);
    newType.setDescription("NEW TYPE");
    objectTypeRequest.persist(newType).to(new Receiver<Long>() {

        @Override
        public void onSuccess(Long response) {
            if (response != null) {
                setNewId(response);
                assertTrue(true);
            } else {
                fail();
            }
        }

        @Override
        public void onFailure(ServerFailure error) {
            fail();
        }
    }).fire();

    if (newId != null) {
        objectTypeRequest = requestFactory.objectTypeRequest();
        objectTypeRequest.find(newId)
                .to(new Receiver<CmsObjectTypeProxy>() {

                    @Override
                    public void onSuccess(CmsObjectTypeProxy response) {
                        if (response != null) {
                            foundProxy = response;
                        }
                    }

                    @Override
                    public void onFailure(ServerFailure error) {
                        fail();
                    }
                }).fire();
    }

    if (foundProxy != null) {
        // Edit the newly created object
        objectTypeRequest = requestFactory.objectTypeRequest();
        CmsObjectTypeProxy editableProxy = objectTypeRequest
                .edit(foundProxy);
        editableProxy.setDescription("EDITED NEW TYPE");

        objectTypeRequest.update(editableProxy).to(new Receiver<Boolean>() {

            @Override
            public void onSuccess(Boolean response) {
                assertTrue(response);
            }

            @Override
            public void onFailure(ServerFailure error) {
                fail();
            }
        }).fire();
    }

    // Remove it when we're done..
    objectTypeRequest.delete(foundProxy).to(new Receiver<Boolean>() {

        @Override
        public void onSuccess(Boolean response) {
            System.out.println("onSuccess from delete.");
            assertTrue(response);
        }

        @Override
        public void onFailure(ServerFailure error) {
            fail();
        }
    });
    objectTypeRequest.fire();
}

Вот мои вопросы. Как лучше всего обрабатывать редактирование, если оно связано не с create(), а с find()? Если я попытаюсь связать поиск с обновлением, мой foundProxy будет нулевым, и ничего не обновится. Должны ли прокси оставаться привязанными к контексту, в котором они созданы, чтобы иметь возможность выполнять на них обновления? Если бы кто-нибудь мог объяснить, как это работает, или указать мне на какую-то документацию, в которой указано, что мне не хватает, я был бы признателен. Возможно ли, что это как-то связано с тем, как среда тестирования обрабатывает запросы? Я прочитал следующее, поэтому, если я что-то пропустил в них, сообщите мне: Отличное описание от tbroyer

Документы Google Будем очень признательны за любую помощь. Спасибо!


person mcfar    schedule 12.04.2011    source источник


Ответы (1)


Взгляните на RequestFactoryTest в исходном коде GWT для примеров. testChangedEdit() аналогичен тому, что вы пытаетесь написать. Он вызывает метод find(), а затем работает с возвращенным прокси в методе onSuccess().

RequestContext не является долгоживущим объектом. Он действителен только с момента его вызова, когда вы вызываете для него fire(). Его можно использовать повторно, только если метод onFailure() или onViolation() вызывается в вашем файле Receiver.

EntityProxy или ValueProxy, возвращаемые через Receiver.onSuccess(), представляют собой моментальный снимок данных сервера. Таким образом, прокси неизменяем, если только он не связан с RequestContext вызовом edit(). Прокси, возвращаемые RequestContext.create(), изменяемы. Изменяемый прокси-сервер всегда связан ровно с одним RequestContext, и "пересечение потоков." Использование re-edit() изменяемого прокси-сервера не является ошибкой.

Причина, по которой это работает, заключается в том, что клиент RequestFactory может отправлять на сервер только дельты. Дельты применяются к долгоживущим сущностям на сервере путем вызова метода find() объекта домена (или использования Locator). RequestContext, по сути, является аккумулятором для proxy.setFoo() вызовов и одного или нескольких вызовов Request/InstanceRequest.

Общие рекомендации:

  • Не храните экземпляры RequestContext в полях объектов, время жизни которых превышает время жизни вызова метода fire().
  • Точно так же редактируемые экземпляры EntityProxy или ValueProxy не должны сохраняться после вызова fire().
  • EntityProxyId, возвращенный из EntityProxy.stableId(), может храниться неограниченное время, даже из вновь созданного прокси. Объект stableId подходит для использования в качестве ключа в объектах Map и имеет стабильную семантику идентификатора объекта (т. е. два снимка одного и того же объекта домена сервера с разными версиями будут возвращать один и тот же `EntityProxyId').
  • Экземпляры RequestFactory должны создаваться один раз и сохраняться на протяжении всего срока службы модуля, поскольку стоимость их создания нетривиальна.
person BobV    schedule 12.04.2011
comment
Спасибо за ваш четкий и краткий ответ, однако он приводит к другому вопросу, который может быть частью моей проблемы. У меня есть класс ObjectLocator, который расширяет Locator. Мой вопрос касается метода find(clazz, id), который я переопределяю. Я видел пример использования Objectify, который использует API-интерфейс хранилища данных Google App Engine, похоже, это механизм кэширования для объектов на стороне сервера. Я видел еще один пример, который делает вызов базы данных для получения объекта. Что я должен вернуть здесь, мне нужно сделать вызов базы данных или реализовать механизм кэширования, чтобы получить объект сервера? - person mcfar; 13.04.2011
comment
Кэширование объекта или вызов новых объектов зависит от уровня параллелизма, ожидаемого в вашем приложении. Если объект неизменяем, то кэширование экземпляров имеет смысл. Что нужно помнить об AppEngine, так это то, что запросы могут перебрасываться между экземплярами вашего приложения. Мое предложение состоит в том, чтобы написать самую простую вещь, которая работает, а затем повторять, как только вы определили, что что-то является или не является проблемой производительности. - person BobV; 13.04.2011
comment
Привет боб. Мне кажется, что образец DynaTableRf не применяет некоторые из ваших рекомендаций, особенно в отношении сохранения RequestContext и прокси-объектов; или я неправильно понял, как работает DynaTableRf? Считаете ли вы DynaTableRf примером хорошей практики, или мне стоит поискать другой? - person David; 10.06.2011
comment
Большое тебе спасибо. Наконец-то кто-то прояснил эти проблемы для меня. Очень хорошая работа, Боб! - person Piotr Sobczyk; 26.04.2012