Geb*_*ebb 17 time datetime web-services transactions
我正在寻找在整个交易中使用当前日期和时间的一致值的指导原则.
通过事务我松散地表示应用程序服务方法,这种方法通常执行单个SQL事务,至少在我的应用程序中.
在这个问题的答案中描述的一种方法是将当前日期放在环境上下文中,例如DateTimeProvider
,并使用它来代替DateTime.UtcNow
任何地方.
然而,这种方法的目的只是为了使设计单元可测试,而我还想防止由不必要的多次查询引起的错误DateTime.UtcNow
,其中一个例子如下:
// In an entity constructor:
this.CreatedAt = DateTime.UtcNow;
this.ModifiedAt = DateTime.UtcNow;
Run Code Online (Sandbox Code Playgroud)
此代码创建的实体具有稍微不同的创建和修改日期,而人们希望这些属性在创建实体后立即相等.
此外,环境上下文很难在Web应用程序中正确实现,所以我想出了另一种方法:
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;
}
Run Code Online (Sandbox Code Playgroud)
这是一个好方法吗?有什么缺点?还有更好的选择吗?
对于在这里诉诸权威的逻辑谬误感到抱歉,但这很有趣:
约翰卡马克曾说过:
游戏有四个主要输入:击键,鼠标移动,网络数据包和时间.(如果你不把时间视为输入值,那么在你做之前要考虑它 - 这是一个重要的概念)"
资料来源:1998年John Carmack的.plan帖子(scribd)
(我总是觉得这句话非常有趣,因为有人认为,如果某些东西对你来说不合适,你应该认为它真的很难,直到它看起来是正确的,这只是一个主要的极客会说的.)
所以,这是一个想法:将时间视为输入.它可能不包含在构成Web服务请求的xml中(您不会想要它),但是在将xml转换为实际请求对象的处理程序中,获取当前时间并使其成为一部分您的请求对象.
因此,当在处理事务的过程中在您的系统中传递请求对象时,始终可以在请求中找到被视为"当前时间"的时间.所以,它不再是"当前时间",而是请求时间.(事实上,它将是同一个,或者非常接近同一个,这完全是无关紧要的.)
这样,测试也变得更加容易:您不必模拟时间提供程序接口,时间总是在输入参数中.
而且,通过这种方式,其他有趣的事情变得可能,例如,服务请求被追溯地应用在与实际当前时刻完全无关的时刻.想想可能性.(bob squarepants-with-a rainbow的图片就在这里.)
嗯.. 对于CodeReview.SE来说,这感觉比对于StackOverflow来说是一个更好的问题,但是当然 - 我会咬住。
这是一个好方法吗?
如果使用正确,在您描述的场景中,这种方法是合理的。它实现了两个既定目标:
使您的代码更具可测试性。这是一种常见的模式,我称之为“模拟时钟”,并且可以在许多设计良好的应用程序中找到。
将时间锁定为单个值。这种情况不太常见,但您的代码确实实现了该目标。
有什么缺点?
由于您要为每个请求创建另一个新对象,因此它将产生少量的额外内存使用量以及垃圾收集器的额外工作。这在某种程度上是一个有争议的问题,因为这通常是所有具有每个请求生命周期的对象(包括控制器)的处理方式。
在从时钟读取读数之前会添加一小部分时间,这是由于加载对象和延迟加载时所做的额外工作造成的。不过它可以忽略不计——可能只有几毫秒。
由于该值被锁定,因此始终存在这样的风险:您(或使用您的代码的其他开发人员)可能会忘记该值在下一个请求之前不会更改,从而引入微妙的错误。您可以考虑使用不同的命名约定。例如,将其称为“requestRecievedTime”或类似名称,而不是“now”。
与上一项类似,您的提供程序也存在加载错误生命周期的风险。您可能会在新项目中使用它,但忘记设置实例,将其作为单例加载。然后,所有请求的值都会被锁定。您无法执行太多操作来强制执行此操作,因此请务必对其进行注释。标签<summary>
是个好地方。
您可能会发现在无法进行构造函数注入的场景(例如静态方法)中需要当前时间。您要么必须重构才能使用实例方法,要么必须将时间或时间提供者作为参数传递到静态方法中。
有更好的选择吗?
是的,请参阅迈克的回答。
您还可以考虑Noda TimeIClock
,它通过接口和SystemClock
实现内置了类似的概念FakeClock
。然而,这两种实现都被设计为单例。它们有助于测试,但无法实现将每个请求的时间锁定为单个值的第二个目标。不过,您始终可以编写一个实现来实现这一点。