Как стать автором
Обновить
77
0
Vladimir @vkhorikov

Пользователь

Отправить сообщение
Верно ли, что результат невыполнения контракта — всегда исключение, а при валидации ситуация обрабатывается внутри метода?

Верно. Причем исключения эти не должны отлавливаться в коде (разве что для логирования ошибки), приложению в этом случае нужно дать упасть (fail-fast прицнип). Более подробно на тему исключений: habrahabr.ru/post/263685

Если внутри GetById проверить OrganizationId > 0, это будет контракт или валидация?

В целом ничего не мешает обозначить это как контракт, но с точки зрения best practices контрактами лучше всего помечать значимые с точки зрения модеолирования условия. К примеру то, что юзера нельзя удалить если он уже удален — значимое условие и дает разработчику важную информацию о доменной модели приложения. Еще пример:

public class Organization : Entity
{
    public void ProvisionUser(User user, Subscription subscription)
    {
        Contracts.Require(UsersInternal.Contains(user));
        Contracts.Require(SubscriptionsInternal.Contains(subscription));
        Contracts.Require(subscription.SeatsRemaining > 0);
        Contracts.Require(subscription.IsProvisionable);
        Contracts.Require(!user.IsProvisionedTo(subscription));

        subscription.AddSeat(user);
        user.AddSeat(subscription);
    }
}

Здесь мы даем кучу информации о том, какие условия должны быть соблюдены, чтобы подписать юзера на subscription, клиент этого кода должен быть ответственен за их выполнение перед вызовом ProvisionUser.

Заметил, что я не ответил на этот вопрос:
Чем технически различаются контракты и валидация?

Технически разницы нет. Весь инструментарий поверх контрактов — опционален. Разница в том, как трактовать результат невыполнения условия. В случае с контрактами невыполнение условия — это всегда ошибка в приложении, с точки зрения валидации, — обычное дело, которое можно выводить юзеру в качестве ошибки.
Если я правильно понял, вопрос в том, как правильно обозначать проверки в коде и как контракты?
У меня в коде это обычно выглядит так. Предусловия контактов обозначаю через самописный класс Contracts (внутри которого if с выбрасыванием специального исключения в случае невыполнения условия):

public void DeleteUser(User user)
{
    Contracts.Require(user.Status != UserStatus.Deleted);

    user.MarkAsDeleted();
}

Проверки выполняются обычными if-ами:

Organization org = _organizationRepository.GetById(model.OrganizationId);
if (orgOrNothing == null)
    return Error("No organization found for Id = " + model.OrganizationId);

Так при чтении кода становится понятно что есть что.
Все описанные вами проверки не являются контрактами, предусловия контрактов не должны зависить от состояния внешних по отношению к коду систем (в вашем случае это диск).
Контракты в коде — это набор техник, направленных на
1) Документирование поведения кода
2) Обеспечение быстрого фидбека в случае некорректного поведения кода

Проект CodeContracts и статические проверки — инструментарий поверх этих техник. Они полезны, но не являются обязательными.
Мы точно также можем обозначить контракты в коде (как минимум предусловия) с помощью самописных конструкций (сводящихся к обычным if-ам), главное чтобы было очевидно, что это именно контакты, а не что-то другое.
В коде клиента — имеется ввиду в коде класса-клиента внутри системы, а не в коде системы, которая вызывает API извне.
Т.е. на первой картинке баг будет в классе Class1.
Есть вероятность, что их добавят на уровне самого языка в одной из следующий версия C#: github.com/dotnet/roslyn/issues/119
Это кстати не только в Go такая тенденция. В функциональных языках тоже заметно стремление использовать контейнеры с результатом выполнения операции вместо выбрасывания исключения. Either монада как пример
В целом согласен, действительно настраивать каскад в БД или в самой ORM — разница не большая. Лично я обычно стараюсь делать так, чтобы база данных была настолько «тупой» насколько можно, поэтому если есть возможность, то все-таки выношу каскады из БД. Комментарии читал :)
Да, в целом вы правы. Проблема тут только в том, что этой функциональности нет «out of the box»
>А NH как будто может отслеживать такие ситуации
Может. Почитайте про Hi/Lo algorithm.
Например тут: stackoverflow.com/questions/282099/whats-the-hi-lo-algorithm
Да, могу. Проблема в том, что этой функциональности нет из коробки.
Да, я убрал эту часть из примера чтобы не делать нагромождений. У меня в целом была задача показать, что EF хуже поддерживает UoW из-за того, что у него только одна стратегия генерации идшников. Пример я думаю не выбран не слишком удачный для этого, с этим я согласен.
В задаче, к кот. я ссылаюсь в посте, они были нужны для целей real-time репортинга.
>кто мешает руками нужные ид сохранить самостоятельно?
В EF6 есть только одна стратегия генерации целочисленных Ids — database generated ids, в этом как раз и проблема. Вставка идшников руками решила бы проблему, как это решает Hi/Lo в NH.
Вы про ваш коммент? Если да, то это не совсем то, что нужно. Необходимо получение идентификаторов не в конце операции, а именно по мере создания сущностей.
В любом случае, сделать честный UoW, с database generated идентификаторами невозможно.
Либо через транзакцию, да. Суть в том, что в случае отмены операции эти данные придется удалять (либо вручную либо через транзакцию), что приводит к проседанию производительности. Гораздо эффективнее просто не вставлять эти данные в БД до момента завершения транзакции.
Имеется ввиду, что единственный способ в EF получить целочисленный идентификатор сущности — это сохранить ее в БД. Соответственно если в процессе операции вы решите отменить эту операцию, то вам придется удалить все сохраненные к этому моменту записи вручную. В NH есть различные стратегии генерации ID, что позволяет генерировать их не сохраняя саму сущность в базе.

В EF7 это кстати поправили, Rowan Miller говорит, что там уже есть Hi/Lo стратегия как в NH.
Да, этот момент я пропустил. Согласен, предусловия у MoveNext есть, но все же они не требуют наличия коннекта к БД (если за эталон брать реализацию List-а и пары других реализаций в BCL). Тут опять же вопрос — что брать за эталон, т.к. явных контрактов эти интерфейсы не объявляют.
Сначала вы берёте List как эталонную реализацию и говорите, что Reset, Current и MoveNext могут бросать исключения когда им вздумается:

Нет, я как раз пишу, что Reset и Current могут бросать исключения как им вздумается (исходя из нескольких реализаций в .NET), а вот MoveNext всегда возвращает bool, и не имеет при этом никаких предусловий. DbQuery нарушает принцип, потому что усиливает предусловия именно в MoveNext, а не в Reset или Current. Думаю стоило обозначить это более явно.

Вовсе нет. Нигде не объявлено, что Process работает с конечной коллекцией. Бесконечно обрабатывать запросы — вполне валидная задача.

Тут тоже наверное нужно было подобрать более подходящий пример. Этим примером я хотел показать, что Process ожидает именно конечную коллекцию. То, что бесконечная обработка запросов является валидной задачей — это безусловно.
(ошибся веткой)

Информация

В рейтинге
Не участвует
Работает в
Зарегистрирован
Активность