Pull to refresh

Comments 29

Способ интресен.

Но я не очень люблю reflection из-за некоторых просадок по перфомансу (http://stackoverflow.com/questions/435553/java-reflection-performance). Так что лучше использовать исходя из потребностей.
В данном конкретном случае была использована парадигма «Преждевременная оптимизация — корень всех зол» :)

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

Если уж у нас все равно имеет место быть такой момент, когда данные нужно «заполнить и отправить», то почему (нет, правда, почему?) этот момент нельзя контролировать? Ну то есть, вовремя заполнить и отправить на клиент DTO/ViewModel. При этом на сервере использовать в зависимости от задачи либо Dynamic Proxy для Hibernate (для Hibernate, кстати, ведь есть Proxy, как и для NHibernate, верно?), либо вообще просто проекции.
Для использования это совершенно не громоздко. Просто нужно написать интерфейс и все. Буквально.

Имплементация тоже достаточно простая. Насчет средств Hibernate — не скажу, что считаю себя большим специалистом в этой технологии. В основном я работал на уровне JPA. Но по-моему там нет ничего, что позволяло бы делать прокси методом «написал интерфейс и готово».

А что за проекции?
Ну, в Hibernate я не знаток деталей, поскольку работаю с .NET и NHibernate, и вот в последнем есть как раз что-то типа плагина Dynamic Proxy (на базе Castle и LinFu), так что про прокси даже думать не приходится, можно просто использовать и все.

Проекция — это выборка сразу определенного набора данных, полей (но обычно не всех) под конкретную ситуацию, скажем так, «в обход сущности». Т.е., например, объект Person нигде не материализуется, а выбирается напрямую PersonViewModel, который и является проекцией. Это особенно удобно в .NET, поскольку и LINQ позволяет их очень легко создавать, и NHibernate их понимает. Но суть не поменяется, если использовать «старый добрый» NHibernate Criteria API и SetProjection().
Смущает, что при таком подходе кроме основных сущностей (у меня их 20 + контроллеры отдельно), мне еще придется иметь туеву хучу прокси-объектов.
Согласен, есть такой момент. Приходится прокси интерфейсами отражать сущности. Немножко запаривает. Но запаривает недостаточно, чтобы взяться и переписать всё это используя CGLIB и, соответственно «гибридизировать» прокси систему так, чтобы простые сущности она отдавала «как есть».
а чем плох способ догружать данные только по мере необходимости их на клиенте? ведь уровни вложенности могут быть глубокими, и заранее все подгружать не всегда эффективно.
Почему плох? Все зависит от контекста использования. В определенных ситуациях постепенная подгрузка — это единственный вариант vs выгружать пол базы.

Насчет глубоких уровней вложенности — так данный подход как раз позволяет регулировать с точностью до поля уровень вложенности:

Допустим, есть интерфейсы IItem и IStrippedItem:

public interface IItem {
    int getId();
    void setId(int);

    List<IStrippedItem> getChildren();
    void setChildren(List<IStrippedItem> items);
}

public interface IStrippedItem {
    int getId();
    void setId(int);
}


Если запроксировать сущность к IItem, то дети детей проксироваться не будут.
Хм… по тексту у меня сложилось ощущение что классы сущностей отображаются на интерфейсы и обратно без изменений? (Person IPerson)
Если это так — то возможнбудет удобней Person Implements IPerson? тогда можно использовать extract interface/superclass + pull up methods. Пустячек а удобно. + шажок к ортодоксальному ООП)

И ещё — почему вы всёже не остановились на DTO(как упоминалось выше подмножество полей сущности/сущностей в зависимости от задачи)? Ведь если в Person есть связь многие- ко-многим — то прямая проекция создаст дополнительную (возможно значительную) нагрузках даже на простых операциях. Хотя с другой стороны прямая проекция + прокси может быть просто «частным случаем» традиционной кухни с DTO.
Person не имплементирует IPerson потому, что тогда нельзя будет делать множество различных интерфейсов.

«И ещё — почему вы всёже не остановились на DTO?» — А это и есть DTO :)
На первый взгляд красиво (интерфейс, магический класс), но писать POJO с нужными полями и конструктором от Person ничем не хуже, а даже немного лучше за счет производительности (без рефлексии), простоты кода (особенно, если геттеры и сеттеры генерируются Roo) и прямолинейности использования (передается параметр в конструктор, а не неочевидный класс для получения IPerson).

Что-то вроде:
@RooJavaBean
public class PersonView {
  private long id;
  private String firstName;
  private String lastName;
  private List<PersonView> children;

  public PersonView(Person p){
    id = p.getId();
    firstName = p.getFirstName();
    lastName = p.getLastName();
    //считаем, что children != null или добавляем проверки
    children = new ArrayList<PesonView>(p.getChildren().size());
    //for
  }
}


Т.е. относительно интерфейса добавляется только конструктор, зато не нужно использовать сторонние классы. В IDE такой конструктор так же полуавтоматически создается.
Проще (вместо ProxyFactory) было бы написать аннотацию для Roo, которая бы создавала подобный конструктор (если руками по какой-то причине не хочется писать). Что-то вроде @RooProxyConstructor(Person.class). Не очень понятно какие проверки в ант-билдере, возможно он бы и не понадобился (или его достаточно просто адаптировать). Плюс Roo в том, что он срабатывает в момент компиляции, а не выполнения (как рефлексии).
Важное преимущество данного способа — эстетичность. Он не нарушает процесса понимания системы. Не портит и не усложняет восприятие. Реализовывал такое в одном из проектов: начитавшись, выбрал именно этот способ… «За красивые глаза», как говорится.
Вот вариант с использованием кастомного генератора RooProxyConstructor для Roo (вместо написания ProxyFactory и проверки для анта):
@RooJavaBean
@RooProxyConstructor(Person.class)
public class PersonView {
  private long id;
  private String firstName;
  private String lastName;
  private List<PersonView> children;
}


Мне кажется, что это учитывает все ваши плюсы + генерация кода во время компиляции, а не во время выполнения (строго там не генерация кода, но нечто подобное).
Не знал про Spring Roo. Похоже, очень полезный интсрумент. Почитаю про него и напишу.
UFO just landed and posted this here
Не считаю, что какой-либо язык нужно любить и лелеять. Языком нужно пользоваться. Пользоваться так, чтобы тем, к кому обращаешься было понятно. Давайте не будем оффтопить (т.е. я хотел сказать будем избегать комментарии, не относящиеся к обсуждаемой теме) в тематическом блоге.
Секундочку, а что, OpenSessionInView уже отменили?
В распределенной архитектуре (бекенд-сервера и фронтенд-сервера) не поможет, а так да, еще есть :).
А, невнимательно прочитал пост. Сущности по проводам отправляются в другую JVM, ясно.

Понимаю, что сильно оффтопик, но из личного опыта знаю, что требования к содержимому этих сущностей на сервере и на клиенте очень сильно разные. В текущем проекте у нас веб-приложение (Spring+Hibernate, rich domain), REST API, и командная строка как один из потребителей этого REST API. Плюс флэшевый клиент, который по нашему недосмотру не работает через REST, а через BlazeDS Remoting работает с сервисами бизнес-логики.

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

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

Честно говоря, я бы ни за что не подписался посылать хайбернейтовские/JPA сущности по проводам никуда. Уж больно много потенциальных проблем.

Но если бы пришлось — стал бы смотреть в сторону распределенных кэшей, или оригинальной Терракоты.
Секундочку, а как всё-таки вы решаете проблему с ленивой инициализацией?
Прокси, создавая объект по переданному ей интерфейсу, заполняет его всеми данными из
переданного ей объекта examplePerson? То есть examplePerson загружается из базы целиком, даже если в этом нет особой необходимости? В этом случае можно было использовать EAGER.
По-моему что-то опять не так в консерватории.
Хибернейтовские сущности в прокси не попадают. Entity-поля заменяются прокси.
И какие действия осуществляет прокси при обращении к её полям?
У прокси внутри лежит Map [имя поля -> значение]. Значения в нем — это или примитивные типы или другие прокси. Прокси, при обращении к полям возвращает или записывает значение из мэпа.
То есть из базы вытаскивается всё, в случае вложенности? И до какого предела? Или прокси поддерживает соединение?
А в случае изменения сущности что прокси делает?

Давайте код прокси этой, спрашивать тут вечно можно.
Ох, кода много вставлять придется. Не кошерно получится. Из базы вытаскивается только то, что нужно.
Sign up to leave a comment.

Articles