Skip to content

Instantly share code, notes, and snippets.

@melihovv
Last active January 26, 2018 15:09
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save melihovv/fa6ac4784096f1902036c4e801612551 to your computer and use it in GitHub Desktop.
Save melihovv/fa6ac4784096f1902036c4e801612551 to your computer and use it in GitHub Desktop.
Domain-Driven Design in PHP
<?php
# Example of Test Data Builder
class AuthorBuilder
{
private $username;
private $email;
private $fullName;
private function __construct()
{
$this->username = new Username('johndoe');
$this->email = new Email('john@doe.com');
$this->fullName = new FullName('John', 'Doe');
}
public static function anAuthor()
{
return new self();
}
public function withFullName(FullName $aFullName)
{
$this->fullName = $aFullName;
return $this;
}
public function withUsername(Username $aUsername)
{
$this->username = $aUsername;
return $this;
}
public function withEmail(Email $anEmail)
{
$this->email = $anEmail;
return $this;
}
public function build()
{
return new Author(
$this->username,
$this->fullName,
$this->email
);
}
}
class MyTest extends PHPUnit_Framework_TestCase
{
/** @test */
public function itDoesSomething()
{
$author = AuthorBuilder::anAuthor()
->withEmail(new Email('other@email.com'))
->build();
}
}
<?php
class DoctrineSession implements TransactionalSession
{
private $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function executeAtomically(callable $operation)
{
return $this
->entityManager
->transactional($operation);
}
}
# Usage
/** @var EntityManager $em */
$nonTxApplicationService = new SignUpUserService(
$em->getRepository(
'BoundedContext\Domain\Model\User\User'
)
);
$txApplicationService = new TransactionalApplicationService(
$nonTxApplicationService,
new DoctrineSession($em)
);
$response = $txApplicationService->execute(
new SignUpUserRequest(
'user@example.com',
'password'
)
);
<?php
# In large business-critical applications, it’s quite common to have a mix of several technologies. For
# example, in read-intensive web applications, you usually have some sort of denormalized data source
# (Solr, Elasticsearch, Sphinx, etc.) that provides all the reads of the application, while a traditional
# RDBMS like MySQL or Postgres is mainly responsible for handling all the writes. When this occurs,
# one of the concerns that normally arises is whether we can have read operations go with the search
# engine and write operations go with the traditional RDBMS data source. Our general advice here is
# that these kind of situations are a smell for CQRS, since we need to scale the reads and the writes
# of the application independently. So if you can go with CQRS, that’s likely the best choice.
# But if for any reason you can’t go with CQRS, an alternative approach is needed. In this situation, the
# use of the Proxy pattern from the Gang of Four comes in handy. We can define an implementation
# of a Repository in terms of the Proxy pattern:
namespace BuyIt\Billing\Infrastructure\FullTextSearching\Elastica;
use BuyIt\Billing\Domain\Model\Order\OrderRepository;
use BuyIt\Billing\Infrastructure\Domain\Model\Order\Doctrine\DoctrineOrderRepository;
use Elastica\Client;
class ElasticaOrderRepository implements OrderRepository
{
private $client;
private $baseOrderRepository;
public function __construct(
Client $client,
DoctrineOrderRepository $baseOrderRepository
) {
$this->client = $client;
$this->baseOrderRepository = $baseOrderRepository;
}
public function find($id)
{
return $this->baseOrderRepository->find($id);
}
public function findBy(array $criteria)
{
$search = new \Elastica\Search($this->client);
// ...
return $this->toOrder($search->search());
}
public function add($anOrder)
{
// First we attempt to add it to the Elastic index
$ordersIndex = $this->client->getIndex('orders');
$orderType = $ordersIndex->getType('order');
$orderType->addDocument(
new \Elastica\Document(
$anOrder->id(),
$this->toArray($anOrder)
)
);
$ordersIndex->refresh();
// When it is done, we attempt to
// add it to the RDBMS store
$this->baseOrderRepository->add($anOrder);
}
}
<?php
# DTO example
namespace Lw\Application\Service\User;
class SignUpUserRequest
{
private $email;
private $password;
public function __construct($email, $password)
{
$this->email = $email;
$this->password = $password;
}
public function email()
{
return $this->email;
}
public function password()
{
return $this->password;
}
}
<?php
# Application service example.
namespace Lw\Application\Service\User;
use Ddd\Application\Service\ApplicationService;
use Lw\Domain\Model\User\User;
use Lw\Domain\Model\User\UserAlreadyExistsException;
use Lw\Domain\Model\User\UserRepository;
class SignUpUserService
{
private $userRepository;
public function __construct(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}
public function execute(SignUpUserRequest $request)
{
$email = $request->email();
$password = $request->password();
$user = $this->userRepository->ofEmail($email);
if ($user) {
throw new UserAlreadyExistsException();
}
$this->userRepository->add(
new User(
$this->userRepository->nextIdentity(),
$email,
$password
)
);
}
}
<?php
class TransactionalApplicationService
implements ApplicationService
{
private $session;
private $service;
public function __construct(
ApplicationService $service,
TransactionalSession $session
) {
$this->session = $session;
$this->service = $service;
}
public function execute(BaseRequest $request)
{
$operation = function () use ($request) {
return $this->service->execute($request);
};
return $this->session->executeAtomically($operation);
}
}
<?php
interface TransactionalSession
{
/**
* @return mixed
*/
public function executeAtomically(callable $operation);
}
<?php
# Factory in root aggregate example.
class User
{
// ...
public function makeWish(WishId $wishId, $email, $content)
{
$wish = new WishEmail(
$wishId,
$this->id(),
$email,
$content
);
DomainEventPublisher::instance()->publish(
new WishMade($wishId)
);
return $wish;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment