Life without ORM

Sergey Protko

InTouch Health

Life Without ORM

Sergey Protko

ORM: The Vietnam War of Computer Science

Object/Relational Impedance Mismatch

  • Flat vs Hierarchical
  • References
  • Encapsulation

In memory structure

author_id post_id post_title post_body post_author_id post_created_at
67 34 Some title just having fun 67 2019-05-03T14:53:41.670300
67 45 GoT 8 season it sucks 67 2019-05-03T23:26:41.670300
67 56 Chernobyl  nice, HBO 67 2019-05-28T23:26:41.670300
67 58 HBO cancelling subscription 67 2019-05-28T23:26:41.670300
68 42 Continious delivery Companies want to deliver frequently 68 2019-05-28T23:26:41.670300
68 57 DevOps missunderstanding 68 2019-05-28T23:26:41.670300
68 87 Trunk based development TBD 68 2019-05-28T23:26:41.670300
68 94 Yet another post I too lazy to think about text 68 2019-05-28T23:26:41.670300
68 98 And another one Hodor hodor 68 2019-05-28T23:26:41.670300

Relational World

$resultSet = $db->fetchAll($sql, [$params], [$paramTypes]);

$authorMapper = mapper([
    'id' => pk('author_id'),
    'name' => field('author_name'),
]);

$result = array_reduce($resultSet, $authorMapper->extend([
    'posts' => many([
        'id' => pk('post_id'),
        'title' => field('post_title'),
        'body' => field('post_body'),
        'author' => one($authorMapper),
        'createdAt' => field('post_created_at', 'datetimetz_immutable')
    ])
]));

Relatively easy to solve

Query Builders

mostly needed for query introspection*

Query Builders

mostly needed for query introspection*

const selectUser = (userId: string) =>
  sql`SELECT u.id, u.name, age(u.birthDate) as age FROM users WHERE u.id=${userId}`

*and parameters binding...

Flat vs Hierarchical

CREATE TABLE customers (
    customer_id integer PRIMARY KEY,
    referral_id integer 
        REFERENCES customers (customer_id)
)

References

$post = $author->posts()->first();
$postAuthor = $post->author();

assert($postAuthor === $author);

Relations

CREATE TABLE authors (
    author_id integer PRIMARY KEY,
    name text NOT NULL
);

CREATE TABLE posts (
    post_id integer PRIMARY KEY,
    image_url text NOT NULL,
    author_id integer NOT NULL REFERENCES authors
);

Encapsulation

Encapsulation

class Customer
{
    private int $id;
    private string $name;
    private ?Customer $referral;

    public function getId(): int
    {
        return $this->id;
    }
    
    public function setName(string $name): void
    {
        $this->name = $name;
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function setReferral(Customer $referral): void
    {
        $this->referral = $referral;
    }

    public function getReferral(): ?Customer
    {
        return $this->referral;
    }
}

This is not what I meant by OOP

NO ORM

Means no relational DB

NO ORM

Means no relational DB

General Purpose ORM

Atlas ORM

Most valuable ORM features

  • Schema generation

  • Mapping of data types

  • Lazy Load

  • Automatic transaction management

  • Query Builder

  • Generate CRUD

Most valuable ORM features

  • Schema generation (DBAL)

  • Mapping of data types (DBAL)

  • Lazy Load

  • Automatic transaction management

  • Query Builder 

  • Generate CRUD

class User
{
    private $id;
    private $email;
    private $emailConfirmationToken;
    private $changedEmail;
    private $password;
    private $passwordResetToken;
    private $name;
    private $profilePic;
    private $birthDate;
}

Transaction Boundaries

class User
{
    private $id;
    private $email;
    private $emailConfirmationToken;
    private $changedEmail;
    private $password;
    private $passwordResetToken;
    private $name;
    private $profilePic;
    private $birthDate;
}

Transaction Boundaries

class User
{
    private $id;
    private $email;
    private $emailConfirmationToken;
    private $changedEmail;
    private $password;
    private $passwordResetToken;
    private $name;
    private $profilePic;
    private $birthDate;
}

Transaction Boundaries

class User
{
    private $id;
    private $email;
    private $emailConfirmationToken;
    private $changedEmail;
    private $password;
    private $passwordResetToken;
    private $name;
    private $profilePic;
    private $birthDate;
}

Transaction Boundaries

Automatic transaction management

$user = $userRepository->find($id);

$user->changePassword($oldPassword, $newPassword, $encoder);

$em->flush();
UPDATE users
SET
    password=?
WHERE 
    id=?

Lazy Load

class Post
{
    private $id;
    private $photo;
    private $postedAt;
    private $description;
    private $tags;        // collection
    private $author;      // reference
    private $reactions;   // collection
    private $comments;    // collection
}

Lazy Load

class Post
{
    private $id;
    private $photo;
    private $postedAt;
    private $description;
    private $tags;        // collection
    private $author;      // reference
    private $reactions;   // collection
    private $comments;    // collection
}

New feature

Model

Aggregates

Aggregates

~= document

{
    post(id: "1000") {
        id
        photo {
           ...photoFields
        }
        description
        comments(limit: 1) {
            ...commentFields
        }
        reactions {
            ...reactionFields
        }
        author {
            ...profileFields
        }
    }
}

Application-side Joins

N+1 queries

Logical transactions

Split process into multiple logical transactions

Saga Pattern

Domain Events!

Eventual Consistency!

Message driven architecture

Pipelines

Split into teams

public function get(string $id, \Closure $fn)
{
    $entity = $this->doctrineRepository->find($id);
    if (!$entity) {
        throw new SomeEntityNotFound();
    }
    $aggregate = $this->automapper->map($entity, SpecificAggregate::class);
    $fn($aggregate);
    $this->automapper->map($aggregate, $entity);
}

You could slice project later

Microservices

are about risk management

What about CRUD?

Task Based UI

Most efficient way to write CRUD

is to write nothing

Most efficient way to write CRUD

is to write nothing

It's easy to blame tools for project failure

Thank you!