Проверьте инварианты в отображенных в спящем классе - PullRequest
10 голосов
/ 26 октября 2009

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

Если у класса есть инварианты, которые зависят от нескольких свойств, дизайн класса становится сложным. Давайте начнем с гипотетического дизайна зеленого поля:

public class A { 
    private int x; 
    private int y; 

    public A(int x, int y) { 
        this.x = x; 
        this.y = y; 
        checkInvariants(this.x, this.y); 
    } 

    private void checkInvariants(int x, int y) { 
        if (x + y « 0) throw new IllegalArgumentException(); 
    } 
}

Это базовая реализация, которая не соответствует требованиям гибернации. Инвариант проверяется в конструкторе. (Содержание метода checkInvariants () не имеет значения, он представлен только для иллюстрации того, что инварианты класса могут зависеть от нескольких свойств.)

Класс можно использовать следующим образом:

new A(0, 0); 
new A(-1, 0); //invalid 

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

public class H { 
    int x; 
    int y; 

    public H(int x, int y) { 
        this.x = x; 
        this.y = y; 
        checkInvariants(this.x, this.y); 
    } 

    H(){} 

    private void checkInvariants(int x, int y) { 
        if (x + y « 0) throw new IllegalArgumentException(); 
    } 
} 

Это имеет два основных недостатка: * Вы начинаете реализовывать код, который зависит от клиента (Hibernate). В идеале класс не знает своих вызывающих. * Особая проблема с этим обходным решением заключается в том, что экземпляры, инициированные hibernate, не проверяются , если встречаются инварианты Вы доверяете данным, загруженным из базы данных, что проблематично. Даже если ваше приложение является единственным, использующим эту конкретную схему базы данных, всегда есть возможность специальных изменений со стороны администраторов.

Второй обходной путь - проверка инвариантов в коде пользователя :

public class I { 
    private int x; 
    private int y; 

    public I() {} 

    public void checkInvariants() { 
        if (x + y « 0) throw new IllegalArgumentException(); 
    } 

    public void setX(int x){ 
        this.x = x; 
    } 

    public void setY(int y){ 
        this.y = y; 
    } 
} 

I i = new I(); 
i.setX(-1); 
i.setY(0); 
i.checkInvariants(); 

Очевидно, это делает код пользователя более сложным и подверженным ошибкам . Этот дизайн не соответствует ожиданиям того, что экземпляр является согласованным после создания и остается согласованным после каждого изменения состояния (вызова метода). Каждый пользователь должен проверять инварианты для каждого создаваемого им экземпляра (возможно, косвенно с помощью hibernate).

Есть ли лучшее решение этой проблемы:

  • не слишком сложный
  • без явных знаний о его пользователях
  • без зависимости от каркаса гибернации?

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

(Просто из любопытства: есть ли платформа ORM, которая поддерживает «внедрение конструктора»?)

Ответы [ 3 ]

5 голосов
/ 26 октября 2009

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

Вы начинаете реализовывать код, который зависит от клиента (Hibernate). В идеале класс не знает своего звонящие.

Вы используете слово "зависимость" здесь довольно быстро и свободно. Hibernate не является «клиентом»; это фреймворк , который вы (как разработчик / архитектор / что у вас) выбрали для реализации своей настойчивости. Поэтому где-то у вас будет код, который использует (и, следовательно, зависит от) Hibernate. Тем не менее, в вашем доменном объекте есть зависимость NO от Hibernate. Наличие конструктора без аргументов - семантическое требование, если хотите; это не вводит фактическую зависимость. Переключите Hibernate на JPA / TopLink / raw jdbc / что у вас есть, и вам не придется ничего менять в объектном коде вашего домена.

Конкретная проблема с этим обходным путем это случаи, инициированные гибернации не проверяются, если встречаются инварианты. Вы доверяете данным загружается из базы данных, которая проблематично. Даже если ваш приложение является единственным, использующим это конкретная схема базы данных есть всегда возможность ad-hoc изменения администратором.

Вам не нужно «доверять» данным (подробнее об этом ниже). Тем не менее, я не думаю, что этот аргумент имеет смысл. Если вы изменяете свои данные в нескольких приложениях, вы должны выполнять проверки на каком-то обычном нижнем уровне, а не полагаться на каждое отдельное приложение для проверки данных. Указанным общим уровнем может быть сама база данных (в простых случаях) или сервисный уровень, предоставляющий общий API для использования несколькими приложениями.

Более того, представление администраторов о внесении изменений непосредственно в базу данных в рамках распорядка дня совершенно нелепо. Если вы говорите об особых случаях (исправления ошибок, что у вас есть), их следует рассматривать как таковые (то есть, предположительно, что-то подобное происходит очень редко, и бремя проверки таких «критических» изменений лежит на том, кто их вносит; не на каждом из ваших приложений в стеке).

Все это говорит о том, что если вы действительно хотите проверить свой объект, когда он загружен , достичь этого достаточно просто. Определите интерфейс Valid, который имеет метод validate(), и каждый соответствующий объект домена реализует его. Вы можете вызвать этот метод из:

  1. Ваш DAO / сервис после загрузки объекта.
  2. Перехватчик или прослушиватель Hibernate - оба настроены в конфигурации Hibernate; все, что вам нужно сделать, это реализовать любой из них, чтобы проверить, реализует ли загружаемый объект Valid и, если это так, вызвать метод.
  3. Или вы можете использовать Hibernate Validator , однако это БУДЕТ связывать ваши доменные объекты с Hibernate, как вы бы их аннотировали.

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

select new A(x, y) from ...

Обновление (к адресам из комментариев Томаса):

  1. Я упомянул только перехватчик для полноты; слушатель гораздо более уместен в этом случае. PostLoadEventListener вызывается после полной инициализации объекта.
  2. Еще раз, наличие конструктора без аргументов не является зависимостью. Да, это контракт, но он никак не привязывает ваш код к Hibernate. А что касается контрактов, то это часть спецификации javabean (на самом деле, она менее ограничительна, потому что конструктор не должен быть публичным), поэтому в большинстве случаев вы все равно будете следовать ей.
  3. Доступ к базе данных. «Рефакторинг базы данных и миграция распространены» - да, они есть. Но это всего лишь код; Вы пишете это, вы тестируете это, вы запускаете интеграционные тесты, вы развертываете это в производство. Если ваш объект модели предметной области использует какой-то метод StringUtil.compare(), который вы написали в каком-либо служебном классе, у вас нет его перепроверки результатов, не так ли? Точно так же объект предметной области не должен проверять, что ваш скрипт миграции ничего не сломал - у вас должны быть соответствующие тесты для этого. «Возможность делать специальные запросы ... это одна из особенностей» - абсолютно. Запросы . Например, как в запросах «только для чтения», используемых для создания отчетов (даже во многих случаях более целесообразно использовать API). Но ручные данные манипуляции как не экстренные - абсолютно нет.
  4. Изменчивость делает инжектор конструктора неактуальным . Я не говорю, что у вас не может быть конструктора не по умолчанию - вы можете, и вы можете использовать его в своем коде. Но если у вас есть методы установки, вы не можете использовать свой конструктор для проверки, поэтому не имеет значения, есть он или нет.
  5. HQL конструктор инъекций и ассоциаций. Вложенные конструкторы не будут работать, насколько я знаю (например, вы не можете написать select new A(x, y, new B(c, d)); поэтому для получения ассоциаций вам нужно будет извлечь их как объекты в предложении select, что означает у них самих должны быть конструкторы без аргументов :-) Или вы можете иметь конструктор для "основной" сущности, который принимает все необходимые вложенные свойства в качестве аргументов и создает / заполняет ассоциации внутри, но это безумие: -)
3 голосов
/ 27 октября 2009

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

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

Но если вы обеспокоены тем, что есть плохие данные, то я бы предположил, что есть более серьезная проблема.

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

В любом случае, если в вашей базе данных есть поврежденные данные или ложные утверждения, чему еще можно доверять?

2 голосов
/ 26 октября 2009

Один из подходов будет основан на вашем «классе I», но с самим классом, вызывающим checkInvariants () при первом использовании полей. Это позволяет полям быть временно недействительными во время присваивания (после вызова setX (), но перед setY (), например). Но это все еще гарантирует, что когда-либо используются только действительные данные.

Ваш пример класса никогда не использует значения, поэтому я предполагаю, что к значениям можно получить доступ через getX (), getY () и doSomething (), например:

public int getX(){ 
    return x; 
} 

public int getY(){ 
    return y; 
} 

public void doSomething(){
    x = x + y;
}

Вы бы изменили это так:

private boolean validated = false;

public int getX(){ 
    if (!validated) {
        checkInvariants();
    }

    return x; 
} 

public int getY(){ 
    if (!validated) {
        checkInvariants();
    }
    return y; 
} 

public void doSomething(){
    if (!validated) {
        checkInvariants();
    }
    x = x + y;
}

public void checkInvariants() { 
    validated = true;
    if (x + y « 0) throw new IllegalArgumentException(); 
}

Вы можете переместить 'if (! Validated)' в checkInvariants (), если хотите.

Вы также можете посмотреть на Hibernate Validator, хотя он может не соответствовать вашему требованию независимости от фреймворка. http://docs.huihoo.com/hibernate/annotations-reference-3.2.1/validator.html

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...