SlideShare a Scribd company logo
1 of 224
Crafting beautiful software
The start
After a while
A year later
Source: fideloper.com/hexagonal-
architecture
Let’s prevent an exponential
growth in technical debt
Problems with the PHP
industry standard
Problems with the PHP
industry standard
Lack of intention
Problems with the PHP
industry standard
Lack of intention
Heavy coupling
Problems with the PHP
industry standard
Lack of intention
Heavy coupling
Anemic domain models
$ whoami
Jorn Oomen
Freelance PHP Web developer
Good weather cyclist
linkedin.com/in/jornoomen
@jornoomen
Let’s craft beautiful software
Requirement
A user needs to be registered
class User
{
private $id;
private $name;
private $email;
// Getters and setters
}
Model
public function registerAction(Request $request) : Response
{
$form = $this->formFactory->create(UserType::class);
$form->handleRequest($request);
if ($form->isValid()) {
/** @var User $user */
$user = $form->getData();
$this->em->persist($user);
$this->em->flush();
}
return new Response(/**/);
}
Controller
Requirement
A user has to have a name and an email
public function __construct(string $name, string $email)
{
$this->setName($name);
$this->setEmail($email);
}
private function setName(string $name)
{
if ('' === $name) {
throw new InvalidArgumentException('Name is required');
}
$this->name = $name;
}
private function setEmail(string $email)
{
if ('' === $email) {
throw new InvalidArgumentException('E-mail is required');
}
$this->email = $email;
}
Model
Requirement
A user needs a valid email
private function setEmail(string $email)
{
if ('' === $email) {
throw new InvalidArgumentException('E-mail is required');
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('E-mail is invalid');
}
$this->email = $email;
}
Model
It’s becoming messy already
It’s becoming messy already
A case for the value object
final class EmailAddress
{
}
Value object
final class EmailAddress
{
public function __construct(string $email)
{
}
}
Value object
final class EmailAddress
{
public function __construct(string $email)
{
if ('' === $email) {
throw new InvalidArgumentException('E-mail is required');
}
}
}
Value object
final class EmailAddress
{
public function __construct(string $email)
{
if ('' === $email) {
throw new InvalidArgumentException('E-mail is required');
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('E-mail is invalid');
}
}
}
Value object
final class EmailAddress
{
public function __construct(string $email)
{
if ('' === $email) {
throw new InvalidArgumentException('E-mail is required');
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('E-mail is invalid');
}
$this->email = $email;
}
}
Value object
final class EmailAddress
{
public function __construct(string $email)
{
if ('' === $email) {
throw new InvalidArgumentException('E-mail is required');
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('E-mail is invalid');
}
$this->email = $email;
}
public function toString() : string;
}
Value object
final class EmailAddress
{
public function __construct(string $email)
{
if ('' === $email) {
throw new InvalidArgumentException('E-mail is required');
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('E-mail is invalid');
}
$this->email = $email;
}
public function toString() : string;
public function equals(EmailAddress $email) : bool;
}
Value object
class User
{
public function __construct(EmailAddress $email, string $name)
{
$this->setEmail($email);
$this->setName($name);
}
//[..]
}
Model
// Before
$user = $form->getData();
Controller
// Before
$user = $form->getData();
//After
$data = $form->getData();
$user = new User(new EmailAddress($data['email']), $data['name'])
Controller
Recap
Recap
Email validation is handled by the value object
Recap
Email validation is handled by the value object
Name and email are required constructor arguments
Recap
Email validation is handled by the value object
Name and email are required constructor arguments
The User model is always in a valid state
if (!empty($user->getEmail())) {
if (filter_var($user->getEmail(), FILTER_VALIDATE_EMAIL)) {
//Send an email
}
}
Before
//Send an email
After
β€œA small simple object, like money or a date range, whose
equality isn't based on identity.”
Martin Fowler
Value objects
Small logical concepts
Contain no identity
Are immutable
Equality is based on value
==
$fiveEur = MoneyMoney::EUR(500);
$tenEur = $fiveEur->add($fiveEur);
echo $fiveEur->getAmount(); // outputs 500
echo $tenEur->getAmount(); // outputs 1000
Immutability example
We have now enforced our
business rules
Everything clear?
Some observation
We are now directly using doctrine for persistence
Some observation
We are now directly using doctrine for persistence
A change of persistence would mean changing
every class where we save or retrieve the user
public function registerAction(Request $request) : Response
{
// [..]
$user = new User(new EmailAddress($data['email']), $data['name']);
$this->em->persist($user);
$this->em>flush();
}
Controller
Let’s move the persistence
out of the controller
class DoctrineUserRepository
{
//[..]
public function save(User $user)
{
$this->em->persist($user);
$this->em->flush();
}
}
Repository
public function registerAction(Request $request) : Response
{
// [..]
$user = new User(new EmailAddress($data['email']), $data['name']);
$this->userRepository->save($user);
}
Controller
Doctrine is great but we
don’t want to marry it
A switch of persistence can be done by changing a
single class
Requirement
A user needs to receive a registration confirmation
public function registerAction(Request $request)
{
if ($form->isValid()) {
// [..]
$content = $this->renderTemplate();
$message = $this->createMailMessage($content, $user);
$this->mailer->send($message);
}
}
Controller
The controller is getting too
FAT
Let’s move the notifying
out of the controller
class UserRegisteredNotifier
{
//[..]
public function notify(string $email, string $name)
{
$content = $this->renderTemplate();
$message = $this->createMailMessage($content, $email, $name);
$this->mailer->send($message);
}
}
Notifier
public function registerAction(Request $request)
{
if ($form->isValid()) {
// [..]
$this->userRegisteredNotifier->notify($data['email'], $data['name']);
}
}
Controller
So this looks better already
Everything clear?
Requirement
User registration has to be available through an API
public function registerAction(Request $request) : JsonResponse
{
}
Controller
public function registerAction(Request $request) : JsonResponse
{
$data = $this->getRequestData($request);
}
Controller
public function registerAction(Request $request) : JsonResponse
{
$data = $this->getRequestData($request);
$user = new User(new EmailAddress($data['email'], $data['name']);
$this->userRepository->save($user);
}
Controller
public function registerAction(Request $request) : JsonResponse
{
$data = $this->getRequestData($request);
$user = new User(new EmailAddress($data['email'], $data['name']);
$this->userRepository->save($user);
$this->userRegisteredNotifier->notify($data['email'], $data['name']);
}
Controller
public function registerAction(Request $request) : JsonResponse
{
$data = $this->getRequestData($request);
$user = new User(new EmailAddress($data['email'], $data['name']);
$this->userRepository->save($user);
$this->userRegisteredNotifier->notify($data['email'], $data['name']);
return new JsonResponse(['id' => $user->getId()]);
}
Controller
$this->userRepository->save($user);
$this->userRegisteredNotifier->notify($data['email'], $data['name']);
Controller
Introducing the command
pattern
class RegisterUser // A command always has a clear intention
{
public function __construct(string $email, string $name)
{
$this->email = $email;
$this->name = $name;
}
// Getters
}
Command
class RegisterUserHandler
{
//[..]
public function handle(RegisterUser $registerUser)
{
}
}
Command handler
class RegisterUserHandler
{
//[..]
public function handle(RegisterUser $registerUser)
{
$user = new User(new EmailAddress($registerUser->getEmail()), $registerUser->getName());
}
}
Command handler
class RegisterUserHandler
{
//[..]
public function handle(RegisterUser $registerUser)
{
$user = new User(new EmailAddress($registerUser->getEmail()), $registerUser->getName());
$this->userRepository->save($user);
}
}
Command handler
class RegisterUserHandler
{
//[..]
public function handle(RegisterUser $registerUser)
{
$user = new User(new EmailAddress($registerUser->getEmail()), $registerUser->getName());
$this->userRepository->save($user);
$this->userRegisteredNotifier->notify($registerUser->getEmail()), $registerUser->getName());
}
}
Command handler
public function registerAction(Request $request) : Response
{
if ($form->isValid()) {
// [..]
$data = $form->getData();
$this->registerUserHandler->handle(
new RegisterUser($data['email'], $data['name'])
);
}
}
Controller
public function registerAction(Request $request) : JsonResponse
{
// [..]
$this->registerUserHandler->handle(
new RegisterUser($data['email'], $data['name'])
);
return new JsonResponse([]);
}
Controller
Commands
Commands
Only contain a message
Commands
Only contain a message
Have a clear intention (explicit)
Commands
Only contain a message
Have a clear intention (explicit)
Are immutable
Commands
Only contain a message
Have a clear intention (explicit)
Are immutable
Command handlers never return a value
Commands and the
command bus
A command bus is a generic command handler
Commands and the
command bus
Commands and the
command bus
A command bus is a generic command handler
It receives a command and routes it to the handler
Commands and the
command bus
A command bus is a generic command handler
It receives a command and routes it to the handler
It provides the ability to add middleware
A great command bus
implementation
github.com/SimpleBus/MessageBus
public function handle($command, callable $next)
{
$this->logger->log($this->level, 'Start, [command => $command]);
$next($command);
$this->logger->log($this->level, 'Finished', [command' => $command]);
}
Logging middleware example
public function handle($command, callable $next)
{
if ($this->canBeDelayed($command)) {
$this->commandQueue->add($command);
} else {
$next($command);
}
}
Queueing middleware example
//Before
$this->registerUserHandler->handle(
new RegisterUser($data['email'], $data['name'])
);
//After
$this->commandBus->handle(
new RegisterUser($data['email'], $data['name'])
);
Controller
The command bus
Provides the ability to add middleware
The command bus
Provides the ability to add middleware
Now logs every command for us
The command bus
Provides the ability to add middleware
Now logs every command for us
Allows queueing of (slow) commands
Everything clear?
The handler is still dealing
with secondary concerns
Introducing domain events
class UserIsRegistered // An event tells us what has happened
{
public function __construct(int $userId, string $emailAddress, string $name)
{}
// Getters
}
Event
class User implements ContainsRecordedMessages
{
//[..]
}
Model
class User implements ContainsRecordedMessages
{
use PrivateMessageRecorderCapabilities;
//[..]
}
Model
class User implements ContainsRecordedMessages
{
use PrivateMessageRecorderCapabilities;
public static function register(EmailAddress $email, string $name) : self
{
}
//[..]
}
Model
class User implements ContainsRecordedMessages
{
use PrivateMessageRecorderCapabilities;
public static function register(EmailAddress $email, string $name) : self
{
$user = new self($email, $name);
}
//[..]
}
Model
class User implements ContainsRecordedMessages
{
use PrivateMessageRecorderCapabilities;
public static function register(EmailAddress $email, string $name) : self
{
$user = new self($email, $name);
$user->record(new UserIsRegistered($user->id, (string) $email, $name));
}
//[..]
}
Model
class User implements ContainsRecordedMessages
{
use PrivateMessageRecorderCapabilities;
public static function register(EmailAddress $email, string $name) : self
{
$user = new self($email, $name);
$user->record(new UserIsRegistered($user->id, (string) $email, $name));
return $user;
}
//[..]
}
Model
class RegisterUserHandler
{
//[..]
public function handle(RegisterUser $registerUser)
{
// save user
foreach ($user->recordedMessages() as $event) {
$this->eventBus->handle($event);
}
}
}
Command handler
class NotifyUserWhenUserIsRegistered
{
//[..]
public function handle(UserIsRegistered $userIsRegistered)
{
$this->userRegisteredNotifier->notify($userIsRegistered->getEmail(), $userIsRegistered->getName());
}
}
Event listener
Domain events
Domain events
Are in past tense
Domain events
Are in past tense
Are always immutable
Domain events
Are in past tense
Are always immutable
Can have zero or more listeners
So we are pretty happy now
Controller creates simple command
Mailing doesn’t clutter our code
We now have a rich user model
We now have a rich user model
It contains data
We now have a rich user model
It contains data
It contains validation
We now have a rich user model
It contains data
It contains validation
It contains behaviour
β€œObjects hide their data behind abstractions and expose
functions that operate on that data. Data structure expose
their data and have no meaningful functions.”
Robert C. Martin (uncle Bob)
Everything clear?
We are still coupled to
doctrine for our persistence
class DoctrineUserRepository
{
//[..]
public function save(User $user)
{
$this->em->persist($user);
$this->em->flush();
}
public function find(int $userId) : User
{
return $this->em->find(User::class, $userId);
}
}
Repository
We shouldn’t depend on
any persistence
implementation
We shouldn’t depend on
any persistence
implementation
A case for the dependency inversion principle
interface UserRepository
{
public function save(User $user);
public function find(int $userId) : User;
}
Repository
class DoctrineUserRepository implements UserRepositoryInterface
{
//[..]
}
Repository
class RegisterUserHandler
{
public function __construct(UserRepositoryInterface $userRepository, MessageBus $eventBus)
{
//[..]
}
}
Command handler
DI: Dependency injection
Our situation
DI: Dependency injection
IoC: Inversion of control
Our situation
DI: Dependency injection
IoC: Inversion of control
DIP: Dependency inversion principle
Our situation
Tests - InMemoryUserRepository
Decoupling provides options
Tests - InMemoryUserRepository
Development - MysqlUserRepository
Decoupling provides options
Tests - InMemoryUserRepository
Development - MysqlUserRepository
Production - WebserviceUserRepository
Decoupling provides options
Be clear about your exceptions
Some note
interface UserRepository
{
/**
* @throws UserNotFoundException
* @throws ServiceUnavailableException
*/
public function find(int $userId) : User;
//[..]
}
Repository
class DoctrineUserRepository implements UserRepositoryInterface
{
/**
* @throws DoctrineDBALExceptionConnectionException
*/
public function find(int $userId) : User;
}
class InMemoryUserRepository implements UserRepository
{
/**
* @throws RedisException
*/
public function find(int $userId) : User;
}
Repository
Don’t do this
class DoctrineUserRepository implements UserRepositoryInterface
{
/**
* @throws DoctrineDBALExceptionConnectionException
*/
public function find(int $userId) : User;
}
class InMemoryUserRepository implements UserRepository
{
/**
* @throws RedisException
*/
public function find(int $userId) : User;
}
Repository
class DoctrineUserRepository implements UserRepositoryInterface
{
public function find(UserId $userId) : User
{
try {
if ($user = $this->findById($userId)) {
return $user;
}
} catch (ConnectionException $e) {
throw ServiceUnavailableException::withOriginalException($e);
}
throw UserNotFoundException::withId($userId);
}
}
Normalize your exceptions
Repository
class DoctrineUserRepository implements UserRepositoryInterface
{
public function find(UserId $userId) : User
{
try {
if ($user = $this->findById($userId)) {
return $user;
}
} catch (ConnectionException $e) {
throw ServiceUnavailableException::withOriginalException($e);
}
throw UserNotFoundException::withId($userId);
}
}
Normalize your exceptions
The implementor now only has to worry about the exceptions
defined in the interface
Repository
The promise of a repository interface is now clear, simple and
implementation independent
Everything clear?
src/UserBundle/
β”œβ”€β”€ Command
β”œβ”€β”€ Controller
β”œβ”€β”€ Entity
β”œβ”€β”€ Event
β”œβ”€β”€ Form
β”œβ”€β”€ Notifier
β”œβ”€β”€ Repository
└── ValueObject
Let’s look at the structure
src/UserBundle/
β”œβ”€β”€ Command
β”œβ”€β”€ Controller
β”œβ”€β”€ Entity
β”œβ”€β”€ Event
β”œβ”€β”€ Form
β”œβ”€β”€ Notifier
β”œβ”€β”€ Repository
└── ValueObject
Let’s look at the structure
The domain, infrastructure and application are all
mixed in the bundle
src/UserBundle/
β”œβ”€β”€ Command
β”‚ β”œβ”€β”€ RegisterUser.php
β”‚ └── RegisterUserHandler.php
β”œβ”€β”€ Controller
β”‚ β”œβ”€β”€ RegisterUserApiController.php
β”‚ └── RegisterUserController.php
β”œβ”€β”€ Entity
β”‚ β”œβ”€β”€ User.php
β”‚ └── UserRepository.php
β”œβ”€β”€ Event
β”‚ └── UserIsRegistered.php
β”œβ”€β”€ Form
β”‚ └── UserType.php
β”œβ”€β”€ Notifier
β”‚ β”œβ”€β”€ NotifyUserWhenUserIsRegistered.php
β”‚ └── UserRegisteredNotifier.php
β”œβ”€β”€ Repository
β”‚ └── DoctrineUserRepository.php
β”œβ”€β”€ Resources/config/doctrine
β”‚ └── User.orm.yml
└── ValueObject
└── EmailAddress.php
src/UserBundle/
β”œβ”€β”€ Command
β”‚ β”œβ”€β”€ RegisterUser.php
β”‚ └── RegisterUserHandler.php
β”œβ”€β”€ Controller
β”‚ β”œβ”€β”€ RegisterUserApiController.php
β”‚ └── RegisterUserController.php
β”œβ”€β”€ Entity
β”‚ β”œβ”€β”€ User.php
β”‚ └── UserRepository.php
β”œβ”€β”€ Event
β”‚ └── UserIsRegistered.php
β”œβ”€β”€ Form
β”‚ └── UserType.php
β”œβ”€β”€ Notifier
β”‚ β”œβ”€β”€ NotifyUserWhenUserIsRegistered.php
β”‚ └── UserRegisteredNotifier.php
β”œβ”€β”€ Repository
β”‚ └── DoctrineUserRepository.php
β”œβ”€β”€ Resources/config/doctrine
β”‚ └── User.orm.yml
└── ValueObject
└── EmailAddress.php
Domain
src/UserBundle/
β”œβ”€β”€ Command
β”‚ β”œβ”€β”€ RegisterUser.php
β”‚ └── RegisterUserHandler.php
β”œβ”€β”€ Controller
β”‚ β”œβ”€β”€ RegisterUserApiController.php
β”‚ └── RegisterUserController.php
β”œβ”€β”€ Entity
β”‚ β”œβ”€β”€ User.php
β”‚ └── UserRepository.php
β”œβ”€β”€ Event
β”‚ └── UserIsRegistered.php
β”œβ”€β”€ Form
β”‚ └── UserType.php
β”œβ”€β”€ Notifier
β”‚ β”œβ”€β”€ NotifyUserWhenUserIsRegistered.php
β”‚ └── UserRegisteredNotifier.php
β”œβ”€β”€ Repository
β”‚ └── DoctrineUserRepository.php
β”œβ”€β”€ Resources/config/doctrine
β”‚ └── User.orm.yml
└── ValueObject
└── EmailAddress.php
Domain
Infrastructure
src/UserBundle/
β”œβ”€β”€ Command
β”‚ β”œβ”€β”€ RegisterUser.php
β”‚ └── RegisterUserHandler.php
β”œβ”€β”€ Controller
β”‚ β”œβ”€β”€ RegisterUserApiController.php
β”‚ └── RegisterUserController.php
β”œβ”€β”€ Entity
β”‚ β”œβ”€β”€ User.php
β”‚ └── UserRepository.php
β”œβ”€β”€ Event
β”‚ └── UserIsRegistered.php
β”œβ”€β”€ Form
β”‚ └── UserType.php
β”œβ”€β”€ Notifier
β”‚ β”œβ”€β”€ NotifyUserWhenUserIsRegistered.php
β”‚ └── UserRegisteredNotifier.php
β”œβ”€β”€ Repository
β”‚ └── DoctrineUserRepository.php
β”œβ”€β”€ Resources/config/doctrine
β”‚ └── User.orm.yml
└── ValueObject
└── EmailAddress.php
Domain
Infrastructure
Application
src/UserBundle/
β”œβ”€β”€ Command
β”‚ β”œβ”€β”€ RegisterUser.php
β”‚ └── RegisterUserHandler.php
β”œβ”€β”€ Controller
β”‚ β”œβ”€β”€ RegisterUserApiController.php
β”‚ └── RegisterUserController.php
β”œβ”€β”€ Entity
β”‚ β”œβ”€β”€ User.php
β”‚ └── UserRepository.php
β”œβ”€β”€ Event
β”‚ └── UserIsRegistered.php
β”œβ”€β”€ Form
β”‚ └── UserType.php
β”œβ”€β”€ Notifier
β”‚ β”œβ”€β”€ NotifyUserWhenUserIsRegistered.php
β”‚ └── UserRegisteredNotifier.php
β”œβ”€β”€ Repository
β”‚ └── DoctrineUserRepository.php
β”œβ”€β”€ Resources/config/doctrine
β”‚ └── User.orm.yml
└── ValueObject
└── EmailAddress.php
src/User/
└── DomainModel
└── User
β”œβ”€β”€ EmailAddress.php
β”œβ”€β”€ User.php
β”œβ”€β”€ UserIsRegistered.php
└── UserRepository.php
src/UserBundle/
β”œβ”€β”€ Command
β”‚ β”œβ”€β”€ RegisterUser.php
β”‚ └── RegisterUserHandler.php
β”œβ”€β”€ Controller
β”‚ β”œβ”€β”€ RegisterUserApiController.php
β”‚ └── RegisterUserController.php
β”œβ”€β”€ Entity
β”‚ β”œβ”€β”€ User.php
β”‚ └── UserRepository.php
β”œβ”€β”€ Event
β”‚ └── UserIsRegistered.php
β”œβ”€β”€ Form
β”‚ └── UserType.php
β”œβ”€β”€ Notifier
β”‚ β”œβ”€β”€ NotifyUserWhenUserIsRegistered.php
β”‚ └── UserRegisteredNotifier.php
β”œβ”€β”€ Repository
β”‚ └── DoctrineUserRepository.php
β”œβ”€β”€ Resources/config/doctrine
β”‚ └── User.orm.yml
└── ValueObject
└── EmailAddress.php
src/User/
β”œβ”€β”€ DomainModel
β”‚ └── User
β”‚ β”œβ”€β”€ EmailAddress.php
β”‚ β”œβ”€β”€ User.php
β”‚ β”œβ”€β”€ UserIsRegistered.php
β”‚ └── UserRepository.php
└── Infrastructure
β”œβ”€β”€ Messaging/UserRegisteredNotifier.php
└── Persistence
β”œβ”€β”€ User
β”‚ └── DoctrineUserRepository.php
└── config/doctrine
└── User.User.orm.yml
src/UserBundle/
β”œβ”€β”€ Command
β”‚ β”œβ”€β”€ RegisterUser.php
β”‚ └── RegisterUserHandler.php
β”œβ”€β”€ Controller
β”‚ β”œβ”€β”€ RegisterUserApiController.php
β”‚ └── RegisterUserController.php
β”œβ”€β”€ Entity
β”‚ β”œβ”€β”€ User.php
β”‚ └── UserRepository.php
β”œβ”€β”€ Event
β”‚ └── UserIsRegistered.php
β”œβ”€β”€ Form
β”‚ └── UserType.php
β”œβ”€β”€ Notifier
β”‚ β”œβ”€β”€ NotifyUserWhenUserIsRegistered.php
β”‚ └── UserRegisteredNotifier.php
β”œβ”€β”€ Repository
β”‚ └── DoctrineUserRepository.php
β”œβ”€β”€ Resources/config/doctrine
β”‚ └── User.orm.yml
└── ValueObject
└── EmailAddress.php
src/User/
β”œβ”€β”€ DomainModel
β”‚ └── User
β”‚ β”œβ”€β”€ EmailAddress.php
β”‚ β”œβ”€β”€ User.php
β”‚ β”œβ”€β”€ UserIsRegistered.php
β”‚ └── UserRepository.php
β”œβ”€β”€ Infrastructure
β”‚ β”œβ”€β”€ Messaging/UserRegisteredNotifier.php
β”‚ └── Persistence
β”‚ β”œβ”€β”€ User
β”‚ β”‚ └── DoctrineUserRepository.php
β”‚ └── config/doctrine
β”‚ └── User.User.orm.yml
└── Application
β”œβ”€β”€ Command
β”‚ β”œβ”€β”€ RegisterUser.php
β”‚ └── RegisterUserHandler.php
β”œβ”€β”€ Controller
β”‚ β”œβ”€β”€ RegisterUserApiController.php
β”‚ └── RegisterUserController.php
β”œβ”€β”€ Form/UserType.php
└── Messaging/NotifyUserWhenUserIsRegistered.php
We are looking pretty good
Application, domain and infrastructural concerns are
separated.
Everything clear?
Let’s test this awesome
software
public function can_register_user()
{
}
public function can_register_user()
{
$this->registerUserHandler->handle(new RegisterUser('aart.staartjes@hotmail.com', 'Aart Staartjes'));
}
public function can_register_user()
{
$this->registerUserHandler->handle(new RegisterUser('aart.staartjes@hotmail.com', 'Aart Staartjes'));
$user = $this->inMemoryUserRepository->find(1);
}
public function can_register_user()
{
$this->registerUserHandler->handle(new RegisterUser('aart.staartjes@hotmail.com', 'Aart Staartjes'));
$user = $this->inMemoryUserRepository->find(1);
$this->assertInstanceOf(User::class, $user);
$this->assertEquals(new EmailAddress('aart.staartjes@hotmail.com'), $user->getEmail());
$this->assertSame('Aart Staartjes', $user->getName());
$this->assertInstanceOf(UserIsRegistered::class, $this->eventBusMock->getRaisedEvents()[0]);
}
There was 1 error:
1) UserDomainModelUserRegisterUserTest::can_register_user
UserDomainModelExceptionUserNotFoundException: User with id 1 not found
FAILURES!
Tests: 1, Assertions: 0, Errors: 1.
public function can_register_user()
{
$this->registerUserHandler->handle(new RegisterUser('aart.staartjes@hotmail.com', 'Aart Staartjes'));
$user = $this->inMemoryUserRepository->find(1);
$this->assertInstanceOf(User::class, $user);
$this->assertEquals(new EmailAddress('aart.staartjes@hotmail.com'), $user->getEmail());
$this->assertSame('Aart Staartjes', $user->getName());
$this->assertInstanceOf(UserIsRegistered::class, $this->eventBusMock->getRaisedEvents()[0]);
}
We rely on the magic of the
persistence layer
User provides identity - The email
Unique identity options
User provides identity - The email
Persistence mechanism generates identity - Auto increment
Unique identity options
User provides identity - The email
Persistence mechanism generates identity - Auto increment
Application generates identity - UUID
Unique identity options
Let’s remove the magic
By implementing an up front id generation strategy
composer require ramsey/uuid
class User implements ContainsRecordedMessages
{
use PrivateMessageRecorderCapabilities;
public static function register(UuidInterface $id, EmailAddress $email, string $name) : self
{
$user = new self($id, $email, $name);
$user->record(new UserIsRegistered((string) $id, (string) $email, $name));
return $user;
}
//[..]
}
Model
public function can_register_user()
{
$id = Uuid::uuid4();
}
public function can_register_user()
{
$id = Uuid::uuid4();
new RegisterUser((string) $id, 'aart.staartjes@hotmail.com', 'Aart Staartjes')
}
public function can_register_user()
{
$id = Uuid::uuid4();
$this->registerUserHandler->handle(
new RegisterUser((string) $id, 'aart.staartjes@hotmail.com', 'Aart Staartjes')
);
}
public function can_register_user()
{
$id = Uuid::uuid4();
$this->registerUserHandler->handle(
new RegisterUser((string) $id, 'aart.staartjes@hotmail.com', 'Aart Staartjes')
);
$user = $this->inMemoryUserRepository->find($id);
// Assertions
}
phpunit --bootstrap=vendor/autoload.php test/
PHPUnit 5.3.2 by Sebastian Bergmann and contributors.
. 1 / 1 (100%)
Time: 125 ms, Memory: 8.00Mb
OK (1 test, 4 assertions)
Tested
But a Uuid can still be any id
We can be more explicit
final class UserId // Simply wrapper of Uuid
{
public static function createNew() : self
/**
* @throws InvalidArgumentException
*/
public static function fromString(string $id) : self
public function toString() : string;
}
Value object
Now we know exactly what
we are talking about
public function can_register_user()
{
$id = UserId::createNew();
$this->registerUserHandler->handle(
new RegisterUser((string) $id, 'aart.staartjes@hotmail.com', 'Aart Staartjes')
);
$user = $this->inMemoryUserRepository->find($id);
// Assertions
}
phpunit --bootstrap vendor/autoload.php src/JO/User/
PHPUnit 4.8.11 by Sebastian Bergmann and contributors.
.
Time: 266 ms, Memory: 5.25Mb
OK (1 test, 4 assertions)
Everything clear?
Let’s test our value objects
public function can_not_create_invalid_user_id()
{
$this->expectException(InvalidArgumentException::class);
UserId::fromString('invalid format');
}
public function can_not_create_invalid_email_address()
{
$this->expectException(InvalidArgumentException::class);
new EmailAddress('invalid format');
}
PHPUnit 5.3.2 by Sebastian Bergmann and contributors.
... 3 / 3 (100%)
Time: 120 ms, Memory: 8.00Mb
Great that’s tested
But..
public function can_not_create_invalid_user_id()
{
$this->expectException(InvalidArgumentException::class);
UserId::fromString('invalid format');
}
public function can_not_create_invalid_email_address()
{
$this->expectException(InvalidArgumentException::class);
new EmailAddress('invalid format');
}
$this->commandBus>handle(
new RegisterUser('invalid id', 'invalid email', $name = '’)
);
We still have limited control
over our exceptions
Useful domain exceptions can give us more control
namespace UserDomainModelException;
abstract class DomainException extends DomainException {}
namespace UserDomainModelException;
abstract class DomainException extends DomainException {}
class InvalidEmailAddressException extends DomainException {}
class InvalidUserIdException extends DomainException {}
class NoEmailAddressProvidedException extends DomainException {}
try {
$id = UserId::createNew();
$this->commandBus>handle(
new RegisterUser((string) $id, 'aart.staartjes@hotmail.com', 'Aart Staartjes')
);
} catch (DomainModelExceptionInvalidEmailAddressException $e) {
// Show invalid email error
}
try {
$id = UserId::createNew();
$this->commandBus>handle(
new RegisterUser((string) $id, 'aart.staartjes@hotmail.com', 'Aart Staartjes')
);
} catch (DomainModelExceptionInvalidEmailAddressException $e) {
// Show invalid email error
} catch (DomainModelExceptionDomainException $e) {
// Some domain exception occurred
}
public function can_not_create_invalid_user_id()
{
$this->expectException(InvalidUserIdException::class);
UserId::fromString('invalid format');
}
public function can_not_create_invalid_email_address()
{
$this->expectException(InvalidEmailAddressProvidedException::class);
new EmailAddress('invalid format');
}
PHPUnit 5.3.2 by Sebastian Bergmann and contributors.
... 3 / 3 (100%)
Time: 122 ms, Memory: 8.00Mb
OK (3 tests, 6 assertions)
Everything clear?
So what did we create?
Intention revealing code
What did we learn?
Intention revealing code
Testable code
What did we learn?
Intention revealing code
Testable code
Preventing the big ball of mud
What did we learn?
Intention revealing code
Testable code
Preventing the big ball of mud
Anemic domain models (anti pattern)
What did we learn?
Intention revealing code
Testable code
Preventing the big ball of mud
Anemic domain models (anti pattern)
Value objects
What did we learn?
Intention revealing code
Testable code
Preventing the big ball of mud
Anemic domain models (anti pattern)
Value objects
Decoupling from the framework
What did we learn?
What did we learn?
Writing fast tests (mocked environment)
What did we learn?
Writing fast tests (mocked environment)
Commands
What did we learn?
Writing fast tests (mocked environment)
Commands
Domain Events
What did we learn?
Writing fast tests (mocked environment)
Commands
Domain Events
Dependency inversion principle
What did we learn?
Writing fast tests (mocked environment)
Commands
Domain Events
Dependency inversion principle
Up front id generation strategy
What did we learn?
Writing fast tests (mocked environment)
Commands
Domain Events
Dependency inversion principle
Up front id generation strategy
Creating powerful domain exceptions
What did we learn?
Writing fast tests (mocked environment)
Commands
Domain Events
Dependency inversion principle
Up front id generation strategy
Creating powerful domain exceptions
Liskov substitution principle
We wrote intention revealing code. Separated the
We wrote intention revealing code. Separated the
domain, infrastructure and application. Created
We wrote intention revealing code. Separated the
domain, infrastructure and application. Created
abstractions to improve testability and flexibility. We
We wrote intention revealing code. Separated the
domain, infrastructure and application. Created
abstractions to improve testability and flexibility. We
used commands to communicate with a unified
voice. Created domain events to allow for extension
We wrote intention revealing code. Separated the
domain, infrastructure and application. Created
abstractions to improve testability and flexibility. We
used commands to communicate with a unified
voice. Created domain events to allow for extension
without cluttering the existing code. We end up with
We wrote intention revealing code. Separated the
domain, infrastructure and application. Created
abstractions to improve testability and flexibility. We
used commands to communicate with a unified
voice. Created domain events to allow for extension
without cluttering the existing code. We end up with
clear, maintainable and beautiful software.
We wrote intention revealing code. Separated the
domain, infrastructure and application. Created
abstractions to improve testability and flexibility. We
used commands to communicate with a unified
voice. Created domain events to allow for extension
without cluttering the existing code. We end up with
clear, maintainable and beautiful software.
That keeps us excited!
Questions?Questions?
Please rate!
joind.in/17557
Further reading
Used resources
http://www.slideshare.net/matthiasnoback/hexagonal-architecture-messageoriented-software-design
https://www.youtube.com/watch?v=Eg6m6mU0fH0
https://www.youtube.com/watch?v=mQsQ6QZ4dGg
https://kacper.gunia.me/blog/ddd-building-blocks-in-php-value-object
http://williamdurand.fr/2013/12/16/enforcing-data-encapsulation-with-symfony-forms/
http://simplebus.github.io/MessageBus/
http://php-and-symfony.matthiasnoback.nl/2014/06/don-t-use-annotations-in-your-controllers/
http://alistair.cockburn.us/Hexagonal+architecture
http://www.slideshare.net/cakper/2014-0407-php-spec-the-only-design-tool-you-need-4developers/117-
enablesRefactoring

More Related Content

What's hot

Min-Maxing Software Costs - Laracon EU 2015
Min-Maxing Software Costs - Laracon EU 2015Min-Maxing Software Costs - Laracon EU 2015
Min-Maxing Software Costs - Laracon EU 2015Konstantin Kudryashov
Β 
PHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4DevelopersPHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4DevelopersKacper Gunia
Β 
The IoC Hydra
The IoC HydraThe IoC Hydra
The IoC HydraKacper Gunia
Β 
β€œWriting code that lasts” … or writing code you won’t hate tomorrow. - PHPKonf
β€œWriting code that lasts” … or writing code you won’t hate tomorrow. - PHPKonfβ€œWriting code that lasts” … or writing code you won’t hate tomorrow. - PHPKonf
β€œWriting code that lasts” … or writing code you won’t hate tomorrow. - PHPKonfRafael Dohms
Β 
Design Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et PimpleDesign Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et PimpleHugo Hamon
Β 
Php unit the-mostunknownparts
Php unit the-mostunknownpartsPhp unit the-mostunknownparts
Php unit the-mostunknownpartsBastian Feder
Β 
The IoC Hydra - Dutch PHP Conference 2016
The IoC Hydra - Dutch PHP Conference 2016The IoC Hydra - Dutch PHP Conference 2016
The IoC Hydra - Dutch PHP Conference 2016Kacper Gunia
Β 
Symfony CoP: Form component
Symfony CoP: Form componentSymfony CoP: Form component
Symfony CoP: Form componentSamuel ROZE
Β 
How I started to love design patterns
How I started to love design patternsHow I started to love design patterns
How I started to love design patternsSamuel ROZE
Β 
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you needDutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you needKacper Gunia
Β 
international PHP2011_Bastian Feder_jQuery's Secrets
international PHP2011_Bastian Feder_jQuery's Secretsinternational PHP2011_Bastian Feder_jQuery's Secrets
international PHP2011_Bastian Feder_jQuery's Secretssmueller_sandsmedia
Β 
PHP Language Trivia
PHP Language TriviaPHP Language Trivia
PHP Language TriviaNikita Popov
Β 
Refactoring using Codeception
Refactoring using CodeceptionRefactoring using Codeception
Refactoring using CodeceptionJeroen van Dijk
Β 
Zero to SOLID
Zero to SOLIDZero to SOLID
Zero to SOLIDVic Metcalfe
Β 
Symfony World - Symfony components and design patterns
Symfony World - Symfony components and design patternsSymfony World - Symfony components and design patterns
Symfony World - Symfony components and design patternsŁukasz ChruΕ›ciel
Β 
Silex meets SOAP & REST
Silex meets SOAP & RESTSilex meets SOAP & REST
Silex meets SOAP & RESTHugo Hamon
Β 
PHPCon 2016: PHP7 by Witek Adamus / XSolve
PHPCon 2016: PHP7 by Witek Adamus / XSolvePHPCon 2016: PHP7 by Witek Adamus / XSolve
PHPCon 2016: PHP7 by Witek Adamus / XSolveXSolve
Β 
November Camp - Spec BDD with PHPSpec 2
November Camp - Spec BDD with PHPSpec 2November Camp - Spec BDD with PHPSpec 2
November Camp - Spec BDD with PHPSpec 2Kacper Gunia
Β 
Adding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsAdding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsSam Hennessy
Β 

What's hot (20)

Min-Maxing Software Costs
Min-Maxing Software CostsMin-Maxing Software Costs
Min-Maxing Software Costs
Β 
Min-Maxing Software Costs - Laracon EU 2015
Min-Maxing Software Costs - Laracon EU 2015Min-Maxing Software Costs - Laracon EU 2015
Min-Maxing Software Costs - Laracon EU 2015
Β 
PHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4DevelopersPHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4Developers
Β 
The IoC Hydra
The IoC HydraThe IoC Hydra
The IoC Hydra
Β 
β€œWriting code that lasts” … or writing code you won’t hate tomorrow. - PHPKonf
β€œWriting code that lasts” … or writing code you won’t hate tomorrow. - PHPKonfβ€œWriting code that lasts” … or writing code you won’t hate tomorrow. - PHPKonf
β€œWriting code that lasts” … or writing code you won’t hate tomorrow. - PHPKonf
Β 
Design Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et PimpleDesign Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et Pimple
Β 
Php unit the-mostunknownparts
Php unit the-mostunknownpartsPhp unit the-mostunknownparts
Php unit the-mostunknownparts
Β 
The IoC Hydra - Dutch PHP Conference 2016
The IoC Hydra - Dutch PHP Conference 2016The IoC Hydra - Dutch PHP Conference 2016
The IoC Hydra - Dutch PHP Conference 2016
Β 
Symfony CoP: Form component
Symfony CoP: Form componentSymfony CoP: Form component
Symfony CoP: Form component
Β 
How I started to love design patterns
How I started to love design patternsHow I started to love design patterns
How I started to love design patterns
Β 
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you needDutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Β 
international PHP2011_Bastian Feder_jQuery's Secrets
international PHP2011_Bastian Feder_jQuery's Secretsinternational PHP2011_Bastian Feder_jQuery's Secrets
international PHP2011_Bastian Feder_jQuery's Secrets
Β 
PHP Language Trivia
PHP Language TriviaPHP Language Trivia
PHP Language Trivia
Β 
Refactoring using Codeception
Refactoring using CodeceptionRefactoring using Codeception
Refactoring using Codeception
Β 
Zero to SOLID
Zero to SOLIDZero to SOLID
Zero to SOLID
Β 
Symfony World - Symfony components and design patterns
Symfony World - Symfony components and design patternsSymfony World - Symfony components and design patterns
Symfony World - Symfony components and design patterns
Β 
Silex meets SOAP & REST
Silex meets SOAP & RESTSilex meets SOAP & REST
Silex meets SOAP & REST
Β 
PHPCon 2016: PHP7 by Witek Adamus / XSolve
PHPCon 2016: PHP7 by Witek Adamus / XSolvePHPCon 2016: PHP7 by Witek Adamus / XSolve
PHPCon 2016: PHP7 by Witek Adamus / XSolve
Β 
November Camp - Spec BDD with PHPSpec 2
November Camp - Spec BDD with PHPSpec 2November Camp - Spec BDD with PHPSpec 2
November Camp - Spec BDD with PHPSpec 2
Β 
Adding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsAdding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy Applications
Β 

Similar to Crafting beautiful software

Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For BeginnersJonathan Wage
Β 
Tidy Up Your Code
Tidy Up Your CodeTidy Up Your Code
Tidy Up Your CodeAbbas Ali
Β 
laravel tricks in 50minutes
laravel tricks in 50minuteslaravel tricks in 50minutes
laravel tricks in 50minutesBarang CK
Β 
50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 Minutes50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 MinutesAzim Kurt
Β 
WordPress REST API hacking
WordPress REST API hackingWordPress REST API hacking
WordPress REST API hackingJeroen van Dijk
Β 
Introduction to Zend Framework web services
Introduction to Zend Framework web servicesIntroduction to Zend Framework web services
Introduction to Zend Framework web servicesMichelangelo van Dam
Β 
How kris-writes-symfony-apps-london
How kris-writes-symfony-apps-londonHow kris-writes-symfony-apps-london
How kris-writes-symfony-apps-londonKris Wallsmith
Β 
Bag Of Tricks From Iusethis
Bag Of Tricks From IusethisBag Of Tricks From Iusethis
Bag Of Tricks From IusethisMarcus Ramberg
Β 
PHPUnit elevato alla Symfony2
PHPUnit elevato alla Symfony2PHPUnit elevato alla Symfony2
PHPUnit elevato alla Symfony2eugenio pombi
Β 
ε‰εŽη«―mvc经ιͺŒ - webrebuild 2011 session
ε‰εŽη«―mvc经ιͺŒ - webrebuild 2011 sessionε‰εŽη«―mvc经ιͺŒ - webrebuild 2011 session
ε‰εŽη«―mvc经ιͺŒ - webrebuild 2011 sessionRANK LIU
Β 
Easy rest service using PHP reflection api
Easy rest service using PHP reflection apiEasy rest service using PHP reflection api
Easy rest service using PHP reflection apiMatthieu Aubry
Β 
Bootstrat REST APIs with Laravel 5
Bootstrat REST APIs with Laravel 5Bootstrat REST APIs with Laravel 5
Bootstrat REST APIs with Laravel 5Elena Kolevska
Β 
Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)Fabien Potencier
Β 
WordPress REST API hacking
WordPress REST API hackingWordPress REST API hacking
WordPress REST API hackingJeroen van Dijk
Β 
How Kris Writes Symfony Apps
How Kris Writes Symfony AppsHow Kris Writes Symfony Apps
How Kris Writes Symfony AppsKris Wallsmith
Β 

Similar to Crafting beautiful software (20)

Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For Beginners
Β 
Solid in practice
Solid in practiceSolid in practice
Solid in practice
Β 
SOLID in Practice
SOLID in PracticeSOLID in Practice
SOLID in Practice
Β 
Tidy Up Your Code
Tidy Up Your CodeTidy Up Your Code
Tidy Up Your Code
Β 
laravel tricks in 50minutes
laravel tricks in 50minuteslaravel tricks in 50minutes
laravel tricks in 50minutes
Β 
50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 Minutes50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 Minutes
Β 
WordPress REST API hacking
WordPress REST API hackingWordPress REST API hacking
WordPress REST API hacking
Β 
Introduction to Zend Framework web services
Introduction to Zend Framework web servicesIntroduction to Zend Framework web services
Introduction to Zend Framework web services
Β 
How kris-writes-symfony-apps-london
How kris-writes-symfony-apps-londonHow kris-writes-symfony-apps-london
How kris-writes-symfony-apps-london
Β 
Bag Of Tricks From Iusethis
Bag Of Tricks From IusethisBag Of Tricks From Iusethis
Bag Of Tricks From Iusethis
Β 
Laravel
LaravelLaravel
Laravel
Β 
PHPUnit elevato alla Symfony2
PHPUnit elevato alla Symfony2PHPUnit elevato alla Symfony2
PHPUnit elevato alla Symfony2
Β 
ε‰εŽη«―mvc经ιͺŒ - webrebuild 2011 session
ε‰εŽη«―mvc经ιͺŒ - webrebuild 2011 sessionε‰εŽη«―mvc经ιͺŒ - webrebuild 2011 session
ε‰εŽη«―mvc经ιͺŒ - webrebuild 2011 session
Β 
Easy rest service using PHP reflection api
Easy rest service using PHP reflection apiEasy rest service using PHP reflection api
Easy rest service using PHP reflection api
Β 
Bootstrat REST APIs with Laravel 5
Bootstrat REST APIs with Laravel 5Bootstrat REST APIs with Laravel 5
Bootstrat REST APIs with Laravel 5
Β 
PHP || [Student Result Management System]
PHP || [Student Result Management System]PHP || [Student Result Management System]
PHP || [Student Result Management System]
Β 
Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)
Β 
WordPress REST API hacking
WordPress REST API hackingWordPress REST API hacking
WordPress REST API hacking
Β 
Framework
FrameworkFramework
Framework
Β 
How Kris Writes Symfony Apps
How Kris Writes Symfony AppsHow Kris Writes Symfony Apps
How Kris Writes Symfony Apps
Β 

Recently uploaded

Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsAlberto GonzΓ‘lez Trastoy
Β 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfkalichargn70th171
Β 
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdfThe Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdfkalichargn70th171
Β 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...ICS
Β 
What is Binary Language? Computer Number Systems
What is Binary Language?  Computer Number SystemsWhat is Binary Language?  Computer Number Systems
What is Binary Language? Computer Number SystemsJheuzeDellosa
Β 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software DevelopersVinodh Ram
Β 
Cloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackCloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackVICTOR MAESTRE RAMIREZ
Β 
How To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.jsHow To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.jsAndolasoft Inc
Β 
Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)OPEN KNOWLEDGE GmbH
Β 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...MyIntelliSource, Inc.
Β 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVshikhaohhpro
Β 
Active Directory Penetration Testing, cionsystems.com.pdf
Active Directory Penetration Testing, cionsystems.com.pdfActive Directory Penetration Testing, cionsystems.com.pdf
Active Directory Penetration Testing, cionsystems.com.pdfCionsystems
Β 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionSolGuruz
Β 
(Genuine) Escort Service Lucknow | Starting β‚Ή,5K To @25k with A/C πŸ§‘πŸ½β€β€οΈβ€πŸ§‘πŸ» 89...
(Genuine) Escort Service Lucknow | Starting β‚Ή,5K To @25k with A/C πŸ§‘πŸ½β€β€οΈβ€πŸ§‘πŸ» 89...(Genuine) Escort Service Lucknow | Starting β‚Ή,5K To @25k with A/C πŸ§‘πŸ½β€β€οΈβ€πŸ§‘πŸ» 89...
(Genuine) Escort Service Lucknow | Starting β‚Ή,5K To @25k with A/C πŸ§‘πŸ½β€β€οΈβ€πŸ§‘πŸ» 89...gurkirankumar98700
Β 
Right Money Management App For Your Financial Goals
Right Money Management App For Your Financial GoalsRight Money Management App For Your Financial Goals
Right Money Management App For Your Financial GoalsJhone kinadey
Β 
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AISyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AIABDERRAOUF MEHENNI
Β 
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...OnePlan Solutions
Β 
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer DataAdobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer DataBradBedford3
Β 

Recently uploaded (20)

Exploring iOS App Development: Simplifying the Process
Exploring iOS App Development: Simplifying the ProcessExploring iOS App Development: Simplifying the Process
Exploring iOS App Development: Simplifying the Process
Β 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Β 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Β 
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdfThe Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
Β 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
Β 
What is Binary Language? Computer Number Systems
What is Binary Language?  Computer Number SystemsWhat is Binary Language?  Computer Number Systems
What is Binary Language? Computer Number Systems
Β 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software Developers
Β 
Cloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackCloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStack
Β 
Call Girls In Mukherjee Nagar πŸ“± 9999965857 🀩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Call Girls In Mukherjee Nagar πŸ“±  9999965857  🀩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...Call Girls In Mukherjee Nagar πŸ“±  9999965857  🀩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Call Girls In Mukherjee Nagar πŸ“± 9999965857 🀩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Β 
How To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.jsHow To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.js
Β 
Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)
Β 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Β 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTV
Β 
Active Directory Penetration Testing, cionsystems.com.pdf
Active Directory Penetration Testing, cionsystems.com.pdfActive Directory Penetration Testing, cionsystems.com.pdf
Active Directory Penetration Testing, cionsystems.com.pdf
Β 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with Precision
Β 
(Genuine) Escort Service Lucknow | Starting β‚Ή,5K To @25k with A/C πŸ§‘πŸ½β€β€οΈβ€πŸ§‘πŸ» 89...
(Genuine) Escort Service Lucknow | Starting β‚Ή,5K To @25k with A/C πŸ§‘πŸ½β€β€οΈβ€πŸ§‘πŸ» 89...(Genuine) Escort Service Lucknow | Starting β‚Ή,5K To @25k with A/C πŸ§‘πŸ½β€β€οΈβ€πŸ§‘πŸ» 89...
(Genuine) Escort Service Lucknow | Starting β‚Ή,5K To @25k with A/C πŸ§‘πŸ½β€β€οΈβ€πŸ§‘πŸ» 89...
Β 
Right Money Management App For Your Financial Goals
Right Money Management App For Your Financial GoalsRight Money Management App For Your Financial Goals
Right Money Management App For Your Financial Goals
Β 
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AISyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
Β 
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Β 
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer DataAdobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Β 

Crafting beautiful software

Editor's Notes

  1. We are going to walk through the process of building an application. We will start with some poor decisions and improve bit by bit and explaining the choices I make Show techniques to decrease technical dept, improve code readability and maintainability So that we end up with beautiful software
  2. This is how every projects starts. A lot of excitement. A clean sheet This time I’m going to do everything the right way
  3. At the start the amount of progress is huge New features are added rapidly
  4. Then it slows down
  5. And after a year you are barely moving forward
  6. You slow down due to the increase of technical dept Technical debt will increase over time but your goal is to have a linear increase You want to prevent the exponential growth in technical debt You fix one thing and break two
  7. Let’s prevent an exponential growth in technical depth
  8. Lack of intention - You don’t immediately see what a piece of code is about
  9. Lack of intention - You don’t immediately see what a piece of code is about
  10. Heavy coupling - to delivery mechanisms like http and persistence mechanisms like MySQL en MongoDB
  11. Anemic domain models - Important models for the business that contain no behaviour and no validation
  12. We start with a requirement
  13. We create our user model with getters and setters
  14. We make a controller we check if the form is submitted and valid. We then persist the data. That one requirement handled.
  15. We require the name and email in the constructor. We then validate that they are not empty.
  16. The customer comes in with the next requirement
  17. We expand our email validation so that it also checks for the email format.
  18. It’s becoming messy already To solve this we can use a value object
  19. It’s becoming messy already To solve this we can use a value object
  20. We can create a value object like this
  21. The constructor simply takes the argument as a string
  22. We validate the the email is not empty
  23. And that the email is in a valid format
  24. And only then we assign the email to the user
  25. Add a toString got retrieve the emailaddress
  26. And we can add a check to check if two email addresses are the same.
  27. The user can simply accept an email without worrying about validation
  28. In the register user action we used to do $form->getData() to get a user
  29. We now have to pass the required arguments to the user __construction
  30. Our User is now always in a valid state It looks like a small step but we made huge progress. This means we can simplify other parts of the application
  31. If we can not rely on the User to be in a valid state we place validation all over our application
  32. Because we force our model to always be in a valid state We can simplify this code We can now just send our e-mail
  33. Equality is based on value The one 10 euro bill is the same as the other It can still be different bills
  34. If we were to create something for a bank, and care about each individual bill, then it would not be a value object.
  35. The user always has a name and a valid e-mail
  36. Everything clear? Do you also know how to cope with form validation?
  37. This is our current implementation, directly using the doctrine entity manager
  38. We can simplify the controller and just call the save method
  39. Render template Create message Send an email
  40. The controller is rendering templates, creating messages and sending e-mails We can easily extract the notifying
  41. We separate this into it’s own class We can now easily test it
  42. We can now simply call the notifier. The rendering, creating and sending of the mail message will be handled in the notifier.
  43. Persistence is handled by the repository And sending a confirmation message by the notifier
  44. We create a new api controller With a register action That returns a JSON response
  45. We get the request data
  46. Create and save the user
  47. Notify the user
  48. and return a JsonResponse
  49. We are duplicating the registration of a user This will become problematic when adding more ways to register a user or when adding new requirements to the registration
  50. One simple command to register a new user
  51. One simple handler Creates the user Saves the user And notifies the user
  52. One simple handler Creates the user Saves the user And notifies the user
  53. One simple handler Creates the user Saves the user And notifies the user
  54. One simple handler Creates the user Saves the user And notifies the user
  55. The web controller is now simply calling RegisterUserHandler::handle
  56. We now always register a user in the same way The command is always ignorant of the delivery mechanism. If we want to add an auto generated password we only have to add it in the handler
  57. There is always one to one relation
  58. A command bus is a generic command handler It receives a command and routes it to the handler It provides the ability to add middleware
  59. A command bus is a generic command handler It receives a command and routes it to the handler It provides the ability to add middleware
  60. A command bus is a generic command handler It receives a command and routes it to the handler It provides the ability to add middleware
  61. A command bus is a generic command handler It receives a command and routes it to the handler It provides the ability to add middleware
  62. A command bus is a generic command handler It receives a command and routes it to the handler It provides the ability to add middleware
  63. You can easily add logging to log every command in your application
  64. We are still sending a confirmation message from within the handler
  65. A simple object with the changed data
  66. The ContainsRecordedMessages interface tells that we expose events.
  67. The PrivateMessageRecorderCapabilities is a trait that allows us to record events and exposes them
  68. The User::register method is an explicit way of creating a user. It also takes care of recording the event for us.
  69. We can now let the eventbus publish all recorded messages
  70. Handler publishes the event, the notifier and the logger listen
  71. Some characteristics
  72. We started with a data structure but we now have an object with meaningful behaviour
  73. In our domain we have our UserRepository that uses doctrine to persist the user.
  74. Our RegisterUserHandler shouldn’t deal with low level persistence
  75. Our RegisterUserHandler shouldn’t deal with low level persistence
  76. We can now rely on the interface instead of the concrete doctrine implementation
  77. We are injecting the repository in the RegisterUserHandler The RegisterUserHandler lets the repository handle the persistence We don’t rely on low level details
  78. We are injecting the repository in the RegisterUserHandler The RegisterUserHandler lets the repository handle the persistence We don’t rely on low level details
  79. We are injecting the repository in the RegisterUserHandler The RegisterUserHandler lets the repository handle the persistence We don’t rely on low level details
  80. Small step with huge benefits
  81. Small step with huge benefits
  82. Small step with huge benefits
  83. The one relying on your interface only knows about the exceptions defined in that interface. It can not cope with every possible implementation
  84. So this is the entire structure
  85. The red classes are part of our domain. They contain the things that have the most value for our business. They contain the business rules
  86. The blue parts are infrastructural concerns.
  87. The green parts are part of the application
  88. The highlighted classes are part of our domain. The framework we happen to use is not important here
  89. The highlighted classes are part of our infrastructure. The framework we happen to use is not important here
  90. The highlighted classes are part of our application. The framework we happen to use is not important here
  91. We are also less coupled to the framework. A switch of framework or using a different framework for an api or microservice is now easier. We are using a structure independent of the framework
  92. I agree with this, because testable code is not always good code. But untestable code is almost always bad code.
  93. We now require the persistence layer to handle id generation And we don’t know the id until the user is registered When using a command we will never know the id
  94. There is still a problem We are using generic exceptions that are used by almost any library
  95. We can catch the very specific exception
  96. We can catch the general domain exception
  97. We can now catch the more explicit exceptions
  98. We used the hexagonal architecture consisting of the inside and the outside
  99. The application and the domain The hexagonal architecture is also called ports and adapters The number of ports on a hexagon is six. Six is an arbitrary number and not important here.
  100. The domain doesn’t know anything about the delivery or persistence mechanisms It contains our most import things, our business rules
  101. Please give me some feedback. Tell me what you liked and things I can improve
  102. Clean code, DDD in PHP, Implement domain driven design Carlos, the author of DDD in PHP is also present the Dutch PHP conference