Ein unveränderliches Bankkonto
<?php declare(strict_types=1);
namespace spriebsch\tfd\softwareArchitecture\eventBased\eventSourcing\immutableAccount;
use RuntimeException;
final class Account
{
public function __construct(
private readonly AccountId $id,
private readonly Currency $currency,
private Money $balance
) {
$this->ensureSameCurrency($balance);
}
public function id(): AccountId
{
return $this->id;
}
public function balance(): Money
{
return $this->balance;
}
public function currency(): Currency
{
return $this->currency;
}
private function ensureSameCurrency(Money $money): void
{
if ($this->currency !== $money->currency()) {
throw new RuntimeException('Currency mismatch');
};
}
}
Mit der AccountId
können wir das Bankkonto wieder finden beziehungsweise unter mehreren Konten eindeutig identifizieren:
<?php declare(strict_types=1);
namespace spriebsch\tfd\softwareArchitecture\eventBased\eventSourcing\immutableAccount;
final readonly class AccountId
{
public function __construct(
private string $id,
) {}
public function asString(): string
{
return $this->id;
}
}
Diese AccountId
ist sozusagen ein "technischer" Identifier.
Das hat nichts mit beispielsweise einer IBAN zu tun, die ein Bankkonto fachlich global eindeutig identifziert.
<?php declare(strict_types=1);
namespace spriebsch\tfd\softwareArchitecture\eventBased\eventSourcing\immutableAccount;
use RuntimeException;
final readonly class Money
{
public function __construct(
private int $amountInCents,
private Currency $currency
) {}
public function amountInCents(): int
{
return $this->amountInCents;
}
public function currency(): Currency
{
return $this->currency;
}
public function add(self $that): self
{
$this->ensureSameCurrency($that);
return new self($this->amountInCents + $that->amountInCents, $this->currency);
}
private function ensureSameCurrency(self $that): void
{
if ($this->currency !== $that->currency) {
throw new RuntimeException('Currency mismatch');
};
}
}
<?php declare(strict_types=1);
namespace spriebsch\tfd\softwareArchitecture\eventBased\eventSourcing\immutableAccount;
enum Currency
{
case EUR;
case GBP;
}
<?php declare(strict_types=1);
namespace spriebsch\tfd\softwareArchitecture\eventBased\eventSourcing\immutableAccount;
require __DIR__ . '/autoload.php';
$account = new Account(
new AccountId('the-id'),
Currency::EUR,
new Money(100, Currency::EUR)
);
print number_format($account->balance()->amountInCents() / 100, 2) . ' ' . $account->currency()->name . PHP_EOL;
1.00 EUR
Unser Bankkonto ist jetzt global eindeutig identifizierbar. Das bedeutet, wir können es auch tatsächlich wieder finden, nachdem wir es irgendwo gespeichert haben.
Das Konto wird in einer bestimmten Währung geführt, und wir können dort nur Geld in dieser Währung deponieren. Allerdings hat unser Konto einen Schönheitsfehler. Da es unveränderlich ist, können wir den Kontostand nach der Initialisierung nicht mehr ändern.
Das klingt nach einer Einschränkung, die unser Konto praktisch unbenutzbar macht. Streng genommen ist dem aber gar nicht so.
Wenn der Kontostand beispielsweise in einer Datenbank gespeichert ist und dort durch ein SQL-Statement verändert wird,
dann können wir Account
durchaus verwenden, um den aktuellen Kontostand im Arbeitsspeicher zu repräsentieren,
während unser PHP-Programm läuft.
Das soll natürlich nicht bedeuten, dass so etwas ein gutes Design ist.
Unser Account
-Objekt ist irgendwie passiv und unbeteiligt, weil der interessanteste Geschäftsvorfall, nämlich die Veränderung des Kontostands, darin gar nicht abgebildet ist.
Es ist kein gutes Design, wenn wir ein Geschäftsobjekt erstellen, das am wichtigsten Geschäftsvorfall nicht beteiligt ist.