SlideShare a Scribd company logo
Keep yourself DRY
How not to repeat yourself
☔
but not 💩 up abstraction
Jiří Pudil
from Brno, CZE
open-sourcerer
coffee addict
guitar noisemaker
DRY
☔
Don't Repeat Yourself
Don't Repeat Yourself
Every piece of knowledge must have a single, unambiguous,
authoritative representation within a system
“
Don't Repeat Yourself
A modification of any single element of a system does not require
a change in other logically unrelated elements.
“
Let's write some <code/>
📝 👉 📧
We're sending a weekly digest to users by cron
class SendWeeklyDigestCommand extends Command
{
public function execute($input, $output): int
{
// …
foreach ($users as $user) {
mail(
$user->email,
'Weekly Digest',
$weeklyDigestBody,
['From' => $this->systemEmail]
);
}
return self::SUCCESS;
}
}
󰳖
We want to send the last week's digest upon registration.
󰳕
OK.
🤔
I already have the mail sending code in the command.
󰳕
"How do I call this CLI command from the UI controller?"
󰳓
Move the domain logic into a separate layer.
Call it from both places.
class WeeklyDigestSender
{
public function __construct(private readonly string $systemEmail) {}
public function send(User $user): void
{
// …
mail(
$user->email,
'Weekly Digest',
$weeklyDigestBody,
['From' => $this->systemEmail]
);
}
}
class SendWeeklyDigestCommand extends Command
{
public function execute($input, $output): int
{
// …
foreach ($users as $user) {
$this->weeklyDigestSender->send($user);
}
return self::SUCCESS;
}
}
class UserRegistrationController extends Controller
{
public function actionRegister($formData): void
{
// …
$this->weeklyDigestSender->send($user);
$this->redirectTo('Homepage');
}
}
Domain
Logic
UI controller
CLI command
API handler
󰳖
People forget passwords. We need to allow them
to reset their password.
󰳕
OK.
🤔
I already have the mail sending code in the domain layer.
class WeeklyDigestSender
{
public function __construct(private readonly string $systemEmail) {}
public function send(User $user): void
{
// …
mail(
$user->email,
'Weekly Digest',
$weeklyDigestBody,
['From' => $this->systemEmail]
);
}
}
󰳓
OOP blah blah abstraction blah blah inheritance!
abstract class EmailSender
{
public function __construct(private readonly string $systemEmail) {}
public function send(User $user): void
{
mail(
$user->email,
$this->getSubject(),
$this->getBody(),
['From' => $this->systemEmail]
);
}
abstract protected function getSubject(): string;
abstract protected function getBody(): string;
}
class WeeklyDigestSender extends EmailSender
{
protected function getSubject(): string
{
return 'Weekly Digest';
}
protected function getBody(): string
{
// …
return $weeklyDigestBody;
}
}
class PasswordResetSender extends EmailSender
{
protected function getSubject(): string
{
return 'Reset your password';
}
protected function getBody(): string
{
// …
return $passwordResetBody;
}
}
🤦
󰳕
Huh, there's a bug somewhere.
class PasswordResetSender extends EmailSender
{
protected function getSubject(): string
{
return 'Reset your password';
}
protected function getBody(): string
{
// …
return $passwordResetBody;
}
}
abstract class EmailSender
{
public function send(User $user): void
{
mail($user->email, $this->getSubject(), $this->getBody());
}
abstract protected function getSubject(): string;
abstract protected function getBody(): string;
}
class PasswordResetSender extends EmailSender
{
protected function getSubject(): string
{
return 'Reset your password';
}
protected function getBody(): string
{
// …
return $passwordResetBody;
}
}
󰳕
Huh, I need some user data in the password reset email.
class PasswordResetSender extends EmailSender
{
protected function getSubject(): string
{
return 'Reset your password';
}
protected function getBody(User $user): string
{
// …
return $passwordResetBody;
}
}
abstract class EmailSender
{
public function send(User $user): void
{
mail(
$user->email,
$this->getSubject(),
$this->getBody($user),
);
}
abstract protected function getSubject(): string;
abstract protected function getBody(User $user): string;
}
class WeeklyDigestSender extends EmailSender
{
protected function getSubject(): string
{
return 'Weekly Digest';
}
protected function getBody(User $user): string
{
// …
return $weeklyDigestBody;
}
}
class WeeklyDigestSender extends EmailSender
{
protected function getSubject(): string
{
return 'Weekly Digest';
}
protected function getBody(User $user): string
{
// … I don't really need $user :/
return $weeklyDigestBody;
}
}
Don't Repeat Yourself
A modification of any single element of a system does not require
a change in other logically unrelated elements.
“
󰳕
Huh, I need some other data in the password reset email.
class PasswordResetSender extends EmailSender
{
public function __construct(
private readonly DataProvider $provider,
) {}
// …
}
class PasswordResetSender extends EmailSender
{
public function __construct(
private readonly DataProvider $provider,
) {
parent::__construct();
}
// …
}
class PasswordResetSender extends EmailSender
{
public function __construct(
private readonly DataProvider $provider,
string $systemEmail,
) {
parent::__construct($systemEmail);
}
// …
}
🤯
This is hell!
🤯
This is constructor hell!
🤔
Also, how do I test this code?
class WeeklyDigestSender
{
public function __construct(private readonly string $systemEmail) {}
public function send(User $user): void
{
// …
mail(
$user->email,
'Weekly Digest',
$weeklyDigestBody,
['From' => $this->systemEmail]
);
}
}
󰳓
blah blah ahem composition?
class EmailSender
{
public function __construct(private readonly string $systemEmail) {}
public function send(
User $user,
string $subject,
string $body,
): void
{
// …
mail($user->email, $subject, $body, ['From' => $this->systemEmail]);
}
}
class WeeklyDigestSender
{
public function __construct(private readonly EmailSender $sender) {}
public function send(User $user): void
{
// …
$this->sender->send(
$user,
'Weekly Digest',
$weeklyDigestBody,
);
}
}
class PasswordResetSender
{
public function __construct(private readonly EmailSender $sender) {}
public function send(User $user): void
{
// …
$this->sender->send(
$user,
'Reset your password',
$passwordResetBody,
);
}
}
class PasswordResetSender
{
public function __construct(private readonly EmailSender $sender) {}
public function send(User $user): void
{
// …
$this->sender->send(
$user,
'Reset your password',
$passwordResetBody,
);
}
}
class PasswordResetSender
{
public function __construct(private readonly EmailSender $sender) {}
public function send(User $user): void
{
// …
$this->sender->send(
$user,
'Reset your password',
$passwordResetBody,
);
}
}
class PasswordResetSender
{
public function __construct(
private readonly EmailSender $sender,
private readonly DataProvider $provider,
) {}
public function send(User $user): void
{
// …
$this->sender->send(
$user,
'Reset your password',
$passwordResetBody,
);
}
}
👤
󰳕
󰳖
󰳖
People will read emails on bricks these days.
Password reset must include plain-text format.
class EmailSender
{
public function send(
User $user,
string $subject,
string $body,
string|null $plainTextBody = null,
): void
{
// …
$body = $this->combineBodies($body, $plainTextBody);
// …
}
}
󰳖
We need to add a List-Unsubscribe header
to the weekly digest.
class EmailSender
{
public function send(
User $user,
string $subject,
string $body,
string|null $plainTextBody = null,
string|null $unsubscribeLink = null,
): void
{
// …
$headers = ['From' => $this->systemEmail];
if ($unsubscribeLink !== null) {
$headers['List-Unsubscribe'] = $unsubscribeLink;
}
// …
}
}
󰳖
Oh, and we want to send weekly digests
from a different email address…
class EmailSender
{
public function send(
User $user,
string $subject,
string $body,
string|null $plainTextBody = null,
string|null $unsubscribeLink = null,
string|null $from = null,
): void
{
// …
$headers = ['From' => $from ?? $this->systemEmail];
// …
}
}
󰳖
We want to log emails in dev environment
instead of actually sending them.
class EmailSender
{
public function __construct(private readonly string $systemEmail, private readonly bool $isDev)
public function send(
User $user,
string $subject,
string $body,
string|null $plainTextBody = null,
string|null $unsubscribeLink = null,
string|null $from = null,
): void
{
// …
if ($this->isDev) { /* … */ } else { /* … */ }
}
}
󰳕
Hm, I just want to store the emails in an array
in tests.
class EmailSender
{
private array $sentEmails = [];
public function send(
User $user,
string $subject,
string $body,
string|null $plainTextBody = null,
string|null $unsubscribeLink = null,
string|null $from = null,
): void
{
// …
$this->sentEmails[] = /* … */;
}
}
󰳖
It's time we added email verification.
class EmailVerificationSender
{
public function __construct(private readonly EmailSender
$sender) {}
public function send(User $user): void
{
$this->sender->send();
}
}
😱
󰳕
"How do I call this CLI command from the UI controller?"
󰳓
Always question your abstractions
in the face of requirement changes.
🛌
☕
👀
class Message
{
public function setFrom(string $from): void { /* … */ }
public function setTo(string $to): void { /* … */ }
public function setSubject(string $subject): void { /* … */ }
public function setBody(string $htmlBody): void { /* … */ }
// …
}
interface Transport
{
public function send(Message $message): void;
}
class EmailVerificationSender
{
public function __construct(
private readonly Transport $transport
) {}
public function send(User $user): void
{
$message = new Message();
// …
$this->transport->send($message);
}
}
class PasswordResetSender
{
public function __construct(
private readonly Transport $transport
) {}
public function send(User $user): void
{
$message = new Message();
// …
$this->transport->send($message);
}
}
class WeeklyDigestSender
{
public function __construct(
private readonly Transport $transport
) {}
public function send(User $user): void
{
$message = new Message();
// …
$this->transport->send($message);
}
}
class WeeklyDigestSender
{
public function __construct(
private readonly Transport $transport
) {}
public function send(User $user): void
{
$message = new Message();
// …
$this->transport->send($message);
}
}
Don't Repeat Yourself
Every piece of knowledge must have a single, unambiguous,
authoritative representation within a system
“
Don't Repeat Yourself
Every piece of knowledge must have a single, unambiguous,
authoritative representation within a system
“
class WeeklyDigestSender
{
public function __construct(
private readonly Transport $transport
) {}
public function send(User $user): void
{
$message = new Message();
// …
$this->transport->send($message);
}
}
production:
transport: SmtpTransport(…)
production:
transport: SmtpTransport(…)
development:
transport: FileLoggingTransport
DRY
☔
󰳖
So, we were talking with other managers, and …
Jiří Pudil
@jiripudil
me@jiripudil.cz
https://jiripudil.cz
󰳓
Always question your abstractions
in the face of requirement changes.

More Related Content

Similar to Jak neopakovat kód, ale nepo**** abstrakci | Jiří Pudil | 15. 2. 2023 – Kiwi.com

SummaryHW6 Account ManagementIn HW4, you kept track of multiple.pdf
SummaryHW6 Account ManagementIn HW4, you kept track of multiple.pdfSummaryHW6 Account ManagementIn HW4, you kept track of multiple.pdf
SummaryHW6 Account ManagementIn HW4, you kept track of multiple.pdf
ARORACOCKERY2111
 
Dependency injection in Drupal 8
Dependency injection in Drupal 8Dependency injection in Drupal 8
Dependency injection in Drupal 8
Alexei Gorobets
 

Similar to Jak neopakovat kód, ale nepo**** abstrakci | Jiří Pudil | 15. 2. 2023 – Kiwi.com (20)

Be pragmatic, be SOLID
Be pragmatic, be SOLIDBe pragmatic, be SOLID
Be pragmatic, be SOLID
 
SummaryHW6 Account ManagementIn HW4, you kept track of multiple.pdf
SummaryHW6 Account ManagementIn HW4, you kept track of multiple.pdfSummaryHW6 Account ManagementIn HW4, you kept track of multiple.pdf
SummaryHW6 Account ManagementIn HW4, you kept track of multiple.pdf
 
Implicit parameters, when to use them (or not)!
Implicit parameters, when to use them (or not)!Implicit parameters, when to use them (or not)!
Implicit parameters, when to use them (or not)!
 
Dependency injection in Drupal 8
Dependency injection in Drupal 8Dependency injection in Drupal 8
Dependency injection in Drupal 8
 
How Kris Writes Symfony Apps
How Kris Writes Symfony AppsHow Kris Writes Symfony Apps
How Kris Writes Symfony Apps
 
Crafting beautiful software
Crafting beautiful softwareCrafting beautiful software
Crafting beautiful software
 
Unittests für Dummies
Unittests für DummiesUnittests für Dummies
Unittests für Dummies
 
Why is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenariosWhy is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenarios
 
Decoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DICDecoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DIC
 
Separation of concerns - DPC12
Separation of concerns - DPC12Separation of concerns - DPC12
Separation of concerns - DPC12
 
Symfony2 from the Trenches
Symfony2 from the TrenchesSymfony2 from the Trenches
Symfony2 from the Trenches
 
Codemotion appengine
Codemotion appengineCodemotion appengine
Codemotion appengine
 
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and more
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and moreSymfony Guard Authentication: Fun with API Token, Social Login, JWT and more
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and more
 
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
 
Как получить чёрный пояс по WordPress?
Как получить чёрный пояс по WordPress?Как получить чёрный пояс по WordPress?
Как получить чёрный пояс по WordPress?
 
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
 
Demystifying Object-Oriented Programming - ZendCon 2016
Demystifying Object-Oriented Programming - ZendCon 2016Demystifying Object-Oriented Programming - ZendCon 2016
Demystifying Object-Oriented Programming - ZendCon 2016
 
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? v2.0
Как получить чёрный пояс по WordPress? v2.0Как получить чёрный пояс по WordPress? v2.0
Как получить чёрный пояс по WordPress? v2.0
 

More from WebScience1

More from WebScience1 (7)

The Clone Wars: Spoutání Síly PHP a Nextras ORM | Jakub Fatrdla | 19. 4. 2023...
The Clone Wars: Spoutání Síly PHP a Nextras ORM | Jakub Fatrdla | 19. 4. 2023...The Clone Wars: Spoutání Síly PHP a Nextras ORM | Jakub Fatrdla | 19. 4. 2023...
The Clone Wars: Spoutání Síly PHP a Nextras ORM | Jakub Fatrdla | 19. 4. 2023...
 
Kompletní průvodce obrázky | Tomáš Krejčí | 19. 4. 2023 – SUPERKODERS.CZ
Kompletní průvodce obrázky | Tomáš Krejčí | 19. 4. 2023 – SUPERKODERS.CZKompletní průvodce obrázky | Tomáš Krejčí | 19. 4. 2023 – SUPERKODERS.CZ
Kompletní průvodce obrázky | Tomáš Krejčí | 19. 4. 2023 – SUPERKODERS.CZ
 
Rust jako náhrada C pro vývoj PHP extensions?
Rust jako náhrada C pro vývoj PHP extensions?Rust jako náhrada C pro vývoj PHP extensions?
Rust jako náhrada C pro vývoj PHP extensions?
 
Jak jsme překládali pomocí Symfony/Messenger
Jak jsme překládali pomocí Symfony/MessengerJak jsme překládali pomocí Symfony/Messenger
Jak jsme překládali pomocí Symfony/Messenger
 
Framework X jako API
Framework X jako APIFramework X jako API
Framework X jako API
 
Jak na více Docker kompozic na lokále | Jan Drábek | 15. 2. 2023 – Kiwi.com
Jak na více Docker kompozic na lokále | Jan Drábek | 15. 2. 2023 – Kiwi.comJak na více Docker kompozic na lokále | Jan Drábek | 15. 2. 2023 – Kiwi.com
Jak na více Docker kompozic na lokále | Jan Drábek | 15. 2. 2023 – Kiwi.com
 
RabbitMQ v PHP webových aplikacích | Adam Král | 15. 2. 2023 – Kiwi.com
RabbitMQ v PHP webových aplikacích | Adam Král | 15. 2. 2023 – Kiwi.comRabbitMQ v PHP webových aplikacích | Adam Král | 15. 2. 2023 – Kiwi.com
RabbitMQ v PHP webových aplikacích | Adam Král | 15. 2. 2023 – Kiwi.com
 

Recently uploaded

527598851-ppc-due-to-various-govt-policies.pdf
527598851-ppc-due-to-various-govt-policies.pdf527598851-ppc-due-to-various-govt-policies.pdf
527598851-ppc-due-to-various-govt-policies.pdf
rajpreetkaur75080
 
Introduction of Biology in living organisms
Introduction of Biology in living organismsIntroduction of Biology in living organisms
Introduction of Biology in living organisms
soumyapottola
 

Recently uploaded (14)

Pollinator Ambassador Earth Steward Day Presentation 2024-05-22
Pollinator Ambassador Earth Steward Day Presentation 2024-05-22Pollinator Ambassador Earth Steward Day Presentation 2024-05-22
Pollinator Ambassador Earth Steward Day Presentation 2024-05-22
 
Hi-Tech Industry 2024-25 Prospective.pptx
Hi-Tech Industry 2024-25 Prospective.pptxHi-Tech Industry 2024-25 Prospective.pptx
Hi-Tech Industry 2024-25 Prospective.pptx
 
527598851-ppc-due-to-various-govt-policies.pdf
527598851-ppc-due-to-various-govt-policies.pdf527598851-ppc-due-to-various-govt-policies.pdf
527598851-ppc-due-to-various-govt-policies.pdf
 
Eureka, I found it! - Special Libraries Association 2021 Presentation
Eureka, I found it! - Special Libraries Association 2021 PresentationEureka, I found it! - Special Libraries Association 2021 Presentation
Eureka, I found it! - Special Libraries Association 2021 Presentation
 
0x01 - Newton's Third Law: Static vs. Dynamic Abusers
0x01 - Newton's Third Law:  Static vs. Dynamic Abusers0x01 - Newton's Third Law:  Static vs. Dynamic Abusers
0x01 - Newton's Third Law: Static vs. Dynamic Abusers
 
Acorn Recovery: Restore IT infra within minutes
Acorn Recovery: Restore IT infra within minutesAcorn Recovery: Restore IT infra within minutes
Acorn Recovery: Restore IT infra within minutes
 
The Canoga Gardens Development Project. PDF
The Canoga Gardens Development Project. PDFThe Canoga Gardens Development Project. PDF
The Canoga Gardens Development Project. PDF
 
Oracle Database Administration I (1Z0-082) Exam Dumps 2024.pdf
Oracle Database Administration I (1Z0-082) Exam Dumps 2024.pdfOracle Database Administration I (1Z0-082) Exam Dumps 2024.pdf
Oracle Database Administration I (1Z0-082) Exam Dumps 2024.pdf
 
Introduction of Biology in living organisms
Introduction of Biology in living organismsIntroduction of Biology in living organisms
Introduction of Biology in living organisms
 
123445566544333222333444dxcvbcvcvharsh.pptx
123445566544333222333444dxcvbcvcvharsh.pptx123445566544333222333444dxcvbcvcvharsh.pptx
123445566544333222333444dxcvbcvcvharsh.pptx
 
Writing Sample 2 -Bridging the Divide: Enhancing Public Engagement in Urban D...
Writing Sample 2 -Bridging the Divide: Enhancing Public Engagement in Urban D...Writing Sample 2 -Bridging the Divide: Enhancing Public Engagement in Urban D...
Writing Sample 2 -Bridging the Divide: Enhancing Public Engagement in Urban D...
 
Getting started with Amazon Bedrock Studio and Control Tower
Getting started with Amazon Bedrock Studio and Control TowerGetting started with Amazon Bedrock Studio and Control Tower
Getting started with Amazon Bedrock Studio and Control Tower
 
05232024 Joint Meeting - Community Networking
05232024 Joint Meeting - Community Networking05232024 Joint Meeting - Community Networking
05232024 Joint Meeting - Community Networking
 
Sharpen existing tools or get a new toolbox? Contemporary cluster initiatives...
Sharpen existing tools or get a new toolbox? Contemporary cluster initiatives...Sharpen existing tools or get a new toolbox? Contemporary cluster initiatives...
Sharpen existing tools or get a new toolbox? Contemporary cluster initiatives...
 

Jak neopakovat kód, ale nepo**** abstrakci | Jiří Pudil | 15. 2. 2023 – Kiwi.com