DDD é primariamente sobre modelar uma Linguagem Ubíqua em um Contexto Limitado explícito.
Domain-Driven Design Distilled
Exemplo de entidade.
class Customer
{
/** @var string */
private $name;
/** @var EmailAddress */
private $email;
/** @var CustomerStatus **/
private $status;
}
class Customer
{
private $id;
private $name;
private $email;
private $status;
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
public function setStatus(CustomerStatus $status): self
{
$this->status = $status;
return $this;
}}
Adicionando comportamento 1/2
class Customer
{
private $id;
private $name;
private $email;
private $status;
public function __construct(
string $name,
string $email
) {
$this->name = $name;
$this->email = $email;
}
// ...
public function updateEmail(string $newEmail) : void
{
$event = new EmailChanged($this->id, $this->email, $newEmail);
$dispatcher->dispatch(EmailChanged::NAME, $event);
$this->email = $newEmail;
}
public function getEmail(): string
{
return $this->email;
}
}
Adicionando comportamento 2/2
class Customer
{
// ...
/** @var int whether or not the customer is approved */
private $status;
public function __construct(
string $name,
string $email
) {
$this->name = $name;
$this->email = $email;
$this->status = 0;
}
// ...
public function updateEmail(string $newEmail) : void
{
$event = new EmailChanged($this->id, $this->email, $newEmail);
$dispatcher->dispatch(EmailChanged::NAME, $event);
$this->email = $newEmail;
}
public function approve() : void
{
$this->status = 1;
}
public function getStatus() : int
{
return $this->status;
}
}
/**
* Representa um endereço de e-mail tratado e válido.
*/
class EmailAddress
{
/** @var string */
private $emailAddress;
/** @throws \InvalidArgumentException */
public function __construct(string $emailAddress)
{
// remove characters that are considered spaces.
$formatted = mb_ereg_replace('\s', '', $emailAddress);
// remove dot at the end of the email address (as it is a common mistake).
$formatted = rtrim($formatted, '.');
if (!filter_var($formatted, FILTER_VALIDATE_EMAIL)) {
throw new \InvalidArgumentException(sprintf("Value '%s' is not a email address.", $emailAddress));
}
$this->emailAddress = $formatted;
}
public function getEmailAddress() : string
{
return $this->emailAddress;
}
public function equals($other) : bool
{
if (! $other instanceof self) {
return false;
}
return $this->getEmailAddress() === $other->getEmailAddress();
}}
Refactor 1/2
class Customer
{
// ...
/** @var EmailAddress */
private $email;
public function __construct(
string $name,
EmailAddress $email
) {
$this->name = $name;
$this->email = $email;
$this->status = 0;
}
// ...
public function updateEmail(EmailAddress $newEmail) : void
{
if (!$this->email->equals($newEmail)) {
$event = new EmailChanged($this->id, $this->email, $newEmail);
$dispatcher->dispatch(EmailChanged::NAME, $event);
$this->email = $newEmail;
}
}
public function getEmail(): EmailAddress
{
return $this->email;
}}
/**
* Contem todos os status que um Customer pode ter.
* @method static CustomerStatus WAITING_CREATION_APPROVAL()
* @method static CustomerStatus ACTIVE()
*/
class CustomerStatus extends \MyCLabs\Enum\Enum
{
private const WAITING_CREATION_APPROVAL = 10;
private const ACTIVE = 20;
public function getName() : string
{
return mb_convert_case($this->getKey(), MB_CASE_TITLE);
}
public function getDisplayName() : string
{
$values = [
10 => 'Aguardando aprovação',
20 => 'Ativo',
];
return $values[$this->getValue()];
}
}
Refactor 2/2
class Customer
{
// ...
/** @var CustomerStatus */
private $status;
public function __construct(
string $name,
string $email
) {
$this->name = $name;
$this->email = $email;
$this->status = CustomerStatus::WAITING_CREATION_APPROVAL();
}
public function approve() : void
{
if (!$this->status->equals(CustomerStatus::ACTIVE())) {
$this->status = CustomerStatus::ACTIVE();
$event = new CustomerApproved($this->id);
$dispatcher->dispatch(CustomerApproved::NAME, $event);
}
}
public function getStatus() : CustomerStatus
{
return $this->status;
}
}
class Customer
{
// ...
/** @var int */
private $id;
public function __construct(
string $name,
string $email
) {
$this->name = $name;
$this->email = $email;
$this->status = CustomerStatus::WAITING_CREATION_APPROVAL();
}
class Customer
{
// ...
/** @var CustomerId */
private $id;
public function __construct(
CustomerId $customerId, string $name,
string $email
) {
$this->id = $customerId;
$this->name = $name;
$this->email = $email;
$this->status = CustomerStatus::WAITING_CREATION_APPROVAL();
}
class CustomerId
{
/** @var string */
private $id;
public function __construct(?string $id = null)
{
$this->id = $id ?? \Ramsey\Uuid\Uuid:uuid4()->toString();
}
public function id() : string
{
return $this->id;
}
/**
* @param CustomerId $object
*/
public function equals($object) : bool
{
if (!$object instanceof self) {
return false;
}
return $this->id() === $object->id();
}
}
class Order
{
// ...
public function approve() : void
{
if (!$this->status->equals(OrderStatus::APPROVED())) {
$this->status = OrderStatus::APPROVED();
$event = new OrderApproved($this->id);
$dispatcher->dispatch(OrderApproved::NAME, $event);
}
}
}
Faz a mediação entre o domínio e as camadas de mapeamento de dados utilizando uma interface do tipo coleção para acessar os objetos de domínio.
interface CustomerRepository
{
public function ofCustomerId(CustomerId $customerId) : ?Customer;
public function ofEmail(EmailAddress $email) : ?Customer;
public function add(Customer $customer) : void;
public function nextIdentity() : CustomerId;
}
class DoctrineCustomerRepository extends ServiceEntityRepository implements CustomerRepository
{
public function __construct(RegistryInterface $registry)
{
parent::__construct($registry, Customer::class);
}
public function ofId(int $id) : ?Customer
{
return $this->find($id);
}
public function ofCustomerId(CustomerId $customerId) : ?Customer
{
return $this->findOneBy(['customerId' => $customerId]);
}
public function ofEmail(EmailAddress $email) : ?Customer
{
return $this->findOneBy(['email' => $email]);
}
}
class InMemoryCustomerRepository implements CustomerRepository
{
/** @var Customer[] */
private $customers;
public function __construct()
{
$this->customers = [
new Customer(new CustomerId(), 'FooBar', new EmailAddress('foo@bar.com')),
new Customer(new CustomerId(), 'FooBarzinho', new EmailAddress('foo@barzinho.com')),
];
}
public function ofCustomerId(CustomerId $id) : ?Customer
{
foreach ($this->customers as $customer) {
if ($customer->getCustomerId()->equals($id)) {
return $customer;
}
}
return null;
}
}
http://verraes.net/2013/12/related-entities-vs-child-entities/