Подтвердить что ты не робот

Последовательно используя значение "сейчас" для транзакции

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

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

Окружающий контекст

Один из подходов, описанный в ответах на этот вопрос, заключается в том, чтобы поместить текущую дату в окружающий контекст, например. DateTimeProvider и используйте это вместо DateTime.UtcNow всюду.

Однако цель этого подхода состоит только в том, чтобы сделать тестовый блок конструктивным, тогда как я также хочу предотвратить ошибки, вызванные ненужным множественным запросом, в DateTime.UtcNow, примером чего является это:

// In an entity constructor:
this.CreatedAt = DateTime.UtcNow;
this.ModifiedAt = DateTime.UtcNow;

Этот код создает объект с немного отличающимися созданными и измененными датами, тогда как ожидается, что эти свойства будут равны сразу после создания объекта.

Кроме того, окружающий контекст трудно реализовать правильно в веб-приложении, поэтому я придумал альтернативный подход:

Метод Injection + DefinisticTimeProvider

  • Класс DeterministicTimeProvider зарегистрирован как "экземпляр на всю жизнь" экземпляр "AKA" для HTTP-запроса в зависимости от веб-приложения.
  • Он вводится конструктором в службу приложения и передается в конструкторы и методы сущностей.
  • Метод IDateTimeProvider.UtcNow используется вместо обычного DateTime.UtcNow/DateTimeOffset.UtcNow для получения текущей даты и времени.

Вот реализация:

/// <summary>
/// Provides the current date and time.
/// The provided value is fixed when it is requested for the first time.
/// </summary>
public class DeterministicTimeProvider: IDateTimeProvider
{
    private readonly Lazy<DateTimeOffset> _lazyUtcNow =
        new Lazy<DateTimeOffset>(() => DateTimeOffset.UtcNow);

    /// <summary>
    /// Gets the current date and time in the UTC time zone.
    /// </summary>
    public DateTimeOffset UtcNow => _lazyUtcNow.Value;
}

Это хороший подход? Каковы недостатки? Есть ли лучшие альтернативы?

4b9b3361

Ответ 1

Хммм.. это похоже на лучший вопрос для CodeReview.SE, чем для StackOverflow, но уверен - я буду кусать.

Это хороший подход?

При правильном использовании в описанном вами сценарии этот подход является разумным. Он достигает двух заявленных целей:

  • Сделайте ваш код более надежным. Это общий шаблон, который я называю "Mock the Clock" и находится во многих хорошо разработанных приложениях.

  • Блокировка времени до одного значения. Это реже, но ваш код действительно достигает этой цели.

Каковы недостатки?

  • Поскольку вы создаете еще один новый объект для каждого запроса, он создаст умеренный объем использования дополнительной памяти и дополнительную работу для сборщика мусора. Это несколько спорный вопрос, поскольку обычно это относится ко всем объектам с продолжительностью жизни по запросу, включая контроллеры.

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

  • Поскольку значение заблокировано, всегда существует риск того, что вы (или другой разработчик, который использует ваш код) может ввести тонкую ошибку, забыв, что значение не изменится до следующего запроса. Вы можете рассмотреть другое соглашение об именах. Например, вместо "now", назовите его "requestRecievedTime" или что-то в этом роде.

  • Как и в предыдущем пункте, существует риск того, что ваш провайдер может быть загружен с неправильным жизненным циклом. Вы можете использовать его в новом проекте и забыть установить инстанс, загрузив его как одноэлементный. Затем значения блокируются для всех запросов. Там не так много, чтобы это сделать, поэтому не забудьте прокомментировать это. Тег <summary> - хорошее место.

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

Есть ли лучшие альтернативы?

Да, см. Ответ Майка.

Вы также можете рассмотреть Noda Time, который имеет аналогичную концепцию, встроенную через интерфейс IClock, и SystemClock и FakeClock. Тем не менее, обе из этих реализаций рассчитаны на одноэлементы. Они помогают с тестированием, но они не достигают вашей второй цели - блокировать время до одного значения за запрос. Вы всегда можете написать реализацию, которая это делает.

Ответ 2

Извините за логическую ошибочность обращения к власти здесь, но это довольно интересно:

Джон Кармак однажды сказал:

В игру входят четыре основных входа: нажатия клавиш, перемещения мыши, сетевые пакеты и время. (Если вы не учитываете время ввода значения, подумайте об этом, пока не сделаете это - это важная концепция).

Источник: Джон Кармак. Плановые сообщения с 1998 года (scribd)

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

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

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

Таким образом, тестирование также становится еще проще: вам не нужно издеваться над интерфейсом провайдера времени, время всегда находится во входных параметрах.

Таким образом, возможны другие забавные вещи, например запросы на обслуживание, которые будут применяться ретроактивно, в момент времени, который полностью не связан с фактическим текущим моментом времени. Подумайте о возможностях. (Изображение bob squarepants-with-a-rainbow идет здесь.)

Ответ 3

Код выглядит разумным.

Недостаток - скорее всего, время жизни объекта будет контролироваться контейнером DI, и, следовательно, пользователь провайдера не может быть уверен, что он всегда будет правильно настроен (для каждого вызова и не больше времени жизни, например app/singleton).

Если у вас есть тип, представляющий "транзакцию", лучше поставить вместо него "Started" time.

Ответ 4

Это не то, на что можно ответить часами реального времени и запросом или путем тестирования. Разработчик, возможно, выяснил какой-то неясный способ достижения основного вызова библиотеки...

Так что не делай этого. Инжекционная инъекция также не спасет вас здесь; проблема в том, что вам нужен стандартный шаблон для времени в начале сеанса.

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

// In an entity constructor:
this.CreatedAt = DateTime.UtcNow;