Domain-Driven Design in a Nutshell I.
Strategic Patterns
- Bounded Context
- Ubiquitous Language
Tactical Patterns
- Onion Architecture
- Entity, Value Object
- Repository
- Services
Strategic Patterns
- Ubiquitous Language
- Bounded Context
Ubiquitous Language
"The Ubiquitous Language is a shared language developed by the team—a team comprised of both domain experts and software developers."
Ubiqutious Language
interface User
{
public function changePersonalName(string firstName, string lastName);
public function relocateTo(PostalAddress changedPostalAddress);
public function changeHomeTelephone(Telephone telephone);
public function changeMobileTelephone(Telephone telephone);
}
Bounded Context
Bounded Context
Bounded Context
Concepts can be modelled with different granularity
Bounded Context
Generic vs. Core Domain
Bounded Context
Context Mapping helps you to orient between the different models
Bounded Context
They should be the foundation of the micro-service architecture
Tactical Patterns
- Onion Architecture
- Entity, Value Object
- Repository
- Services
According to Evans, they should have been in the appendix
Tactical Patterns
They are still very useful and instructive
They foster putting business logic into the center of the software
Tactical Patterns
They can make your programs easy to unit-test
Tactical Patterns
They even give advices about performance optimization
Tactical Patterns
But in return...
Tactical Patterns
You need more layers
Tactical Patterns
You need much more classes
Tactical Patterns
You have to practice a lot
Tactical Patterns
Development time becomes longer
Tactical Patterns
But maintenance becomes shorter
Refactoring becomes less risky
Onion Architecture
Entity, Value Object
The concepts of the domain
Product
Customer
Address
Shop
Inventory
Image
PhoneNumber
Cart
Order
Article
Order
Shipping
BankAccount
Entity, Value Object
Plain Old PHP Objects (POPO)
Entity, Value Object
They only have Entity and Value Object depenencies*
Entity, Value Object
They enforce the business rules
Otherwise we talk about Anemic Domain Model
Entity, Value Object
Entity: identifier equality
VO: structural equality
class User
{
/** @var Uuid */
private $id;
/** @var string */
private $name;
// ...
}
class EmailAddress
{
/** @var string */
private $email;
// ...
}
Entity, Value Object
Entity: mutable
VO: immutable*
class User
{
public function changeName(NameChange $nameChange)
{
$this->name = $nameChange->getName();
}
}
class EmailAddress
{
public function withEmail(string $email): EmailAddress
{
$self = clone $self;
$self->email = $email;
return $self;
}
}
Entity, Value Object
PSR-7:
class ProductController
{
public function redirectToNewUrl(ServerRequestInterface $request, ResponseInterface $response)
{
$response = $response->withStatus(301);
$response = $response->withHeader("Location", "https://example.com/products/123");
return $response;
}
}
https://github.com/moneyphp/money
Money:
$jim_price = $hannah_price = Money::EUR(2500);
$coupon = Money::EUR(500);
$jim_price = $jim_price->subtract($coupon);
// $jim_price->subtract($coupon);
$jim_price->lessThan($hannah_price); // true
$jim_price->equals(Money::EUR(2000)); // true
Entity, Value Object
class User
{
/** @var EmailAddress[] */
private $emails = [];
public function addEmailAddress(NewEmailAddress $newEmail): void
{
$email = $newEmail->getEmailAddress();
if (isset($this->emails[$email->__toString()])) {
throw new EmailAddressAlreadyExists();
}
if (count($this->emails) >= 15) {
throw new TooManyEmailAddresses();
}
$this->emails[$email->__toString()] = $email;
}
public function removeEmailAddress(EmailAddress $email): void
{
if (isset($this->emails[$email->__toString()]) === false) {
throw new EmailAddressNotFound();
}
if (count($this->emails) <= 1) {
throw new TooFewEmailAddresses();
}
if ($email->isPrimary()) {
throw new EmailAddressCantBeRemoved();
}
unset($this->emails[$email->__toString()]);
}
}
Repository
Let's abstract away the persistence layer!
Repository
It will be easy to change DBMS
It will be easy to write unit tests
It will be less hard to change DBMS
Repository
"In-memory like collections"*
Repository
interface ProductRepositoryInterface
{
public function getProductBySku(string $sku): Product;
public function getProductList(ProductListing $productListing): ProductList;
public function saveProduct(Product $product); //public function addProduct(Product $product);
public function deleteProduct(Product $product); //public function removeProduct(Product $product);
}
The interface is part of the Domain Layer
Repository
class EloquentProductRepository implements ProductRepositoryInterface
{
// ...
}
class FakeProductRepository implements ProductRepositoryInterface
{
// ...
}
Implementations are part of the Infrastructure Layer
Repository
General vs. Specialized
interface ProductRepositoryInterface
{
public function getProductBySku(string $sku): Product;
// ...
}
interface RepositoryInterface
{
public function getModelById($id): AbstractModel;
// ...
}
Repository
Domain objects are returned*
Repository
You can use black magic via a Data Mapper ORM
... or Factories with the Composite Pattern
You can use an Active Record ORM
Repository
But...
Repository
How may objects would you like to get when you let's say retrieve a list of books along with their authors and publishers?
Repository
As many Entities and VOs the list has
As many different Entities and VOs the list has
Repository
Repository
Identity Map For The Win
Repository
Ensures that an object is loaded only once
Repository
-
Windows, Virtualbox, Docker
-
PHP 7 + Opcache
-
~200 KB of JSON response
-
50 main entity
-
4 relationships by entity
Without Identity Map:
With Identity Map:
Service
Application Service vs. Domain Service
Domain Service
Involves business processes
Domain Service
Combines tasks that involve multiple entities
Domain Service
Stateless
Application Service
~ Controller
Application Service
Request -> λ -> View
Request -> λ -> Response
Application Service
class AddEmailAddress extends AbstractService
{
/** @var MeRepositoryInterface */
protected $meRepository;
/** @var EmailAddressVerificationRepositoryInterface */
protected $verificationRepository;
/** @var EmailServiceInterface */
protected $emailService;
public function execute(JsonApi $jsonApi): ResponseInterface
{
$userId = $this->config->getCurrentUser()->getId();
$newEmailAddress = new NewEmailAddress($jsonApi->request->getResourceAttribute("email_address", ""));
// Add email address
$me = $this->meRepository->getMeById($userId);
$me->addEmailAddress($newEmailAddress);
$this->meRepository->saveMe($me);
// Create email verification token
$verification = EmailAddressVerification::generate($me, $newEmailAddress->getEmailAddress());
$this->verificationRepository->createVerification($verification);
// Send email verification email
$this->emailService->send(new EmailAddressVerificationEmail($verification));
// Respond with 204 No Content
return $jsonApi->respond()->noContent();
}
}
Conclusion
Why to use DDD?
Conclusion
Encourages collaboration with Domain Experts
Conclusion
Uses the power of the language
Conclusion
Makes the business logic explicit
Conclusion
"DDD: object-oriented programming done right"
What's next?
Domain Events
CQRS
Event Sourcing
Event Storming
...
Aggregates
Resources
- http://www.drdobbs.com/architecture-and-design/domain-driven-design-the-good-and-the-ch/240169117
- http://www.informit.com/articles/article.aspx?p=1944876&seqNum=3
- http://martinfowler.com/bliki/BoundedContext.html
- https://blog.codecentric.de/en/2015/04/bounded-contexts-and-data-duplication-in-action-adding-a-shop-system-to-the-movie-database/
- http://www.infoq.com/articles/ddd-contextmapping
- http://www.martinfowler.com/bliki/AnemicDomainModel.html
- http://verraes.net/2016/02/type-safety-and-money/
- http://enterprisecraftsmanship.com/2016/09/22/email-uniqueness-as-an-aggregate-invariant/
- https://coderwall.com/p/wngbng/building-an-identity-map-in-php
- https://igor.io/2013/02/03/http-foundation-value.html
- http://blog.mirkosertic.de/architecturedesign/dddexample
- http://blog.sapiensworks.com/post/2016/05/12/ddd-is-expensive-myth
Domain-Driven Design in a Nutshell I.
By Máté Kocsis
Domain-Driven Design in a Nutshell I.
- 1,233