Tworzenie pierwszego agregatu z użyciem pakietu Spatie Event Sourcing

Agregat to klasa, która podejmuje decyzje oparte na przeszłych zdarzeniach.

Tworzenie Agregatu

Najprostszym sposobem utworzenia korzenia agregatu będzie użycie polecenia make:aggregate:

				
					php artisan make:aggregate AccountAggregate

				
			

Spowoduje to utworzenie klasy takiej jak ta:

				
					namespace App\Aggregates;

use Spatie\EventSourcing\AggregateRoots\AggregateRoot;

class AccountAggregate extends AggregateRoot
{
}

				
			

Rejestracja Zdarzeń

Możesz dodać dowolne metody lub zmienne, których potrzebujesz w agregacie. Aby przyswoić sobie modelowanie zdarzeń za pomocą agregatów, zaimplementujemy mały fragment przykładowej aplikacji Larabank. Dodamy metody do rejestrowania zdarzeń AccountCreated, MoneyAdded i MoneySubtracted.

Najpierw dodajmy metodę createAccount do naszego agregatu, która zarejestruje zdarzenie AccountCreated.

				
					namespace App\Aggregates;

use Spatie\EventSourcing\AggregateRoots\AggregateRoot;

class AccountAggregate extends AggregateRoot
{
    public function createAccount(string $name, string $userId)
    {
        $this->recordThat(new AccountCreated($name, $userId));
        
        return $this;
    }

    public function addMoney(int $amount)
    {
        $this->recordThat(new MoneyAdded($amount));
        
        return $this;
    }

    public function subtractAmount(int $amount)
    {
        $this->recordThat(new MoneySubtracted($amount));
        
        return $this;
    }

    public function deleteAccount()
    {
        $this->recordThat(new AccountDeleted());
        
        return $this;
    }
}

				
			

Funkcja recordThat nie zapisuje zdarzeń w bazie danych, tylko przechowuje je w pamięci. Zdarzenia zostaną zapisane do bazy danych, gdy sam agregat zostanie zapisany.

Warto zwrócić uwagę na dwie rzeczy. Po pierwsze, nazwy metod są zapisywane w czasie teraźniejszym, a nie przeszłym. Drugą rzeczą, na którą należy zwrócić uwagę, jest to, że ani metoda, ani zdarzenie nie zawierają identyfikatora uuid. Agregat sam wie, jakiego uuid należy użyć, ponieważ jest przekazywany do metody pobierania.

Dzięki temu możesz użyć agregatu w następujący sposób:

				
					AccountAggregate::retrieve($uuid)
    ->createAccount('my account', auth()->user()->id)
    ->persist();
    
AccountAggregate::retrieve($uuid)
    ->addMoney(123)
    ->persist();
    
AccountAggregate::retrieve($uuid)
    ->subtractMoney(456)
    ->persist();
				
			

Kiedy zapisujemy agregat, wszystkie nowo zarejestrowane zdarzenia wewnątrz korzenia agregatu zostaną zapisane w bazie danych. Nowo zarejestrowane zdarzenia zostaną również przekazane do wszystkich projektorów i reaktorów, które nasłuchują na nie.

Implementacja Pierwszej Reguły Biznesowej

Teraz, kiedy już zrozumieliśmy podstawy tworzenia agregatów, przejdźmy do implementacji pierwszej reguły biznesowej: konto nie może spaść poniżej -$5000. Gdy pobieramy agregat, wszystkie zdarzenia dla danego UUID zostaną pobrane i przekazane do odpowiednich metod na agregacie.

Obsługa Salda Konta

Aby kontrolować saldo konta, musimy utrzymywać je w pamięci agregatu. W naszym agregacie dodajemy prywatną zmienną $balance, która będzie przechowywać saldo konta. Następnie tworzymy metody applyMoneyAdded i applyMoneySubtracted, które będą aktualizować saldo na podstawie odpowiednich zdarzeń.

				
					// w naszym agregacie

private $balance = 0;

//...

public function applyMoneyAdded(MoneyAdded $event)
{
    $this->balance += $event->amount;
}

public function applyMoneySubtracted(MoneySubtracted $event)
{
    $this->balance -= $event->amount;
}

				
			

Teraz, gdy mamy saldo konta w pamięci agregatu, możemy dodać prostą kontrolę do metody subtractAmount, aby zapobiec zarejestrowaniu zdarzenia, jeśli saldo konta spadnie poniżej -$5000.

Kontrola Salda Konta

W metodzie subtractAmount dodajemy warunek, który sprawdza, czy po odjęciu określonej kwoty saldo konta nie spadnie poniżej -$5000. Jeśli warunek nie zostanie spełniony, rzucamy wyjątek informujący o braku wystarczających środków na koncie.

				
					public function subtractAmount(int $amount)
{
    if (! $this->hasSufficientFundsToSubtractAmount($amount)) {
        throw CouldNotSubtractMoney::notEnoughFunds($amount);
    }

    $this->recordThat(new MoneySubtracted($amount));
}

private function hasSufficientFundsToSubtractAmount(int $amount): bool
{
    return $this->balance - $amount >= $this->accountLimit;
}

				
			

Teraz nasz agregat jest w stanie obsłużyć pierwszą regułę biznesową dotyczącą minimalnego salda konta. Dzięki zastosowaniu tej reguły możemy zapewnić, że saldo konta nie spadnie poniżej określonej wartości, co jest istotne dla stabilności finansowej użytkowników.

Implementacja Kolejnej Reguły Biznesowej

Możemy pójść o krok dalej. Możesz również zarejestrować zdarzenie, że osiągnięto limit konta.

				
					public function subtractAmount(int $amount)
{
    if (! $this->hasSufficientFundsToSubtractAmount($amount)) {
        $this->recordThat(new AccountLimitHit($amount));

        // zapisz agregat, aby zdarzenie zostało zarejestrowane
        $this->persist();

        throw CouldNotSubtractMoney::notEnoughFunds($amount);
    }

    $this->recordThat(new MoneySubtracted($amount));
}

				
			

Teraz dodajmy nową regułę biznesową. Gdy ktoś trzy razy przekroczy limit, powinna zostać wysłana propozycja pożyczki.

				
					private $accountLimitHitCount = 0;

// musimy dodać tę metodę, aby zliczyć ile razy limit został przekroczony
public function applyAccountLimitHit()
{
    $this->accountLimitHitCount++;
}

public function subtractAmount(int $amount)
{
    if (! $this->hasSufficientFundsToSubtractAmount($amount)) {
        $this->recordThat(new AccountLimitHit($amount));

        if ($this->accountLimitHitCount === 3) {
            $this->recordThat(new LoanProposed());
        }

        // zapisz agregat, aby zdarzenia zostały zarejestrowane
        $this->persist();

        throw CouldNotSubtractMoney::notEnoughFunds($amount);
    }

    $this->recordThat(new MoneySubtracted($amount));
}

				
			

Gdy limit zostanie przekroczony trzy razy, zarejestrujemy kolejne zdarzenie LoanProposed. Możemy skonfigurować reaktor, który nasłuchuje tego zdarzenia i wysyła faktyczną wiadomość e-mail.