<?php // Domain Events describe things that have happened, but where do they come from? Wouldn't it be nice if we could make // the objects themselves responsible for recording whatever happened to them? We can, by implementing the // `RecordsEvents` interface. namespace Buttercup\Protects\Tests; use Buttercup\Protects\DomainEvent; use Buttercup\Protects\DomainEvents; use Buttercup\Protects\RecordsEvents; use Buttercup\Protects\Tests\Misc\ProductId; // Being good TDD'ers, let's write our tests first. $test = function () { // We pick up a Basket, add a product, and remove the product again. $basket = Basket::pickUp(BasketId::generate()); $basket->addProduct(new ProductId('TPB123'), "The Princess Bride"); $basket->removeProduct(new ProductId('TPB123')); // We'll want the recorded events to reflect that these three operations have happened. $events = $basket->getRecordedEvents(); it("should have recorded 3 events", 3 == count($events)); it("should have a BasketWasPickedUp event", $events[0] instanceof BasketWasPickedUp); it("should have a ProductWasAddedToBasket event", $events[1] instanceof ProductWasAddedToBasket); it("should have a ProductWasRemovedFromBasket event", $events[2] instanceof ProductWasRemovedFromBasket); // We'll want a way to clear the events, so that the next time we call a method on Basket, we don't get a list with // old and new events mixed in the same result. $basket->clearRecordedEvents(); it("should have no more events after clearing it", 0 == count($basket->getRecordedEvents())); }; // We'll implement the simplest Basket we can to satisfy the tests. By ignoring the PHP syntax, you can read the class // definition as **natural language**: // > A basket records events.
} /** * @param RecordsEvents $aggregate * @return void */ public function add(RecordsEvents $aggregate) { // To store the updates made to an Aggregate, we only need to commit the latest recorded events to the `EventStore`. $events = $aggregate->getRecordedEvents(); $this->eventStore->commit($events); $aggregate->clearRecordedEvents(); } /** * @param IdentifiesAggregate $aggregateId * @return Basket */ public function get(IdentifiesAggregate $aggregateId) { // Fetching a single `Basket` is extremely easy: all we need is to reconstitute it from its history! Compare // that to the complexity of traditional ORMs. $aggregateHistory = $this->eventStore->getAggregateHistoryFor($aggregateId); return Basketv4::reconstituteFrom($aggregateHistory); } } $basketId = BasketId::generate(); $basket = BasketV4::pickUp($basketId); $basket->addProduct(ProductId::fromString('TPB01'), "The Princess Bride"); $baskets = new BasketRepository(new InMemoryEventStore()); $baskets->add($basket); $reconstitutedBasket = $baskets->get($basketId); it('should reconstitute a Basket to its state after persisting it', $basket == $reconstitutedBasket);
public function __construct($basketId) { $this->basketId = (string) $basketId; } public static function fromString($string) { return new BasketId($string); } public function __toString() { return $this->basketId; } public function equals(IdentifiesAggregate $other) { return $other instanceof BasketId && $this->basketId == $other->basketId; } // A nice convention is to have a static generate() method to create a random BasketId. The example here is a bad // way to generate a UUID, so don't do this in production. Use something like https://github.com/ramsey/uuid public static function generate() { $badSampleUuid = md5(uniqid()); return new BasketId($badSampleUuid); } } // Sample usage: $basketId = BasketId::fromString('12345678-90ab-cdef-1234-567890abcedf1234'); // Casting to string gives you the original string back: it("should cast to string", (string) $basketId == '12345678-90ab-cdef-1234-567890abcedf1234'); // Testing equality: it("should equal instances with the same type and value", (new BasketId('same'))->equals(new BasketId('same'))); it("should not equal instances with a different value", !(new BasketId('other'))->equals(new BasketId('same')));