# Lesson 2: Tactical DDD

{% hint style="info" %}
Not having code for *Lesson 2?*

`git checkout lesson-2`
{% endhint %}

### Aggregate

An Aggregate is an entity or group of entities that is always kept in a consistent state.\
Aggregates are very explicitly present in the Command Model, as that is where change is initiated and business behaviour is placed.

Let's create our first *Aggregate* `Product.`

```php
namespace App\Domain\Product;

use Ecotone\Modelling\Attribute\Aggregate;
use Ecotone\Modelling\Attribute\Identifier;
use Ecotone\Modelling\Attribute\CommandHandler;
use Ecotone\Modelling\Attribute\QueryHandler;

#[Aggregate]
class Product
{
    #[Identifier]
    private int $productId;

    private int $cost;

    private function __construct(int $productId, int $cost)
    {
        $this->productId = $productId;
        $this->cost = $cost;
    }

    #[CommandHandler]
    public static function register(RegisterProductCommand $command) : self
    {
        return new self($command->getProductId(), $command->getCost());
    }

    #[QueryHandler]
    public function getCost(GetProductPriceQuery $query) : int
    {
        return $this->cost;
    }
}
```

1. **Aggregate** attribute marks class to be known as Aggregate
2. **Identifier** marks properties as identifiers of specific Aggregate instance. Each *Aggregate* must contains at least one identifier.
3. **CommandHandler** enables command handling on specific method just as we did in [Lesson 1](/tutorial-php-ddd-cqrs-event-sourcing/php-messaging-architecture.md).\
   If method is static, it's treated as a [factory method](https://en.wikipedia.org/wiki/Factory_method_pattern) and must return a new aggregate instance. Rule applies as long as we use [State-Stored Aggregate](/modelling/command-handling/state-stored-aggregate.md#state-stored-aggregate) instead of [Event Sourcing Aggregate](/modelling/event-sourcing/event-sourcing-introduction/event-sourcing-aggregates.md).
4. **QueryHandler** enables query handling on specific method just as we did in Lesson 1.

{% hint style="info" %}
If you want to known more details about *Aggregate* start with chapter [State-Stored Aggregate](/modelling/command-handling/state-stored-aggregate.md#state-stored-aggregate)
{% endhint %}

Now remove `App\Domain\Product\ProductService` as it contains handlers for the same command and query classes.\
Before we will run our test scenario, we need to register `Repository`.

{% hint style="info" %}
Usually you will mark `services` as Query Handlers not `aggregates` .However Ecotone does not block possibility to place Query Handler on *Aggregate*. It's up to you to decide.
{% endhint %}

### Repository

Repositories are used for retrieving and saving the aggregate to persistent storage.\
We will build an in-memory implementation for now.

```php
namespace App\Domain\Product;

use Ecotone\Modelling\Attribute\Repository;
use Ecotone\Modelling\StandardRepository;

 #[Repository] // 1
class InMemoryProductRepository implements StandardRepository // 2
{
    /**
     * @var Product[]
     */
    private $products = [];

    // 3
    public function canHandle(string $aggregateClassName): bool
    {
        return $aggregateClassName === Product::class;
    }

    // 4
    public function findBy(string $aggregateClassName, array $identifiers): ?object
    {
        if (!array_key_exists($identifiers["productId"], $this->products)) {
            return null;
        }

        return $this->products[$identifiers["productId"]];
    }

    // 5
    public function save(array $identifiers, object $aggregate, array $metadata, ?int $expectedVersion): void
    {
        $this->products[$identifiers["productId"]] = $aggregate;
    }
}
```

1. **Repository** attribute marks class to be known to `Ecotone` as Repository.
2. We need to implement some methods in order to allow `Ecotone` to retrieve and save Aggregate. Based on implemented interface, `Ecotone` knowns, if *Aggregate* is state-stored or event sourced.
3. **canHandle** tells which classes can be handled by this specific repository.
4. **findBy** return found aggregate instance or null. As there may be more, than single indentifier per aggregate, identifiers are array.
5. **save** saves an aggregate instance. You do not need to bother right what is `$metadata` and `$expectedVersion`.

{% hint style="info" %}
If you want to known more details about *Repository* start with chapter [Repository](/modelling/command-handling/repository.md)
{% endhint %}

{% tabs %}
{% tab title="Laravel" %}

```php
# As default auto wire of Laravel creates new service instance each time 
# service is requested from Depedency Container, we need to register 
# ProductService as singleton.

# Go to bootstrap/QuickStartProvider.php and register our ProductService

namespace Bootstrap;

use App\Domain\Product\InMemoryProductRepository;
use Illuminate\Support\ServiceProvider;

class QuickStartProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->singleton(InMemoryProductRepository::class, function(){
            return new InMemoryProductRepository();
        });
    }
(...)
```

{% endtab %}

{% tab title="Symfony" %}

```php
Everything is set up by the framework, please continue...
```

{% endtab %}

{% tab title="Lite" %}

```
Everything is set up, please continue...
```

{% endtab %}
{% endtabs %}

{% hint style="success" %}
Let's run our testing command:

```php
bin/console ecotone:quickstart
Running example...
100
Good job, scenario ran with success!
```

{% endhint %}

Have you noticed what we are missing here? Our `Event Handler` was not called, as we do not publish the `ProductWasRegistered` event anymore.

### Event Publishing

In order to automatically publish events recorded within Aggregate, we need to add method annotated with `AggregateEvents.` This will tell `Ecotone` where to get the events from.\
\
`Ecotone` comes with default implementation, that can be used as trait **WithEvents**.

```php
use Ecotone\Modelling\WithEvents;

#[Aggregate]
class Product
{
    use WithEvents;

    #[Identifier]
    private int $productId;

    private int $cost;

    private function __construct(int $productId, int $cost)
    {
        $this->productId = $productId;
        $this->cost = $cost;

        $this->recordThat(new ProductWasRegisteredEvent($productId));
    }
(...)
```

{% hint style="info" %}
You may implement your own method for returning events, if you do not want to be coupled with the framework.
{% endhint %}

{% hint style="success" %}
Let's run our testing command:
{% endhint %}

```php
bin/console ecotone:quickstart
Running example...
Product with id 1 was registered!
100
Good job, scenario ran with success!
```

{% hint style="success" %}
Congratulations, we have just finished Lesson 2.\
In this lesson we have learnt how to make use of Aggregates and Repositories.\
\
Now we will learn about Converters and Metadata
{% endhint %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.ecotone.tech/tutorial-php-ddd-cqrs-event-sourcing/php-domain-driven-design.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
