diff --git a/app/AggregateHistory.php b/app/AggregateHistory.php
new file mode 100644
index 0000000000000000000000000000000000000000..11c34167f516dc52384fcf8edf3ea790b9ba09b2
--- /dev/null
+++ b/app/AggregateHistory.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * Collection of domain events
+ */
+class AggregateHistory extends ArrayObject {
+
+	private $aggregateId;
+
+	public function __construct($aggregateId, DomainEvents $domainEvents)
+	{
+		$this->aggregateId = $aggregateId;
+
+		// type check for IDomainEvent[]
+		array_filter((array) $domainEvents, function(IDomainEvent $domainEvent) {});
+
+		parent::__construct((array) $domainEvents);
+	}
+
+	/**
+	 * @return array|null|object
+	 */
+	public function getAggregateId()
+	{
+		return $this->aggregateId;
+	}
+
+
+}
\ No newline at end of file
diff --git a/app/aggreagates/AbstractAggregate.php b/app/aggreagates/AbstractAggregate.php
new file mode 100644
index 0000000000000000000000000000000000000000..5ed490db8918a7a8cb02326fac5021bc70215f0e
--- /dev/null
+++ b/app/aggreagates/AbstractAggregate.php
@@ -0,0 +1,35 @@
+<?php
+
+abstract class AbstractAggregate
+{
+
+	/**
+	 * @param \IDomainEvent $domainEvent
+	 * @return string
+	 */
+	private function getApplyMethodForDomainEvent(IDomainEvent $domainEvent)
+	{
+		return "apply" . get_class($domainEvent);
+	}
+
+	/**
+	 * Apply domain event if this aggregate accepts this event
+	 * @param \IDomainEvent $domainEvent
+	 * @internal
+	 */
+	public function applyIfAccepts(IDomainEvent $domainEvent) {
+		if(method_exists($this, $this->getApplyMethodForDomainEvent($domainEvent))) {
+			$this->apply($domainEvent);
+		}
+	}
+
+	/**
+	 * Apply domain event; if objects does not accepts this event -> fail
+	 * @param \IDomainEvent $domainEvent
+	 */
+	public function apply(IDomainEvent $domainEvent) {
+		$method = $this->getApplyMethodForDomainEvent($domainEvent);
+		$this->$method($domainEvent);
+	}
+
+}
\ No newline at end of file
diff --git a/app/aggreagates/Basket.php b/app/aggreagates/Basket.php
index 2886688d9f447d1371ba66803bec49efef51903e..2fe9830f42877d0a3363acf269ed4d337d2f9b86 100644
--- a/app/aggreagates/Basket.php
+++ b/app/aggreagates/Basket.php
@@ -1,6 +1,6 @@
 <?php
 
-final class Basket implements RecordsEvents
+final class Basket extends AbstractAggregate implements RecordsEvents
 {
 
 	/** @var BasketId $basketId */
@@ -29,6 +29,17 @@ final class Basket implements RecordsEvents
 		return $basket;
 	}
 
+	public static function reconstituteFrom(AggregateHistory $aggregateHistory)
+	{
+		$basketId = $aggregateHistory->getAggregateId();
+		$basket = new static($basketId);
+
+		foreach($aggregateHistory as $event) {
+			$basket->apply($event);
+		}
+		return $basket;
+	}
+
 	// ------------- public interface --------------
 	public function addProduct(ProductId $productId, $name)
 	{
@@ -40,11 +51,7 @@ final class Basket implements RecordsEvents
 			new ProductWasAddedToBasket($this->basketId, $productId, $name)
 		);
 
-		// Update internal tracked state
-		if(!$this->isProductInBasket($productId)) {
-			$this->itemsCountById[(string)$productId] = 0;
-		}
-		$this->itemsCountById[(string)$productId]++;
+		// Internal state NOT changed directly @see apply() method
 	}
 
 	public function removeProduct(ProductId $productId)
@@ -59,8 +66,34 @@ final class Basket implements RecordsEvents
 			new ProductWasRemovedFromBasket($this->basketId, $productId)
 		);
 
-		// Update internal tracked state
-		$this->itemsCountById[(string)$productId]--;
+		// Internal state NOT changed directly @see apply() method
+	}
+
+	// --------- object state transitions based on incoming events ---------
+
+	/**
+	 * @param \ProductWasAddedToBasket $productWasAddedToBasket
+	 */
+	public function applyProductWasAddedToBasket(ProductWasAddedToBasket $productWasAddedToBasket)
+	{
+		$productId = $productWasAddedToBasket->getProductId();
+		if (!$this->isProductInBasket($productId)) {
+			$this->itemsCountById[(string) $productId] = 0;
+		}
+		$this->itemsCountById[(string) $productId]++;
+	}
+
+	/**
+	 * @param \ProductWasRemovedFromBasket $productWasRemovedFromBasket
+	 */
+	public function applyProductWasRemovedFromBasket(ProductWasRemovedFromBasket $productWasRemovedFromBasket)
+	{
+		$productId = $productWasRemovedFromBasket->getProductId();
+		$this->itemsCountById[(string) $productId]--;
+	}
+
+	public function applyBasketWasPickedUp()
+	{
 	}
 
 	// ------------ guarding helpers --------------------
@@ -93,6 +126,7 @@ final class Basket implements RecordsEvents
 	private function recordThat(IDomainEvent $domainEvent)
 	{
 		$this->recordedEvents[] = $domainEvent;
+		$this->apply($domainEvent);
 	}
 
 }
\ No newline at end of file