Top Banner
Why Is CRUD a Bad Idea Focus on Real Scenarios
27

Why is crud a bad idea - focus on real scenarios

Jan 21, 2018

Download

Internet

Divante
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Why is crud a bad idea - focus on real scenarios

Why Is CRUD a Bad IdeaFocus on Real Scenarios

Page 2: Why is crud a bad idea - focus on real scenarios

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.

[email protected]

Page 3: Why is crud a bad idea - focus on real scenarios

Typical CRUD

Page 4: Why is crud a bad idea - focus on real scenarios

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

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

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

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

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

What Is an Object Anyway?

Page 10: Why is crud a bad idea - focus on real scenarios

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

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

Anemic / Rich Domain Model

Page 13: Why is crud a bad idea - focus on real scenarios

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

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

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

The Problem Lies in Setters

Page 17: Why is crud a bad idea - focus on real scenarios

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

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

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

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

Conclusion and Recommendations

Page 22: Why is crud a bad idea - focus on real scenarios

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

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

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

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

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

Thanks for listening!

Let’s get down to your questions!