会計トランザクション

トランザクション内の全項目の合計がゼロになるように、2つ以上の項目をリンクします。

これは、2000年代半ばに執筆していた「エンタープライズアプリケーションアーキテクチャ開発のさらなる展開」の一部です。残念ながら、それ以来、多くの他のことに気を取られており、これ以上取り組む時間がありませんでしたし、近い将来に時間を見つけることも難しいと考えています。そのため、この資料は非常に草稿段階のものであり、再び取り組むことができるようになるまで、修正や更新は行いません。

ルカ・パチョーリは、会計士にとってガリレオが物理学者であるのと同じくらい重要な人物かもしれません。彼はフランシスコ会修道士であり、会計の中核となる概念の1つである複式簿記を最初に記述しました。複式簿記の背後にある考え方は非常にシンプルで、すべての引き出しは預金でバランスを取らなければなりません。したがって、簿記で行うすべての操作には、ある勘定からの減額と別の勘定への加算という2つの要素があります。このようにして、お金は物理学者がエネルギーを保存するように保存されます。会計士にとって、お金を作り出すことはできません。それはただ移動されるだけです。

動作方法

トランザクションを使用する場合、実際には2種類のトランザクションがあります。2本立てトランザクションには、常に符号が反対の2つの項目しかありません。これは文字通り、ある勘定から別の勘定への単一の移動です。多本立てトランザクションでは任意の数の項目を許可しますが、それでもすべての項目の合計がゼロになるという全体のルールに従い、お金を保存します。

図1:2本立てトランザクションの例

図2:多本立てトランザクションのクラス図。違いは関連付けの多重度だけです:会計トランザクション -> 項目

図3:多本立てトランザクションの例。これは、2枚の小切手を1枚の預金伝票で銀行口座に入金する場合を表している可能性があります。

2本立てトランザクションは最も扱いやすいので、ビジネスがそのように機能する場合、多本立てトランザクションをサポートできるとしても、多本立てトランザクションを構築しようとする価値はありません。

2本立てトランザクションでは、項目はオプションです。トランザクションに関するすべてのデータを含めることができます。これは、2つの項目が金額の符号だけが異なる場合に機能します。私が英国に住んでいたとき、ある口座から別の口座への送金には常に3日かかりました。したがって、同じ支店内の普通預金口座から貯蓄口座に送金した場合でも、普通預金口座からの引き落としは、貯蓄口座への預金より3日前に発生します。この場合、別々の日付を記録するために項目が必要です。

図4:項目のない会計トランザクションのクラス図

多本立てトランザクションでは、トランザクションの作成方法に困難が生じる場合があります。2本立てトランザクションは1つの操作で非常に簡単に作成できます。しかし、多本立てトランザクションにはもう少し労力が必要です。したがって、適切な勘定に正しく転記する前にトランザクションを構築するには、Proposed Objectを使用する価値があります。

いつ使用するのか

これに対する答えとして、複式簿記がそもそもなぜそれほど良い考えと見なされたのかを考える価値があります。基本的に、それはすべて、漏れを見つけて防ぐこと、つまり不正行為と戦うことにあります。複式簿記がなければ、お金が謎めいた形で出現したり消えたりすることを簡単に許してしまう可能性があります。もちろん、複式簿記ですべての不正行為を排除できるわけではありませんが、見つけることを少し簡単にするため、人々はそれを利用しています。実際、会計の組織構造に深く根付いているため、人々はそれを意識せずに使用しています。

これは、常に会計トランザクションを使用する必要があるという意味ではありません。多くの点で、このパターンの使用は、ドメイン内の人々がそのパターンを使用するかどうかによって異なります。まず、会計トランザクションを使用するのは、Accountを使用する場合にのみ意味があります。そのため、Accountを使用しない場合は、会計トランザクションも使用しません。

会計トランザクションを使用しないもう1つの理由は、すべての項目がコンピューターによって作成される場合です。これのログとトレーサビリティは、すべての漏れ追跡の願望を満たす可能性があります。ソースコードとデータベースログを調べることができるため、十分なレバレッジが得られます。 会計トランザクションを使用しても、それほど多くのメリットはありません。

そのため、パターンの使用時期はドメインエキスパートによって決定されるべきです。特に、ドメインエキスパートが必要と感じていない場合に、追加機能として使用しないでください。多くのパターンと同様に、会計トランザクションはシステムに複雑さを加え、複雑さは独自の代償を伴います。

2本立てか多本立てか?

会計トランザクションを使用することにした場合、2本立てバージョンと多本立てバージョンのどちらを使用するかを決定する必要があります。多本立てトランザクションにより、単一の預金が多数の引き出しの合計になる場合、またはその逆の場合の項目をサポートする柔軟性が向上します。しかし、多くのアプリケーションでは、ドメインに2本立てトランザクションしかないため、それを望んでいません。多本立てトランザクションもかなり複雑です。そのため、機能が絶対に必要な場合にのみ、多本立てトランザクションを使用してください。

多本立てトランザクションで2本立てトランザクションをサポートするのは簡単なので、後で一方からもう一方にリファクタリングするのは通常非常に簡単です。そのため、最初は2本立てから始めて、後で多本立てに変更するのは簡単です。逆も非常に簡単ですが、よく分からなければ、単純なものから始める方が良いでしょう。

例:2本立ての例(Java)

2本立てと多本立ての両方のケースのサンプルコードを示します。まずは簡単な2本立てのケースから始めます。

実際、2本立てのケースでは、単純な会計トランザクションオブジェクトが必要です。

public class AccountingTransaction {
  private Collection entries = new HashSet();
  public AccountingTransaction(Money amount, Account from, Account to, MfDate date) {
    Entry fromEntry = new Entry (amount.negate(), date);
    from.addEntry(fromEntry);
    entries.add(fromEntry);
    Entry toEntry = new Entry (amount, date);
    to.addEntry(toEntry);
    entries.add(toEntry);
  }

これにより、トランザクション処理以外の方法で項目を作成できないように、項目のコンストラクターを制限する必要があります。Javaでは、これはコンストラクターのパッケージアクセスとコーディング規約の組み合わせです。

会計トランザクションコンストラクターを直接使用するのではなく、勘定オブジェクトに適切なメソッドを提供することが理にかなっています。

  void withdraw(Money amount, Account target, MfDate date) {
    new AccountingTransaction (amount, this, target, date);
  }

これにより、操作のコードがはるかに扱いやすくなります。

public void testBalanceUsingTransactions() {
  revenue = new Account(Currency.USD);
  deferred = new Account(Currency.USD);
  receivables = new Account(Currency.USD);
  revenue.withdraw(Money.dollars(500), receivables, new MfDate(1,4,99));
  revenue.withdraw(Money.dollars(200), deferred, new MfDate(1,4,99));
  assertEquals(Money.dollars(500), receivables.balance());
  assertEquals(Money.dollars(200), deferred.balance());
  assertEquals(Money.dollars(-700), revenue.balance());
}

例:多本立ての例(Java)

多本立てトランザクションの作成には労力がかかり、検証が必要なため、多本立てのケースははるかに厄介です。この場合、Proposed Objectを使用して、トランザクションを徐々に組み立て、すべての部品が揃ったら勘定に転記します。

このアプローチでは、個別のメソッド呼び出しを通じてトランザクションに項目を追加できる必要があります。すべてのトランザクションが揃ったら、トランザクションを勘定に転記できます。転記する前に、すべての項目の合計がゼロになることを確認する必要があります。転記したら、トランザクションに項目を追加することはできなくなります。

フィールドとコンストラクターからコードを公開します。

public class AccountingTransaction {
    private MfDate date;
    private Collection entries = new HashSet();
    private boolean wasPosted = false;
    public AccountingTransaction(MfDate date) {
    this.date = date;
    }

この例では、トランザクション全体に1つの日付を使用しています。これは古い英国の銀行では対応できませんが、説明を少し簡単にすることができます。

addメソッドは、トランザクションがまだ転記されていない場合に、トランザクションに項目を追加します。

class Transaction...
  public void add (Money amount, Account account) {
    if (wasPosted) throw new ImmutableTransactionException
          ("cannot add entry to a transaction that's already posted");
    entries.add(new Entry (amount, date, account, this));
  }

この場合、項目とトランザクションと勘定の両方との間の双方向の関連付けを維持するために、異なる項目クラスを使用しています。(項目は不変なので、双方向リンクの維持がはるかに簡単になります。)

class Entry...
    private Money amount;
    private MfDate date;
    private Account account;
    private AccountingTransaction transaction;
    Entry(Money amount, MfDate date, Account account, AccountingTransaction transaction) {
    // only used by AccountingTransaction
    this.amount = amount;
    this.date = date;
    this.account = account;
    this.transaction = transaction;
    }

トランザクションに項目を追加したら、トランザクションを転記できます。

class AccountingTransaction...
  public void post() {
    if (!canPost())
      throw new UnableToPostException();
    Iterator it = entries.iterator();
    while (it.hasNext()) {
      Entry each = (Entry) it.next();
      each.post();
    }
    wasPosted = true;
    }
    public boolean canPost(){
    return balance().isZero();
    }
    private Money balance() {
    if (entries.isEmpty()) return Money.dollars(0);
    Iterator it = entries.iterator();
    Entry firstEntry = (Entry) it.next();
    Money result = firstEntry.amount();
    while (it.hasNext()) {
      Entry each = (Entry) it.next();
      result = result.add(each.amount());
    }
    return result;
    }
class Entry...
  void post() {
    // only used by AccountingTransaction
    account.addEntry(this);
    }

その後、次のようなコードでトランザクションを使用できます。

    AccountingTransaction multi = new AccountingTransaction(new MfDate(2000,1,4));
    multi.add(Money.dollars(-700), revenue);
    multi.add(Money.dollars(500), receivables);
    multi.add(Money.dollars(200), deferred);
    multi.post();
    assertEquals(Money.dollars(500), receivables.balance());
    assertEquals(Money.dollars(200), deferred.balance());
    assertEquals(Money.dollars(-700), revenue.balance());

このような設定と転記の手順は、多本立てトランザクションの使用が非常に厄介である理由を示しています。良いニュースは、一部の時間に2本立てトランザクションしか必要ない場合、多本立てトランザクションを使用して2本立てインターフェースを実装できることです。

class Account...
  void withdraw(Money amount, Account target, MfDate date) {
    AccountingTransaction trans = new AccountingTransaction(date);
    trans.add(amount.negate(), this);
    trans.add(amount, target);
    trans.post();
    }