我正在寻找在整个交易中使用当前日期和时间的一致值的指南。
通过事务我松散地表示应用程序服务方法,这种方法通常执行单个SQL事务,至少在我的应用程序中。
this question的答案中描述的一种方法是将当前日期置于环境上下文中,例如DateTimeProvider
,并在任何地方使用{而不是DateTime.UtcNow
。
然而,这种方法的目的只是为了使设计单元可测试,而我还想防止因不必要的多次查询DateTime.UtcNow
而导致的错误,其中一个例子如下:
// In an entity constructor:
this.CreatedAt = DateTime.UtcNow;
this.ModifiedAt = DateTime.UtcNow;
此代码创建的创建和修改日期略有不同的实体,而人们希望这些属性在创建实体后立即相等。
此外,环境上下文很难在Web应用程序中正确实现,因此我提出了另一种方法:
DeterministicTimeProvider
类在每个生命周期范围内注册为"实例" AKA" Web应用程序中每个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;
}
这是一个好方法吗?有什么缺点?还有更好的选择吗?
答案 0 :(得分:8)
对于诉诸权威的逻辑谬误感到抱歉,但这很有趣:
约翰卡马克曾说过:游戏有四个主要输入:击键,鼠标移动,网络数据包和时间。 (如果你不把时间视为输入值,那么在你做之前要考虑它 - 这是一个重要的概念)“
来源:John Carmack's .plan posts from 1998 (scribd)
(我总是觉得这句话非常有趣,因为有人认为如果某些事情看起来不合适,你应该认为它真的很难,直到它看起来是正确的,只有一个主要的极客才会说。)子>
所以,这是一个想法: 将时间视为输入。 它可能不包含在构成Web服务请求的xml中,(你不会'无论如何都想要它,但是在将xml转换为实际请求对象的处理程序中,获取当前时间并使其成为请求对象的一部分。
因此,在处理事务的过程中,当请求对象在您的系统中传递时,在请求中始终可以找到被视为“当前时间”的时间。因此,它不再是“当前时间”,而是请求时间。 (事实上,它将是同一个,或者非常接近同一个,这完全无关紧要。)
这样,测试也变得更加容易:您不必模拟时间提供者界面,时间总是在输入参数中。
此外,通过这种方式,其他有趣的事情变得可能,例如,服务请求追溯应用,在与实际当前时刻完全无关的时刻。想想可能性。 (bob squarepants-with-a-rainbow的图片就在这里。)
答案 1 :(得分:4)
嗯..这对于CodeReview.SE来说比对StackOverflow感觉更好,但确定 - 我会咬人。
这是一个好方法吗?
如果使用正确,在您描述的场景中,这种方法是合理的。它实现了两个既定目标:
使您的代码更易于测试。这是我称之为#34;模拟时钟&#34;的常见模式,可以在许多精心设计的应用程序中找到。
将时间锁定为单个值。这种情况不常见,但您的代码确实实现了这一目标。
有哪些缺点?
由于您要为每个请求创建另一个新对象,因此它将为垃圾收集器创建一些额外的内存使用量和额外的工作量。这有点没有实际意义,因为这通常是每个请求生命周期的所有对象,包括控制器。
在您从时钟读取数据之前,只需要一小部分时间,这是由于在加载对象和进行延迟加载时所做的额外工作。但它可以忽略不计 - 可能只有几毫秒。
由于该值已被锁定,因此您(或使用您的代码的其他开发人员)可能会因为忘记该值不会发生变化而引入一个微妙的错误。下一个请求。您可以考虑使用不同的命名约定。例如,代替&#34;现在&#34;,将其称为&#34; requestRecievedTime&#34;或类似的东西。
与上一项相似,您的提供程序可能会加载错误的生命周期。您可以在新项目中使用它,忘记设置实例,将其加载为单例。然后,为所有请求锁定值。您无法执行此操作,因此请务必对其进行评论。 <summary>
标记是个好地方。
您可能会发现在构造函数注入不可能的情况下需要当前时间 - 例如静态方法。您必须重构以使用实例方法,或者必须将时间或时间提供程序作为参数传递给静态方法。
有更好的选择吗?
是的,请参阅Mike's answer。
您也可以通过IClock
界面以及SystemClock
和FakeClock
实施来考虑内置类似概念的Noda Time。但是,这两种实现都被设计为单例。他们帮助进行测试,但他们没有实现第二个目标,即将时间锁定为每个请求的单个值。你总是可以编写一个实现它的实现。
答案 2 :(得分:2)
代码看起来很合理。
缺点 - 对象的生命周期最有可能由DI容器控制,因此提供者的用户无法确保始终正确配置(每次调用,而不是像app / singleton那样的生命周期)
如果您有类型代表&#34;交易&#34;放入&#34;开始&#34;可能会更好。而是在那里。
答案 3 :(得分:0)
这不是可以通过实时时钟和查询或通过测试来回答的问题。开发人员可能已经找到了一些模糊的方法来访问底层库调用...
所以不要这样做。依赖注入也不会在这里拯救你;问题是你想要在会议开始时采用标准的时间模式。&#39;
在我看来,根本问题在于你表达了一个想法,并寻找一种机制。正确的机制是命名它,并在名称中说出你的意思,然后只设置一次。 INSERT INTO
members (location, story)
SELECT '$location', '$story'
FROM table_name
WHERE username='$user'
ON DUPLICATE KEY UPDATE
hash = '$password',
location='$location',
story='$story'
是处理构造函数中只设置一次的好方法,并允许编译器和运行时强制执行意味着的内容,即只设置一次。
readonly