Why is crud a bad idea - focus on real scenarios
Transcript of Why is crud a bad idea - focus on real scenarios
![Page 1: Why is crud a bad idea - focus on real scenarios](https://reader031.fdocuments.net/reader031/viewer/2022021923/5a6479ec7f8b9a31568b483f/html5/thumbnails/1.jpg)
Why Is CRUD a Bad IdeaFocus on Real Scenarios
![Page 2: Why is crud a bad idea - focus on real scenarios](https://reader031.fdocuments.net/reader031/viewer/2022021923/5a6479ec7f8b9a31568b483f/html5/thumbnails/2.jpg)
Petr HeinzMore than 8 years of programming experience.
Loves clean code, regular expressions and clever design.
Dedicated last year to developing the Shopsys Framework, open source e-commerce framework made with passion on Symfony 3.
![Page 3: Why is crud a bad idea - focus on real scenarios](https://reader031.fdocuments.net/reader031/viewer/2022021923/5a6479ec7f8b9a31568b483f/html5/thumbnails/3.jpg)
Typical CRUD
![Page 4: Why is crud a bad idea - focus on real scenarios](https://reader031.fdocuments.net/reader031/viewer/2022021923/5a6479ec7f8b9a31568b483f/html5/thumbnails/4.jpg)
Create, Read, Update and DeleteFour basic functions of an persistent storage, often used as an API.
Can be mapped to SQL statements:
INSERT SELECT UPDATE DELETE
Can be mapped to HTTP methods (used in REST APIs):
PUT GET POST DELETE
![Page 5: Why is crud a bad idea - focus on real scenarios](https://reader031.fdocuments.net/reader031/viewer/2022021923/5a6479ec7f8b9a31568b483f/html5/thumbnails/5.jpg)
Example: Articleclass Article {
private $author, $text, $state, $published;
public function setAuthor(Author $author) { $this->author = $author; }
public function getAuthor() { return $this->author; }
public function setText($text) { $this->text = $text; }
// ...
}
class ArticleController {
public function create(...) { // ... }
public function update(...) { // ...
$article->setAuthor($author); $article->setText($text); $article->setState($state); $article->setPublished($published); }
public function delete(...) { // ... }
}
![Page 6: Why is crud a bad idea - focus on real scenarios](https://reader031.fdocuments.net/reader031/viewer/2022021923/5a6479ec7f8b9a31568b483f/html5/thumbnails/6.jpg)
Entities Must Follow Business RulesEntities are often constrained by business rules and the consistency must be kept.
● Company customer must have VAT ID filled in his account.
● Sold out product must be hidden.
● Article in published state must have published date.
This is difficult to achieve in the previous example. That’s because all article
attributes can be changed independently. Developers are not restricted in the way
they interact with the object.
![Page 7: Why is crud a bad idea - focus on real scenarios](https://reader031.fdocuments.net/reader031/viewer/2022021923/5a6479ec7f8b9a31568b483f/html5/thumbnails/7.jpg)
Example: Article without settersclass Article {
const STATE_UNPUBLISHED = 1; const STATE_PUBLISHED = 2; const STATE_DELETED = 3;
private $author, $text, $state, $published;
public function __construct(Author $author, $text, $state, DateTime $published = null) { // ... }
public function update(Author $author, $text, $state, DateTime $published = null) { // ... }
public function delete() { $this->state = self::STATE_DELETED; }
}
![Page 8: Why is crud a bad idea - focus on real scenarios](https://reader031.fdocuments.net/reader031/viewer/2022021923/5a6479ec7f8b9a31568b483f/html5/thumbnails/8.jpg)
Example: Article without settersclass Article {
// ...
public function update(Author $author, $text, $state, DateTime $published = null) { if ($this->state === self::STATE_DELETED) { throw new ArticleIsDeletedException($this); }
$this->author = $author; $this->text = $text; $this->state = $state;
if ($state === self::STATE_PUBLISHED) { $this->published = $published ?: new DateTime(); } elseif ($state === self::STATE_UNPUBLISHED) { $this->published = null; } }
}
![Page 9: Why is crud a bad idea - focus on real scenarios](https://reader031.fdocuments.net/reader031/viewer/2022021923/5a6479ec7f8b9a31568b483f/html5/thumbnails/9.jpg)
What Is an Object Anyway?
![Page 10: Why is crud a bad idea - focus on real scenarios](https://reader031.fdocuments.net/reader031/viewer/2022021923/5a6479ec7f8b9a31568b483f/html5/thumbnails/10.jpg)
Object Oriented ProgrammingObjects have both data (their properties) and behavior (their methods).
Objects model real-world behavior, concepts and relationships.
Encapsulation principle tells us to hide the details about the data and focus solely
on the behavior - the public methods of our objects (“Tell, Don’t Ask”).
In PHP it is easy to combine procedural and object-oriented programming.
![Page 11: Why is crud a bad idea - focus on real scenarios](https://reader031.fdocuments.net/reader031/viewer/2022021923/5a6479ec7f8b9a31568b483f/html5/thumbnails/11.jpg)
Example: Bank Account Objectclass BankAccount {
private $balance;
public function __construct(Money $balance) { $this->balance = $balance; }
public function deposit(Money $amount) { $this->balance = $this->balance->add($amount); }
public function withdraw(Money $amount) { if ($this->balance->isLessThan($amount)) { throw new InsufficientFundsException($balance, $amount); } $this->balance = $this->balance->subtract($amount); }
}
![Page 12: Why is crud a bad idea - focus on real scenarios](https://reader031.fdocuments.net/reader031/viewer/2022021923/5a6479ec7f8b9a31568b483f/html5/thumbnails/12.jpg)
Anemic / Rich Domain Model
![Page 13: Why is crud a bad idea - focus on real scenarios](https://reader031.fdocuments.net/reader031/viewer/2022021923/5a6479ec7f8b9a31568b483f/html5/thumbnails/13.jpg)
Let’s Define Some Terms FirstDomain: Most common definition: “A sphere of knowledge or activity.”
It’s basically the subject area of your application (eg. an online store or news site).
Domain Model: System of abstractions describing part of the domain that can be
used to solve problems. Simplification of the real world.
Domain Object: Object that is part of the domain model (eg. Product, Order, …).
Business Logic: High-level logic reflecting the real-world business rules.
![Page 14: Why is crud a bad idea - focus on real scenarios](https://reader031.fdocuments.net/reader031/viewer/2022021923/5a6479ec7f8b9a31568b483f/html5/thumbnails/14.jpg)
Anemic Domain ModelNo business logic in domain objects
Clearly separates logic and data
Violates object encapsulation
Works well for simple applications
Leads to procedural programming
Called an anti-pattern by M. Fowler
Business logic mainly in domain objects
Domain objects encapsulate inner data,
offer meaningful behavior
Data integrity kept by the encapsulation
Better choice for complex domain
models
Rich Domain Model
![Page 15: Why is crud a bad idea - focus on real scenarios](https://reader031.fdocuments.net/reader031/viewer/2022021923/5a6479ec7f8b9a31568b483f/html5/thumbnails/15.jpg)
Anemic Domain Model Rich Domain Modelclass Worker {
function getVelocity() { return $this->velocity; }
function setVelocity($velocity) { $this->velocity = $velocity; }
}
class WorkerService {
function work(Worker $w, Task $t, $time) { $progress = $t->getProgress(); $progress += $w->getVelocity() * $time; $t->setProgress($progress); }
}
class Worker {
function __construct($velocity) { $this->velocity = $velocity; }
function work(Task $task, $time) { $progress = $this->velocity * $time; $task->makeProgress($progress); }
}
![Page 16: Why is crud a bad idea - focus on real scenarios](https://reader031.fdocuments.net/reader031/viewer/2022021923/5a6479ec7f8b9a31568b483f/html5/thumbnails/16.jpg)
The Problem Lies in Setters
![Page 17: Why is crud a bad idea - focus on real scenarios](https://reader031.fdocuments.net/reader031/viewer/2022021923/5a6479ec7f8b9a31568b483f/html5/thumbnails/17.jpg)
Setters Do Not Exist in the Real WorldSetters have no meaning in the real world:
● A writer does not set a “published” state to an article, he/she publishes it.
● Customers do not set a “paid” status to an order, they pay for it.
● Your happy boss does not set a higher salary to you, he/she raises it.
There is always a better, more expressive, alternative to a setter.
Expressive statements lead to more readable code.
![Page 18: Why is crud a bad idea - focus on real scenarios](https://reader031.fdocuments.net/reader031/viewer/2022021923/5a6479ec7f8b9a31568b483f/html5/thumbnails/18.jpg)
Nobody Expects The Setters To Do StuffSimilarly to adding logic to a CRUD
update, you might feel the need to add
some business logic to your setter.
The problem with this is that nobody
expects setters to do anything beside
setting the property.
An unexpected behavior leads to bugs.
class Order {
// ...
public function setStatus($status) { if (!$this->isValidStatus($status)) { throw new InvalidArgumentException(); }
$this->status = $status;
if ($status === self::STATUS_PAID) { $this->mailService->sendOrderPaidMail( $this->orderNumber, $this->customer ); } }
}
![Page 19: Why is crud a bad idea - focus on real scenarios](https://reader031.fdocuments.net/reader031/viewer/2022021923/5a6479ec7f8b9a31568b483f/html5/thumbnails/19.jpg)
Nobody Expects The Setters To Do StuffSimilarly to adding logic to a CRUD
update, you might feel the need to add
some business logic to your setter.
The problem with this is that nobody
expects setters to do anything beside
setting the property.
An unexpected behavior leads to bugs.
class Order {
// ...
public function pay() { $this->status = self::STATUS_PAID;
$this->mailService->sendOrderPaidMail( $this->orderNumber, $this->customer ); }
public function cancel() { $this->status = self::STATUS_CANCELLED; }
}
![Page 20: Why is crud a bad idea - focus on real scenarios](https://reader031.fdocuments.net/reader031/viewer/2022021923/5a6479ec7f8b9a31568b483f/html5/thumbnails/20.jpg)
An Update in CRUD Is Similar to a SetterGeneric update method in CRUD is similar to a setter:
● It does not have a real-world meaning.
● There are better alternatives based on real scenarios to be implemented.
For example, by “updating” an article we mean “rewriting” it and possibly
“publishing”, “unpublishing” or “deleting” it.
![Page 21: Why is crud a bad idea - focus on real scenarios](https://reader031.fdocuments.net/reader031/viewer/2022021923/5a6479ec7f8b9a31568b483f/html5/thumbnails/21.jpg)
Conclusion and Recommendations
![Page 22: Why is crud a bad idea - focus on real scenarios](https://reader031.fdocuments.net/reader031/viewer/2022021923/5a6479ec7f8b9a31568b483f/html5/thumbnails/22.jpg)
Focus on Real ScenariosBy building your application around you domain objects and their behavior you
can get expressive code that is easier to understand, use and maintain.
Concept of “setting” or “updating” to too generic to be meaningful.
Your API should be focused on real scenarios, real use-cases. This will keep the
API clean and intuitive and it will help you keep the integrity of your data.
![Page 23: Why is crud a bad idea - focus on real scenarios](https://reader031.fdocuments.net/reader031/viewer/2022021923/5a6479ec7f8b9a31568b483f/html5/thumbnails/23.jpg)
Think About the Way You ProgramThere is no best way, no silver bullet. And there probably never will be one.
Keep in mind the techniques of object-oriented programming, encapsulation
principle, focusing on the behavior.
Knowing about the two extremes will help you improve the design of your
application and choose the proper solution for your project.
![Page 24: Why is crud a bad idea - focus on real scenarios](https://reader031.fdocuments.net/reader031/viewer/2022021923/5a6479ec7f8b9a31568b483f/html5/thumbnails/24.jpg)
Need CRUD methods? Add a Layer.If you for some reason want to
allow classical CRUD methods,
you can build it on top of your
internal API.
You can use Adapter pattern for
this task.
class Article {
// ...
public function update(...) { if ($this->state === self::STATE_DELETED) { throw new ArticleIsDeletedEx($this); }
$this->author = $author; $this->text = $text; $this->state = $state;
if ($state === self::STATE_PUBLISHED) { $this->published = $published ?: new DateTime(); } elseif ($state === self::STATE_UNPUBLISHED) { $this->published = null; } }
}
![Page 25: Why is crud a bad idea - focus on real scenarios](https://reader031.fdocuments.net/reader031/viewer/2022021923/5a6479ec7f8b9a31568b483f/html5/thumbnails/25.jpg)
Need CRUD methods? Add a Layer.If you for some reason want to
allow classical CRUD methods,
you can build it on top of your
internal API.
You can use Adapter pattern for
this task.
class ArticleCrudAdapter {
// ...
public function update(...) { if ($this->article->isDeleted()) { throw new ArticleIsDeletedEx($this->article); }
$this->article->rewrite($text, $author); switch ($state) { case Article::STATE_PUBLISHED: $this->article->publish($published); break; case Article::STATE_UNPUBLISHED: $this->article->unpublish(); break; case Article::STATE_DELETED: $this->article->delete(); break; } }
}
![Page 26: Why is crud a bad idea - focus on real scenarios](https://reader031.fdocuments.net/reader031/viewer/2022021923/5a6479ec7f8b9a31568b483f/html5/thumbnails/26.jpg)
It Is About the BalanceEvery way has its tradeoffs and, as always, it is about the balance.
Focusing on real-world use-cases helps to maintain integrity and usability.
Integration with other libraries or components is easier with generic methods.
When developing Shopsys Framework we try to keep that in mind, and take
inspiration from Rich Domain Model.
See for yourself, join closed beta: https://www.shopsys-framework.com
![Page 27: Why is crud a bad idea - focus on real scenarios](https://reader031.fdocuments.net/reader031/viewer/2022021923/5a6479ec7f8b9a31568b483f/html5/thumbnails/27.jpg)
Thanks for listening!
Let’s get down to your questions!