Research

Persistence Ignorance: The Unknown Ideal

Persistence Ignorance is a fairly new concept that is getting more and more attention amongst software developers, but its implications and the problems with current implementations are still not widely understood or appreciated. This article attempts to shed some light on the topic and questions whether pure PI is even possible with today’s development tools.

Tags: ddd designpatterns programming

Introduction

Computers are programmable devices that carry out a sequence of arithmetic or logical operations for calculating a function from an initial state and optional input data. In some computer applications, the computer program’s state needs to outlive the process that created it, either because the program’s execution is suspended and to be resumed at a later time, or because the state constitutes the input for another sequence of operations. This characteristic is called Persistence, and with today’s technology it generally means the storage of state data in a file system or database on non-volatile storage, such as hard drives.

Persistence can be a transparent feature of the execution environment (orthogonal persistence) that executes the computer program (for example, hibernation on laptops or snapshots of virtual machines), but in most applications it is part of the computer program itself (non-orthogonal persistence) and requires the implementation of additional operations to perform the saving and loading of state data (for example, saving a text document on a Flash drive or storing an order in an online shop’s database).

As computer applications grow increasingly more complex, continuous efforts are being made to find better approaches for separating the totality of a computer program into smaller components that address distinct subsets of the overall problem and with the least amount of overlap or duplication in functionality for better maintainability, extensibility and reuse. This process is called Separation of Concerns. In Object-Oriented Programming (OOP), the process leads to the familiar notions of Encapsulation and Information Hiding, in which objects combine a problem-specific sequence of operations along with the data they operate on. In information systems design, the idea of Separation of Concerns inspired layered architectures, such as network protocol stacks or n-Tier data applications, in which the different layers communicate through contractual interfaces, but are otherwise completely independent from each other.

When it comes to modeling the problem domain of a computer program, the software development industry offers various approaches that emphasize different aspects of the subject areas to which the program is being applied. If the focus is the business domain, a common approach is Domain-Driven Design (DDD), which generally includes an architectural layer that is purely concerned with modeling the business domain along with its logic and rules. If the domain objects require persistence, this approach also includes design patterns for the implementation of layers for data access and storage.

One of the central questions in the context of these ideas is this: should an object that models the problem domain have any knowledge of a persistence framework that may or may not be present in the software system? The answer suggested by the process of Separation of Concerns is no, and the corresponding term is called Persistence Ignorance. While this seems to be a straightforward concept, the attempt to implement it raises a lot of questions that may undermine the process as a whole.

A Can of Worms

There seems to be common sense in the statement that the engineers who are tasked with implementing the domain logic should not concern themselves with infrastructure related problems. They should operate in a world of domain objects that can be navigated in a hierarchy according to their relationships with each other. The objects should have methods to modify their state and extract results, rules to ensure their consistency and possibly events to notify other layers in the system. The hierarchy’s root is maintained by the application or a service layer on top of the domain model, so the question of where those objects come from does not arise. They are simply being added and removed as the application sees fit.

It appears to be desirable that the objects for such domain models are implemented using the most plain and ordinary features that a programming language provides, without requiring the use of complex or artificial conventions or frameworks. In Java, these objects are called Plain Old Java Objects (POJO), in the .NET Framework they are called Plain Old CLR Objects (POCO).

Implementing orthogonal persistence for such domain hierarchies is trivial. We simply provide a mechanism for taking a snapshot of the program state. This could be accomplished through different means, such as taking an image of the memory that will later be reconstituted. Everything will be exactly as it was before suspending execution, and nothing in the computer program would have to worry about how exactly it happened. However, this approach silently makes some important assumptions that generally don’t work in many real world applications.

First, it is assumed that the domain model is being handled by a single process on a single computer. For all but the simplest enterprise applications this is not the case. For scalability and performance, a problem is usually divided between multiple processes or multiple physical computers. Sharing a single memory location is possible, but leads to rather complicated remoting frameworks, and distributing your objects is generally not a recommended strategy anyway.

Second, it is assumed that the entirety of the object hierarchy resides in memory, and that a snapshot of the memory would preserve everything there is to know about the domain model and its state. This is almost never the case in reality, because of physical limitations. An online shop may have thousands of products, millions of customers and even more outstanding and past orders. In such cases it is not feasible to keep the entire domain in memory, and only a small portion of it can be handled a time.

Third, it is assumed that there is no cost associated with persisting and reconstituting domain objects. In practice this is not the case either, because interacting with non-volatile storage system is relatively inefficient and time consuming, even if the entire domain model happens to fit into memory.

Last but not least, it is assumed that all objects have an intrinsic identity that differentiates one object from another. For objects that reside in memory, this is their physical address, and relationships between objects are simply references or address pointers. When a memory snapshot is taken, this identity is preserved. However, when domain objects are distributed among different processes or computers, or serialized into files, or converted into database rows, this physical identity is lost and requires the introduction of facilities that can map between storage entities and domain objects.

Pandora’s Box of Solutions

Because of the physical limitations and performance concerns, orthogonal persistence is usually not an option for any large scale application. Custom, non-orthogonal logic is required to load and safe objects to a persistent storage facility. As with almost everything in computational sciences, the first step to a solution is to make compromises, but it somewhat spoils the beauty of the original plan.

The first compromise is to store the domain objects in a centralized location. It could be the memory of some specific process, but in most cases it is a file system or a database. This allows other processes and computers to access and share the objects, but it also implies that they are no longer working with the actual objects, but a copy of them. The in-memory representation needs to be tied to the storage entity. The design pattern that addresses this is called Identity Field, and it is very easy to implement.

The second compromise is to not represent the entire domain in memory. Instead, only the relevant objects that need to be accessed directly are represented in local memory. All other objects are being loaded on demand. This pattern is called Lazy Loading and can require quite a bit of trickery. For improved performance it is also possible to implement eager fetching mechanisms to load objects that have a high probability of being accessed, such as sub-objects or collections of objects in an aggregate.

Accessing non-volatile storage is expensive, and even if only a portion of the domain objects are loaded into memory, the cost of writing them all back for persistence may still be too high. A common approach to counter this is to save only those objects that were actually modified. With Object State Tracking it is possible to determine which objects need to be saved.

There are quite a few frameworks available that map domain models to storage models. Those that map to relational databases as the storage facility are called Object-Relational Mapping (ORM) solutions, such as Hibernate for Java and Entity Framework for the .NET Framework. Many of these solutions use the patterns discussed above and claim to work with persistence ignorant domain classes, more or less.

The problem is that the required compromises lead to solutions that are clearly not 100% persistence-ignorant. Identity fields require introducing at least one new field to domain objects ( usually a key) that would not be necessary otherwise. Getting lazy loading to work requires proxy classes that are generated at run-time. While these proxies are transparent to the domain model, they do require certain programming conventions in the domain model in order to work. Object state tracking is best handled with proxies as well, which make even more invasive assumptions on how domain classes and their properties are defined, or it can be done using dirty flags, which require another field that have nothing to do with the business domain. Of course, it is also possible to take snapshots of domain objects and compare for changes at the time of saving, but all those alternatives are much less elegant.

Why is it that these and similar related concerns are raised every time a new persistence framework is released or an old one is improved? After all, Persistence Ignorance is pretty well defined, or is it?

Persistence Ignorance Defined

Persistence Ignorance is a relatively recent term in software development. Although there are a lot of discussions on this topic, there is still no authoritative definition. However, there are a lot of good hints.

In his book Applying Domain-Driven Design and Patterns from 2006, Jimmy Nilsson says:

PI means clean, ordinary classes where you focus on the business problem at hand without adding stuff for infrastructure-related reasons.

And he adds that there should be no requirements for following certain programming conventions in order to make domain objects persistable. Although the implications of this statement are very clear, they were immediately put into perspective and subsequently not followed throughout the rest of the book. While this was perhaps the first clue that Persistence Ignorance cannot be taken too literally, many developers on the internet share convictions along the lines of:

When your classes are persistence ignorant, they don’t know anything about the data layer they are attached to and carry no dependency to said data layer.

In 2008, Dino Esposito touches in his book Architecting Applications for the Enterprise on Persistence Ignorance as an emerging concept and says:

Persistence ignorance (PI) is an application of the single responsibility principle [..] and, in practice, states that a domain object should not contain code related to persistence because bearing responsibility for both persistence and domain logic would violate the principle. [..] When an object model has no dependency on anything outside of it—and especially no dependencies on the layer that will actually persist it to a database—it is said to be a Persistence Ignorant (PI) model or a Plain Old CLR Object (POCO) model.

In a guide to Domain Driven Design from 2009 written by Casey Charlton, one can find the following:

[..] we should try and keep our code free of anything that refers to, relates to, or propagates, aspects of the mechanisms we intend to use for persisting our data [..]. With persistence ignorance, we try and eliminate all knowledge from our business objects of how, where, why or even if they will be stored somewhere.

Besides the fact that we should try, this definition is rather specific and does not leave much wiggle room. The terminology definition for persistence-ignorant objects in the Entity Framework for .NET is more flexible in this regard:

An object that does not contain any logic that is related to data storage. Also known as a POCO entity.

It is worth noting that here the emphasis is on logic, a greatly loosened constraint that no longer includes properties and their types or attributions. One may believe that this was an accident in phrasing, but more likely the Microsoft team was onto something.

In Domain Driven Design Using Naked Objects from 2009, Dan Haywood provides another interesting clue:

In Naked Objects, domain objects don’t know how persistence is done. However, that’s not the same thing as saying that they don’t know that persistence is done.

So what does it all mean?

With so many vague and diverging definitions and opinions on persistence-ignorance, it is important to make sure that we don’t get into a state of persistent ignorance.

A definition that is strict and appeals to the purest of purists could be as follows:

Persistence-ignorance is a characteristic of domain objects that do not contain any logic, references, attributions or data that are related to their storage.

In the larger scheme of things, starting with the original concepts and considering the compromises and widely accepted solutions, it is probably safe to say that non-orthogonal persistence ignorance in its purest form is not possible with the programming languages and tools that are in common use today. Even the least invasive toolsets have to make certain assumptions about how domain classes are defined, let it be through inheritance, mandatory fields or types, or certain required constructs.

Ideally, programming languages would provide constructs that support persistence-ignorance on the syntax level, so that these ingenious workarounds would no longer be required. In the meantime, a workable and possibly acceptable definition might be:

Persistence-ignorance is a characteristic of domain objects that do not contain any logic or references that are related to their storage.

This means that Persistence Ignorance does not imply that the domain model is ignoring that it can be persisted, but how it can be persisted. It does not allow for referencing layers that have to do with data storage and access, but permits properties and attributions that make persistence possible and more efficient. In this sense, current persistence frameworks, such as Hibernate and Entity Framework do in fact support persistence-ignorant objects, while in the stricter sense they do not.

Related Resources