反向调整

2006年1月2日

这是我在2000年代中期进行的进一步的企业应用程序架构开发写作的一部分。不幸的是,太多其他事情吸引了我的注意力,所以我没有时间进一步研究它们,而且在可预见的将来我也没有看到太多时间。因此,这些材料非常草稿形式,我不会进行任何更正或更新,直到我能找到时间再次处理它。

工作原理

对于每个需要调整的条目,您创建两个新条目。一个条目是对先前条目的简单反转,使用相同的发生日期和相同的金额,但符号相反。然后,您发布一个新的条目,该条目是旧金额应该是什么。

假设我们有 3 月份 50kwh 小时的用电量记录。此事件最初记录在 4 月 5 日,并在 4 月 10 日处理。这产生了 图 1中的对象。

图 1:调整前的事件。

然后在 6 月 1 日,我们意识到犯了一个错误,金额应该是 80 kwh。这导致了 图 2的结构。

您可能会惊讶地发现,反转条目出现在旧事件上,而不是新事件上。这是因为新事件本身可能会在以后被调整,在这种情况下,我们不想生成反转反转条目的条目。因此,将它们放在原始事件上更有意义。我们还将该事件标记为已调整,以明确表明它已被调整,因此不应再次调整。

图 2:调整后的事件

使用反向调整会导致这些反转对中出现大量条目。这意味着,如果任何人想查看条目列表,他们通常会想要查看排除所有反转对的条目。因此,您需要提供一个过滤此信息的查询方法。由于可能涉及大量数据,这通常也会影响您的数据库查询。

何时使用它

反向调整是条目不可变时的简单替代方案,因此您无法使用替换调整。主要缺点是它会导致大量条目:完成后,您将拥有之前每个条目的三个条目。这样做几次,您最终会得到很多条目,其中许多是反转对。虽然您可以过滤掉它,但它们仍然很麻烦。

差异调整是主要的替代方案。通常,它们之间的选择将取决于用户希望如何查看信息。如果条目是可变的,那么最好使用替换调整。通常,您会发现条目在某个日期之前是可变的,之后是不可变的。这将导致您将反向调整替换调整结合起来。

示例:反转不正确的用电量(Java)

这是我们关于不正确的用电量以及如何反转它的示例。

public void setUp() {
    MfDate.setToday(2004,4,1);
    watson = new Customer("Dr Watson");
    watson.setServiceAgreement(testAgreement());
    usageEvent = new Usage(Unit.KWH.amount(50),
                 new MfDate(2004, 3, 31),
                 watson);
    eventList.add(usageEvent);
    eventList.process();
    MfDate.setToday(2004,6,1);
    replacement = new Usage(Unit.KWH.amount(70),
                    new MfDate(2004, 3, 31),
                    watson,
                    usageEvent);
    eventList.add(replacement);
    eventList.process();
}

在此实现中,我们为我们的会计事件提供了一个构造函数,其中包含一个用于调整事件的插槽。

class AccountingEvent...

  public AccountingEvent(EventType type, MfDate whenOccurred, Subject subject, AccountingEvent adjustedEvent) {
      this.type = type;
      this.whenOccurred = whenOccurred;
      this.whenNoticed = MfDate.today();
      this.subject = subject;
      this.adjustedEvent = adjustedEvent;
      adjustedEvent.setReplacementEvent(this);
  }
private AccountingEvent adjustedEvent;

在处理过程中,如果存在调整后的事件,则首先将其反转。反转只是为每个原始条目发布一个相反的条目。然后,事件处理的其余部分可以照常进行。

class AccountingEvent...

  public void process() {
      assert !isProcessed;
      if (adjustedEvent != null) adjustedEvent.reverse();
      subject.process(this);
      markProcessed();
  }
public void reverse() {
    assert isProcessed();
    for (Entry e : getResultingEntries()) reverseEntry(e);
    for(AccountingEvent ev : getSecondaryEvents()) ev.reverse();
}

public void reverseEntry(Entry arg) {
    Entry reversingEntry = new Entry(arg.getAmount().negate(), arg.getDate());
    Account targetAccount = subject.accountFor(arg.getAccount().type());
    targetAccount.post(reversingEntry);
 }