代码之家  ›  专栏  ›  技术社区  ›  Gebb

在整个交易过程中始终使用“现在”的价值

  •  17
  • Gebb  · 技术社区  · 11 年前

    我正在寻找在整个交易中使用当前日期和时间的一致值的指南。

    所谓事务,我大致是指应用程序服务方法,这种方法通常执行单个SQL事务,至少在我的应用程序中是这样。

    环境上下文

    答案中描述了一种方法 this question 将当前日期放在环境上下文中,例如。 DateTimeProvider ,并使用它代替 DateTime.UtcNow 处处

    然而,这种方法的目的只是使设计单元可测试,而我也希望防止不必要的多次查询导致的错误 日期时间.UtcNow ,例如:

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

    这段代码创建了一个创建日期和修改日期略有不同的实体,而人们希望这些属性在实体创建后立即相等。

    此外,环境上下文很难在web应用程序中正确实现,因此我提出了另一种方法:

    方法注入+DeterministicTimeProvider

    • 这个 DeterministicTimeProvider 类注册为“每个生存期范围的实例”AKA“web应用程序中每个HTTP请求的实例”依赖项。
    • 它是注入到应用程序服务的构造函数,并传递给实体的构造函数和方法。
    • 这个 IDateTimeProvider.UtcNow 方法而不是通常的方法 日期时间.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;
    }
    

    这是一个好方法吗?缺点是什么?有更好的选择吗?

    4 回复  |  直到 8 年前
        1
  •  8
  •   Mike Nakis    11 年前

    对不起,逻辑错误 向当局申诉 这里,但这很有趣:

    约翰·卡马克曾说过:

    游戏有四个主要输入:按键、鼠标移动、网络数据包和时间。(如果你不认为时间是一个输入值,那么在你考虑之前就要考虑它——这是一个重要的概念)”

    资料来源: John Carmack's .plan posts from 1998 (scribd)

    (我一直觉得这句话非常有趣,因为只有一个主要的极客才会说,如果你觉得某件事不对,你应该很难想到它,直到它看起来对为止。)

    因此,这里有一个想法: 将时间视为输入。 它可能不包含在构成web服务请求的xml中(无论如何您都不希望它包含在内),但在将xml转换为实际请求对象的处理程序中,获取当前时间并将其作为请求对象的一部分。

    因此,在处理事务的过程中,当请求对象在系统中传递时,总是可以找到被认为是“当前时间”的时间 在请求中 。所以,现在不再是“当前时间”,而是 请求时间 。(事实上,它将是相同的,或非常接近相同的,这是完全无关的。)

    这样,测试也变得更加容易:您不必模拟时间提供程序接口,时间总是在输入参数中。

    此外,通过这种方式,其他有趣的事情也变得可能,例如,在与 真实的 当前时刻。想想可能性。(这里有一张鲍勃·方肩峰和彩虹的照片。)

        2
  •  4
  •   Community Mohan Dere    9 年前

    嗯,这感觉是一个更好的问题 CodeReview.SE 比 StackOverflow ,但肯定-我会咬的。

    这是一个好方法吗?

    如果在您描述的场景中正确使用,这种方法是合理的。它实现了两个既定目标:

    1. 使代码更易于测试。这是我称之为“模拟时钟”的常见模式,在许多精心设计的应用程序中都可以找到。

    2. 将时间锁定为单个值。这不太常见,但您的代码确实实现了这一目标。

    缺点是什么?

    1. 由于您正在为每个请求创建另一个新对象,因此它将为垃圾收集器创建少量的额外内存使用和额外工作。这有点无意义,因为这通常适用于每个请求生存期的所有对象,包括控制器。

    2. 在读取时钟读数之前,会增加一小部分时间,这是由于加载对象和延迟加载时所做的额外工作造成的。尽管如此,它可以忽略不计——可能只有几毫秒的量级。

    3. 由于值被锁定,因此您(或使用您的代码的其他开发人员)可能会忘记值在下一个请求之前不会更改,从而引入一个微妙的错误。您可以考虑不同的命名约定。例如,将其称为“requestRecivedTime”或类似的名称,而不是“now”。

    4. 与前一项类似,您的提供者也有可能加载了错误的生命周期。您可能会在新项目中使用它,而忘记设置实例,将其作为单例加载。然后锁定值 全部的 请求。你没办法强制执行这一点,所以一定要好好评论。这个 <summary> 标签是个好地方。

    5. 您可能会发现,在不可能进行构造函数注入的场景中(例如静态方法),您需要当前时间。您要么必须重构以使用实例方法,要么必须将时间或时间提供程序作为参数传递给静态方法。

    有更好的选择吗?

    是的,看 Mike's answer .

    你也可以考虑 Noda Time 通过 IClock 接口,以及 SystemClock 和 FakeClock 实现。然而,这两个实现都设计为单体。它们有助于测试,但无法实现您的第二个目标,即将每个请求的时间锁定为单个值。不过,您可以编写一个实现来实现这一点。

        3
  •  2
  •   Alexei Levenkov    11 年前

    代码看起来很合理。

    缺点-对象的最有可能的生存期将由DI容器控制,因此提供程序的用户无法确保它始终正确配置(每次调用,而不是像app/sington那样更长的生存期)。

    如果您有表示“事务”的类型,那么最好将“开始”时间放在那里。

        4
  •  0
  •   David Keldsen    11 年前

    这不是可以用实时时钟、查询或测试来回答的。开发人员可能已经找到了一些难以理解的方法来实现底层库调用。。。

    所以不要这样做。依赖注入也不会拯救你;问题是,在“会话”开始时,您需要一个标准的时间模式

    在我看来,最根本的问题是你在表达一个想法,并为此寻找一种机制。正确的机制是命名它,说出名称中的意思,然后只设置一次。 readonly 是在构造函数中只处理一次设置的好方法,它允许编译器和运行时强制执行 意思是 这就是它只被设置一次。

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