You Don’t Need an ORM

Vadim Samokhin
5 min readSep 10, 2017

It was said a lot about why ORM is an anti-pattern. The core reason is described here. The main point is that the way data is stored and retrieved is an intrinsic property of any object. But ORM seems to be the “first-class citizen”: we explicitly call its methods when we need to fetch some data, and ORM frames it as an object; we call ORM’s functionality when we need to persist an object. In both cases ORM operates upon our objects, treating them as data bags. It’s really pretty offensive. So I’d rather invert this: I want my objects to call ORM, not the other way round.

So, what is an object?

After all, what is an object? It’s a representative of some thing from real life, from our ubiquitous language. The way its data is stored is just an implementation detail. I want it to be encapsulated within my object borders. Who said that all object’s data must be stored as object properties? Who said that object contains any data? Is it calculated on the fly? Is it stored in memory? Is it stored on disk? Is it stored in the cloud? It doesn’t matter. Object is not a data bag. Just like my microservices, it exposes behavior instead, not data. This principle is called Uniform Access Principle, and it is a manifestation of encapsulation — fundamental OOP concept.

Persistent object state

So my persisted object encapsulates two things: the first one is used by the object in order to get to the store, and the second one is used by the object in order to find or save its data. The example taken here:

final class PgHuman implements Human
{
/**
* Data source.
*/
private final transient PgSource src;

/**
* Number of it.
*/
private final transient long number;

PgHuman(final PgSource source, final long num)
{
this.src = source;
this.number = num;
}
}

But one thing concerns me in implementation of this concept. Again, consider PgHuman class. There is definitely both database query logic and business logic inside it. Look at the next() method. It retrieves a talk with unread message, and if there is no such, starts a new talk relating to random question without any messages and which wasn’t asked by current user. I think it would be great to separate these responsibilities. I fully understand that database switching happens quite rarely, but nevertheless it would be helpful for local integration tests, where data would be stored just in memory.

Better implementation

So what about quite evident step to factor the database logic out and hide it behind an interface? Consider an example based on this article.
Here is an implementation:

class PurchaseOrder implements IPurchaseOrder
{
/**
*
@var OrderStorage
*/
private $storage;

/**
*
@var OrderId
*/
private $id;

public function __construct(OrderStorage $storage, OrderId $id)
{
$this->storage = $storage;
$this->id = $id;
}

public function newInvoice(
IInvoiceNumber $invoiceNumber,
IVendorInvoiceNumber $vendorInvoiceNumber,
DateTime $dateTime,
InvoiceAmount $amount
)
{
$this->storage->save($invoiceNumber, $vendorInvoiceNumber, $dateTime, $amount);

return new Invoice($this->storage, $invoiceNumber);
}
}

So all database logic is put inside storage’s ‘save()’ method. It could be transaction handling, some inserts in related tables, explicit locks, etc. The nice thing is that domain class PurchaseOrder is not coupled at all to database-specific code.
In case we use any relational database with an imaginary database abstraction library, our storage code might look like the following:

class SQLOrderStorage implements OrderStorage
{
private $connection;

public function __construct(SQLConnection $connection)
{
$this->connection = $connection;
}

public function save(
IInvoiceNumber $invoiceNumber,
IVendorInvoiceNumber $vendorInvoiceNumber,
DateTime $dateTime,
InvoiceAmount $amount
)
{
new InsertQuery($this->connection)
->into('invoice')
->set('invoice_number', $invoiceNumber->value())
->set('vendor_invoice_number', $vendorInvoiceNumber)
->set('datetime', $dateTime)
->set('amount', $amount->amount())
->set('currency', $amount->currency())
;
}
}

Need Memcached caching? No problem, let’s use a decorator:

class MemcachedOrderStorage implements OrderStorage
{
private $storage;
private $memcachedClient;

public function __construct(OrderStorage $storage, MemcachedClient $memcachedClient)
{
$this->storage = $storage;
$this->memcachedClient = $memcachedClient;
}

public function save(
IInvoiceNumber $invoiceNumber,
IVendorInvoiceNumber $vendorInvoiceNumber,
DateTime $dateTime,
InvoiceAmount $amount
)
{
$this->storage->save($invoiceNumber, $vendorInvoiceNumber, $dateTime, $amount);

$this->memcachedClient->set(
$invoiceNumber->value(),
base64_encode(
json_encode(
[
'invoice_number' => $invoiceNumber->value(),
'vendor_invoice_number' => $vendorInvoiceNumber->value(),
'datetime' => $dateTime->format('c'),
'amount' => $amount->amount(),
'currency' => $amount->currency(),
]
)
)
);
;
}
}

Need in-memory caching? Let’s use a decorator again:

class LocalOrderStorage implements OrderStorage
{
private $storage;
private $localStorage;

public function __construct(OrderStorage $storage)
{
$this->storage = $storage;
$this->localStorage = [];
}

public function save(
IInvoiceNumber $invoiceNumber,
IVendorInvoiceNumber $vendorInvoiceNumber,
DateTime $dateTime,
InvoiceAmount $amount
)
{
$this->storage->save($invoiceNumber, $vendorInvoiceNumber, $dateTime, $amount);

$this->localStorage[$invoiceNumber->value()] =
[
'invoice_number' => $invoiceNumber->value(),
'vendor_invoice_number' => $vendorInvoiceNumber->value(),
'datetime' => $dateTime->format('c'),
'amount' => $amount->amount(),
'currency' => $amount->currency(),
]
;
}
}

Transaction management

You shouldn’t worry about transaction management as well. If you are into DDD, you are aware of aggregate root concept. In short, it is a quantum of consistency boundaries in your domain model. Something that absolutely must hold its invariants at all times. And you probably aware of rule of thumb when mutating aggregates: one aggregate per transaction, and the next aggregate is usually triggered by an event, published by the current one. Here is an example of such approach. So there are no transactions spanning several aggregate roots. In my example, ‘PurchaseOrder’ class is an aggregate root, and all transaction logic can reside inside ‘SQLOrderStorage’.

Polyglot objects

This concept spans not only SQL-speaking objects, NoSQL-speaking objects or any other storage-specific-language-speaking objects, but any objects whose data is kept not only in memory. This includes some remote json/xml/etc resource, or a local file. For example, if I can find my financial transaction by calling some API, transaction class could look like this:

class FinancialTransaction
{
private $dataSource;
private $id;

public function __construct(TransactionDataSource $dataSource, TransactionId $id)
{
$this->dataSource = $dataSource;
$this->id = $id;
}

public function isSuccessful()
{
return $this->dataSource->isSuccessful();
}
}

And data source implementation can be like the following:

interface TransactionDataSource
{
public function isSuccessful(TransactionId $transactionId);
}

class HttpTransactionDataSource implements TransactionDataSource
{
private $connection;

public function __construct(Connection $connection)
{
$this->connection = $connection;
}

public function isSuccessful(TransactionId $transactionId)
{
(new Response(
new PostHttpRequest(
$this->connection->uri(),
new HttpVersion('HTTP/1.1'),
[
$this->connection->credentials(),
new Header('Content-Type: application/xml'),
new Header('SOAPAction: ""'),
],
new RequestBody(
(new SimpleXMLElement("<data></data>"))
->addChild('transaction_id', $transactionId->value())
),
$this->connection->transport()
)
))
->value()
;
}
}

Wrapping it up

Well, this approach is nothing more than inverted ORM/HTTP call/etc. But the conceptual difference is huge.
The main point, the motivational force in this approach is in the notion of an “object”. It is a declarative concept, representing real-life entities. Object who knows what to do and how. Object who doesn’t need any instructions. Object who doesn’t need to be orchestrated or controlled in any way. Finally, an object, who possesses all resources or services it needs to do its job.

Using human metaphor, objects are adults, not kids.

--

--