Опять валидация данных

IT IT
Здравствуйте, Козьма Прутков, Вы писали:

КП>Если не жалко, поделитесь идеями, как организовать подобную функциональность...


Прежде всего я бы рассмотрел различные стадии валидации и почему они необходимы, а уже потом её реализацию.

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

1. Базовая валидация допустимых параметров вводимых значений. Сюда входит проверка обязательности и длин полей, формат вводимых данных. Основное назначение данного уровня — минимизировать возможность неправильного ввода, исключить походы на сервер (в случае Web) и предоставить пользователю информацию о некорректно-введённых значениях.

2. Проверка валидности бизнес-энтити как единого целого. Этот уровень повторяет все проверки предыдущего, плюс добавляет проверки комбинации полей объекта. Например, если вводимый адрес принадлежит US, то Zip код должен быть 5 или 9 символов, если это Россия, то размер почтового индекса должен равняться 6-ти.

3. Проверка валидности объекта в пределах системы. Включает все предыдущие проверки, плюс всевозможные проверки на непротиворечивость объекта в системе. Например, тот же адрес обычно проверятся на существование такового с помощью специализированных систем. Email пользователя не должен дублироваться в системе и т.п.

4. Проверка целостности данных перед их сохранением в базе данных. Обычно с успехом выполняется средствами самой БД.

Теперь о реализации. С (4) всё понятно. Использвание констрейнов, пользовательских типов, уникальных индексов и FK решают эту проблему. Частично, например, в случае с e-mail, сюда можно перенести проверки 3-го уровня. В случае обычного UI (не-Web) приложения обычно удаётся совместить 1-й и 2-й уровни. Для Web-приложений 1-й (обычно не полностью) выполняется в браузере, 2-й и 3-й уровни можно совместить, если не используется выделенный сервер приложений. Если используется, то 2-й в Web-приложении, 3-й в сервере приложений.

Реализация 2-го и 3-го уровня обычно делается ручками. Ничего здесь автоматизировать, к сожалению, не получается. Причём 2-й следует делать как часть самого бизнес-объекта, а 3-й как метод бизнес логики. А вот с 1-м, который как правило самый занудный всё гораздо интереснее. Лучшая, на мой взгляд, реализация данного уровня — это применение атрибутов (речь, конечно идёт о .NET). В этом случае, т.к. мы фактически расширяем метадату объекта, удаётся по большей части автоматизировать проверки на UI, т.е. не только свести саму реализацию валидации объекта к описанию в виде атрибутов, но и натренировать UI компоненты понимать эти атрибуты.

Таким образом, 1-й и 2-й уровень удаётся инкапсулировать в самом объекте. 3-й уровень представляет собой один метод бизнес логики. 4-й реализуется средствами самой БД.

В общем, как-то так
... << RSDN@Home 1.1.4 beta 3 rev. 185>>
Козьма Прутков
Козьма Прутков
15.03.2005 08:47
Здравствуйте, IT, Вы писали:

IT>К сожалению, валидацию не получается делать в одном месте за один раз по ряду причин, поэтому обычно она разбивается на несколько уровней.


Отлично. Ясно и по полочкам.
Да, с форматами и длинами я пожалуй спорить не буду: такие вещи крайне редко меняются, особенно форматы .
Рассмотрим веб-приложение (ибо сложнее), но аналогично и винформовое. Пусть по первоначальным требованиям поле Заказчик обязательное. Ок, сделали. Через 3 месяца оказывается, что оно не является обязательным. И мы шагом марш по всем уровням валидации это дело править. Или я чего-то не понял?
Таперь рассмотрим винформс-приложение (в веб-приложении решается написанием метода установки пары значений). Вот подвязана у нас к свойствам Country и ZipCode (он же индекс для примера) пара контролов. По идее, если пользователь меняет один из них, то нужно проверить их пару. Так что пользователь, однажды введя US/12345 уже никогда не введет RU/131000, ибо перекрестные комбинации невалидны. Ну, либо позволить перевести БО в неконсистентное состояние, что вряд ли очень хорошо.
IT
IT
15.03.2005 03:08
Здравствуйте, Козьма Прутков, Вы писали:

КП>Рассмотрим веб-приложение (ибо сложнее), но аналогично и винформовое. Пусть по первоначальным требованиям поле Заказчик обязательное. Ок, сделали. Через 3 месяца оказывается, что оно не является обязательным. И мы шагом марш по всем уровням валидации это дело править. Или я чего-то не понял?

КП>Таперь рассмотрим винформс-приложение (в веб-приложении решается написанием метода установки пары значений). Вот подвязана у нас к свойствам Country и ZipCode (он же индекс для примера) пара контролов. По идее, если пользователь меняет один из них, то нужно проверить их пару. Так что пользователь, однажды введя US/12345 уже никогда не введет RU/131000, ибо перекрестные комбинации невалидны. Ну, либо позволить перевести БО в неконсистентное состояние, что вряд ли очень хорошо.

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

Что же касается первого вопроса об изменениях по всем уровням валидации, то здесь я видимо не совсем ясно выразился. Говоря "этот уровень повторяет все проверки предыдущего" я не имел ввиду дублирование кода, я имел ввиду его повторное использование. Например, имеем вот такой класс Address:

public class Address : BizEntityBase
{
    [MaxLength(35), Required] public string      Address1;
    [MaxLength(35)          ] public string      Address2;
    [MaxLength(35), Required] public string      City;
    [MaxLength(35), Required] public string      Region;
    [               Required] public string      PostalCode;
    [               Required] public CountryCode CountryCode;

    public override void Validate()
    {
        // 1-й уроовень.
        //
        base.Validate();

        // 2-й.
        //
        switch (CountryCode)
        {
            case CountryCode.US:
                if (PostalCode.Length != 5 || PostalCode.Length != 9)
                    throw new Exception("Zip must be 5 or 9 digits.");
                break;

            case CountryCode.RU:
                if (PostalCode.Length != 6)
                    throw new Exception("Почтовый индекс должен содержать 6 цифр.");
                break;
        }
    }
}

Здесь в одном месте реализованы 1-й и 2-й уровни валидации. Третий в бизнес объекте:

public class AddressService : ServiceBase
{
    public void AddAddress(Address address)
    {
        // 1-й, 2-й.
        //
        address.Validate();

        // 3-й. Вызываем код проверки адреса на существование.
        // Используем софт компании Address Validation, Inc.
        //
        new Agents.AddressValidator().Validate(address);

        // Сохраняем обхект в БД.
        //
        new AddressDataAccessor().AddAddress(address);
    }
}

4-й в БД.

IF EXISTS (SELECT * FROM sysobjects WHERE type = 'U' AND name = 'Address')
BEGIN
    PRINT 'Dropping Table Address'
    DROP   Table    Address
END
GO

PRINT 'Creating Table Address'
GO

CREATE TABLE dbo.Address
(
    AddressID  int IDENTITY (1,1) NOT NULL CONSTRAINT PK_Address PRIMARY KEY CLUSTERED,
    Address1       nvarchar(35)   NOT NULL,
    Address2       nvarchar(35),
    City           nvarchar(35)   NOT NULL,
    Region         nvarchar(35)   NOT NULL,
    PostalCode     nvarchar(11)   NOT NULL,
    CountryCode    udt_CountryCode
) ON [PRIMARY]

GRANT SELECT ON Customer TO PUBLIC
GO

Теперь, допустим, нам нужно одно из полей сделать необязательным. Сколько нужно сделать изменений? Правильно, две. Первое — убрать Required атрибут в классе, второе — NOT NULL в SQL скрипте. Готовоо.
... << RSDN@Home 1.1.4 beta 3 rev. 185>>
Козьма Прутков
Козьма Прутков
16.03.2005 01:53
Здравствуйте, IT, Вы писали:

IT>Ты говоришь о валидации непосредственно во время ввода, о валидации каждого введённого символа. Мне эта практика кажется крайне порочной как раз по причинам, которые ты описываешь. Формы, которые думают что они умней меня всегда раздражают. Валидация значений и их комбинаций должна делаться при сабмите формы. Во время ввода можно проверять только формат, типа ничего не давать пользователю вводить кроме цифр и т.п.


Боже упаси! Как ты мог так плохо обо мне подумать! Просто насколько я помню в винформах при подвязке свойств к контролам "сбрасывание" значений в свойства происходит по одному, соответственно, либо страна либо индекс будет присвоен первым, и проверить это нельзя. Но это бог с ним, издержки технологии...

IT>Что же касается первого вопроса об изменениях по всем уровням валидации, то здесь я видимо не совсем ясно выразился. Говоря "этот уровень повторяет все проверки предыдущего" я не имел ввиду дублирование кода, я имел ввиду его повторное использование. Например, имеем вот такой класс Address:


<skipped />

IT>Теперь, допустим, нам нужно одно из полей сделать необязательным. Сколько нужно сделать изменений? Правильно, две. Первое — убрать Required атрибут в классе, второе — NOT NULL в SQL скрипте. Готовоо.


Класс. Но тут как раз и происходит та проблема, о которой я говорил: ты можешь перевести объект в неконсистентное состояние. То есть легко сунуть ему новую страну, а потом сразу в БД.

Ладно, я приблизительно понял твою мысль. Спасибо.
stalcer
stalcer
17.03.2005 10:35
Здравствуйте, Козьма Прутков, Вы писали:

КП>Класс. Но тут как раз и происходит та проблема, о которой я говорил: ты можешь перевести объект в неконсистентное состояние. То есть легко сунуть ему новую страну, а потом сразу в БД.


Сразу в бд неполучится, т.к. насколько я понимаю AddressService.AddAddress должен быть единтвенным доступным способом сохранить объект, а он делает валидацию.

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

Таким образом получается, что для работы с формами лучше иметь бизнес объекты с:
    — наличием конструктора с определенной сигнатурой, например, конструктора без параметров, чтобы форма (или другой сервис) могла без проблем создавать новые объекты
    — полями (без сеттеров), чтобы форма могла присваивать значения в любой последовательности
    — и функцией validate, которая вызывается формой или другими сервисами автоматически.

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

Получается противочечие .

Я вот пишу движок для всего этого (времени много, начальство одобряет). Есть свой язык, своя VM. И если представить, что можно реализовать любые экзотические конструкции в языке, специально для объявления и работы с бизнес объектами, какие-бы вы предпочли? Так, чтобы удобно было и для форм и для бизнес логики? Есть идеи? .
GlebZ
GlebZ
17.03.2005 05:45
Здравствуйте, stalcer, Вы писали:

S>Получается противочечие .

Никакого противоречия нет. На разных уровнях разная бизнес-логика. И поэтому объект проходя по уровню либо меняет свой вид, или предлагает интерфейс, которым могут воспользоваться объекты бизнес логики нескольких уровней. Поэтому бизнес-объект и не делают сверх-навороченным.

S>Я вот пишу движок для всего этого (времени много, начальство одобряет). Есть свой язык, своя VM. И если представить, что можно реализовать любые экзотические конструкции в языке, специально для объявления и работы с бизнес объектами, какие-бы вы предпочли? Так, чтобы удобно было и для форм и для бизнес логики? Есть идеи? .

Бизнес-правила разные для форм и для бизнес-логики. Игорь хорошо это описал. Даже если ты описываешь операцию валидирования, многие операции можно выполнить только на 3 и 4 уровне. Если, конечно, тебе не хватает функциональности System.Data.DataSet.

С уважением, Gleb.
stalcer
stalcer
18.03.2005 07:56
Здравствуйте, GlebZ, Вы писали:

GZ>Никакого противоречия нет. На разных уровнях разная бизнес-логика. И поэтому объект проходя по уровню либо меняет свой вид, или предлагает интерфейс, которым могут воспользоваться объекты бизнес логики нескольких уровней. Поэтому бизнес-объект и не делают сверх-навороченным.


Т.е. объект с "изменяющимся видом" и кучей интерфейсов не считается навороченным? И с ним, наверное, очень просто работать (интуитивно понятно, так сказать).

А потом, я всегда рассуждаю так: есть объект и есть остальная система, которая им пользуется. Так что функциональность разделяется. Но, ту функциональность, которая на объект возложена, он должен выполнять качественно, т.е. не давать перевести себя в невалидное состояние (невалидное, с точки зрения самого объекта).

Например, выше описанный Address:
Его смысл состоит в представлении информации о адресе, исходя из этого он (объект) и должен делать валидацию себя. Одной подсистеме, использующей данный объект может и не нужно знать существует ли такой адрес в реальности, а другой — нужно, исходя из этого подсистема делает (или не делает) соответствующую проверку.
И основная мысль в том, что эта проверка никоим образом к бизнес объекту не относится, она относится к способу использования этого бизнес объекта в конкретной подсистеме.

Поэтому получаются две совершенно разные валидации:
    — валидация самого объекта
    — валидация состояния системы по завершению бизнес транзакции

Причем вторая не столь формализуема как первая.

GZ>Бизнес-правила разные для форм и для бизнес-логики.


Но есть бизнес объект с которым работает и форма и бизнес логика. И валидация с точки зрения самого объекта должна быть одна и та же.

GZ>Даже если ты описываешь операцию валидирования, многие операции можно выполнить только на 3 и 4 уровне.


На третьем — да, об этом я написал выше в данном посте, но повторюсь, что это не валидирование объекта, а валидирование способа его использования в како-либо подсистеме.
Четвертый уровень — только физический. Никакой дополнительной логической нагрузки он не несет. В нормальном фреймворке с базой напрямую никто и работать-то не должен, только с бизнес объектами и специальным языком запросов. А то, что в реальности некоторые проверки удобно выполнять в СУБД (ссылочную целостность, например), так это только оптимизация (ну и еще успокоение нервов админа СУБД), их принципиально можно делать и сервере приложений.

GZ>Если, конечно, тебе не хватает функциональности System.Data.DataSet.


Вот люди. Я с ними как с художниками, про новый движок, про специальный язык, а они... Ширее надо мыслить .
GlebZ
GlebZ
18.03.2005 12:29
Здравствуйте, stalcer, Вы писали:

S>Здравствуйте, GlebZ, Вы писали:


GZ>>Никакого противоречия нет. На разных уровнях разная бизнес-логика. И поэтому объект проходя по уровню либо меняет свой вид, или предлагает интерфейс, которым могут воспользоваться объекты бизнес логики нескольких уровней. Поэтому бизнес-объект и не делают сверх-навороченным.


S>Т.е. объект с "изменяющимся видом" и кучей интерфейсов не считается навороченным? И с ним, наверное, очень просто работать (интуитивно понятно, так сказать).

Допустим, у тебя есть набор полей в форме, которое переводится в бизнес-объект с клиентской бизнес-логикой, которое переводится в некоторый объект DTO, которое переводится в бизнес-объект доменной модели, которое в свою очередь переводится в набор строк в базе данных. Никакой кучи интерфейсов. Просто состояние объекта находится в том виде, которое нужно в данный момент. Естественно данный список приведен от балды.

S>А потом, я всегда рассуждаю так: есть объект и есть остальная система, которая им пользуется. Так что функциональность разделяется. Но, ту функциональность, которая на объект возложена, он должен выполнять качественно, т.е. не давать перевести себя в невалидное состояние (невалидное, с точки зрения самого объекта).

Бизнес-логика которая на клиенте, и бизнес-логика которая на сервере(пускай это даже сервер БД) разные. У них не просто разные возможности, но и разные цели.

S>Например, выше описанный Address:

S>Его смысл состоит в представлении информации о адресе, исходя из этого он (объект) и должен делать валидацию себя. Одной подсистеме, использующей данный объект может и не нужно знать существует ли такой адрес в реальности, а другой — нужно, исходя из этого подсистема делает (или не делает) соответствующую проверку.
S>И основная мысль в том, что эта проверка никоим образом к бизнес объекту не относится, она относится к способу использования этого бизнес объекта в конкретной подсистеме.

S>Поэтому получаются две совершенно разные валидации:

S>

    S>- валидация самого объекта
    S>- валидация состояния системы по завершению бизнес транзакции
    S>

S>Причем вторая не столь формализуема как первая.

Давай классифицируем когда происходит валидация данных на форме. Есть 3 случая:
1. При вводе каждого символа. Например, бизнес задачей определено что в данном поле может быть только цифра. Тогда поле не должно воспринимать ничего кроме нажатие на клавиши с цифрами.
2. При потере фокуса. Если неверное значение, то фокус не должен уходить
3. Перед отправлением объекта на другой уровень.
1-2 случаи логикой бизнес-объекта не управляются. В случае создания нового бизнес-объекта, его может еще и не сущестовать. Плюс к этому, значения выбранные из справочника — вообще незачем на клиенте валидировать.
Более криминальный случай, например:
у тебя объект должен показываться иконкой в зависимости от значения. И на разных формах(клиентах) показ поля разный. Так что запихнуть всю бизнес логику на бизнес-объект, даже на уровне клиента не удастся.


GZ>>Бизнес-правила разные для форм и для бизнес-логики.


S>Но есть бизнес объект с которым работает и форма и бизнес логика. И валидация с точки зрения самого объекта должна быть одна и та же.

Частично да. Но например не обязательно объекты бизнес логики должны пользоваться функционалом бизнес-объекта. В случае уровня 3, легче проверять объект от а до я(или от a to z) полностью. Особенно в случае доменной модели. На 4 уровне — это может быть обязательным условием.

GZ>>Даже если ты описываешь операцию валидирования, многие операции можно выполнить только на 3 и 4 уровне.


S>На третьем — да, об этом я написал выше в данном посте, но повторюсь, что это не валидирование объекта, а валидирование способа его использования в како-либо подсистеме.

S>Четвертый уровень — только физический. Никакой дополнительной логической нагрузки он не несет. В нормальном фреймворке с базой напрямую никто и работать-то не должен, только с бизнес объектами и специальным языком запросов. А то, что в реальности некоторые проверки удобно выполнять в СУБД (ссылочную целостность, например), так это только оптимизация (ну и еще успокоение нервов админа СУБД), их принципиально можно делать и сервере приложений.

Если данные используемые при проверке больше чем оперативная память, то принципиально нельзя.

GZ>>Если, конечно, тебе не хватает функциональности System.Data.DataSet.


S>Вот люди. Я с ними как с художниками, про новый движок, про специальный язык, а они... Ширее надо мыслить .

Не ширее а ширше. Диревня.
Функциональность датасета (или вернее сказать xsd) достаточно продвинута и достаточна. Еще добавить регуляры, то вообще был бы класс.

С уважением, Gleb.
Сомов Александр
S>Таким образом получается, что для работы с формами лучше иметь бизнес объекты с:
S>

    S> — наличием конструктора с определенной сигнатурой, например, конструктора без параметров, чтобы форма (или другой сервис) могла без проблем создавать новые объекты
    S> — полями (без сеттеров), чтобы форма могла присваивать значения в любой последовательности
    S> — и функцией validate, которая вызывается формой или другими сервисами автоматически.
    S>


S>А для программной работы лучше иметь визнес объекты, написанные по всем правилам ООП:

S>

    S> — с такими конструкторами, какие необходимы логически
    S> — со свойствами
    S> — и т.д.
    S>

S>Получается противочечие .


На самом деле это решается паттерном builder. Т.е. есть объект builder, который используется для построения бизнес-объекта. Конструктор пустой, и все свойста для заполнения присутствуют. Как только программист решает, что все данные собраны, он делает newBusinessObject = builder.Create(). И вот Create либо возвращает объект в согласованном состоянии, либо выкидывает исключение. (Кстати, самый простой пример — это StringBuilder (из .NET), правда он как раз всегда может создать строку в согласованном состоянии, но зато мы не увидим строку до того, как всё её содержимое будет собрано).
... << RSDN@Home 1.1.4 beta 4 rev. 303>>
stalcer
stalcer
01.04.2005 06:08
Здравствуйте, Сомов Александр, Вы писали:

СА>На самом деле это решается паттерном builder. Т.е. есть объект builder, который используется для построения бизнес-объекта. Конструктор пустой, и все свойста для заполнения присутствуют. Как только программист решает, что все данные собраны, он делает newBusinessObject = builder.Create(). И вот Create либо возвращает объект в согласованном состоянии, либо выкидывает исключение. (Кстати, самый простой пример — это StringBuilder (из .NET), правда он как раз всегда может создать строку в согласованном состоянии, но зато мы не увидим строку до того, как всё её содержимое будет собрано).


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

Они за вами после этого неделю с топором носиться будут.
И ведь поймают в конце-концов. Потому что их больше.

Если серьезно, то мне в этом отношении нравиться Delphi. Вот с какой точки зрения: Delphi не заставляет среднего пользователя писать объектно-ориентированные вещи. Пользователи преимущественно используют уже готовые (т.е. VCL).
А как известно, писать библиотеки в ОО стиле гораздо-гораздо-гораздо сложнее, чем использовать их.

Поэтому в фреймворке я хочу, чтобы всякие справочники (и бизнес объекты для них) создавались мастерами, т.е. визуальными средствами, например, как в 1С.
А использовать их в бизнес логике, естественно программно.