-
-
Save Rhincodon/31190c59c28a6638afde66b6c419f0a8 to your computer and use it in GitHub Desktop.
Domain-Driven Design in PHP
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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( | |
'[email protected]', | |
'password' | |
) | |
); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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 | |
) | |
); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
interface TransactionalSession | |
{ | |
/** | |
* @return mixed | |
*/ | |
public function executeAtomically(callable $operation); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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