Home arrow PHP arrow Building a Unit of Work in PHP: Applying a Transactional Model to Domain Objects

Building a Unit of Work in PHP: Applying a Transactional Model to Domain Objects

Are you trying to optimize the way your web application handles domain objects, and not finding a good solution? If caching won't help, and data and identity mappers won't suit your needs, you might want to look at using a Unit of Work pattern. What is a UoW? Keep reading to learn how it can help you.

TABLE OF CONTENTS:
  1. Building a Unit of Work in PHP: Applying a Transactional Model to Domain Objects
  2. Making the Unit of Work more functional: clearing entities and performing entity transactions
By: Alejandro Gervasio
Rating: starstarstarstarstar / 3
December 21, 2010

print this article
SEARCH DEV SHED

TOOLS YOU CAN USE

advertisement

One of the most common issues that must be addressed during the development of a large web application that exposes a prolific domain layer is how to optimize the operations that are performed on domain objects. Clearly, if numerous objects need to be fetched, saved to or deleted from the underlying persistence mechanism, which usually rests on a relational database, the overhead can be huge. In fact, it may bring the application to its knees.

Of course, a caching system in any of its flavors may be helpful in a case like this, and I'd be the first to pick one that fits my needs. However, sooner or later the objects in question must be retrieved, persisted or even removed, which means that multiple (and expensive) trips to the database are unavoidable.

There's no need to panic, though, as there exist some approaches to solving this problem, at least partially. For instance, a proper implementation of a data mapper together with an identity map can help to tackle the issue on two well-differentiated fronts: on one hand, the mapper would permit you to decouple domain objects from the persistence layer, thus addressing the so-called impedance mismatch; while on the other hand, the identity map would keep track of which objects have been fetched, and also assure that each of them would be fetched only once.

The major drawback with this approach is the intrinsic nature of an identity map, as it does reduce database trips, but only for retrieval operations. What about object insertions, updates and deletions? This is where an enterprise-level pattern known as Unit of Work (UoW) can help. Simply put, a UoW is an additional layer, usually placed between the domain and the mapping ones, which allows you to hold multiple domain objects in memory and "mark" them for further insertion, update or removal. The objects are queued deliberately in RAM until the actual database operations are performed in one go through a single method call.

While this transactional model permits you to save database trips, its functionality comes with an extra cost worth stressing. First off, a UoW requires you to implement an additional layer upon the existing ones, which in many cases may be overkill. And last but not least, it demands a careful manipulation of in-memory objects to maintain database integrity. With that said, you should consider whether or not your web project really needs to implement a UoW, given the hard upfront work that it demands. In short, be conscientious and evaluate its pros and cons with due care.

In addition, like any other pattern available out there, a UoW is "language agnostic," which means that it can be implemented in any programming language, including PHP. Since the experience can be  instructive, especially for those PHP developers wanting to be in touch with the trends imposed by modern web application development, in this article series I'll be building a simple UoW from scratch, so that you can grasp its underlying logic and see the actual benefits that it brings to the table.

Ready to take the first step of this hopefully educational journey? Then let's get started!

Getting started: defining an abstract Unit of Work

As noted in the introduction, a UoW queues multiple domain objects in memory, which are later inserted, updated or even removed from the persistence layer in a single operation. This resembles the transactional model present in many RDBMS. The question is, how does the UoW know which objects need to be inserted, updated or deleted?

In reality, there are several ways to accomplish this, but in many cases the tracking process is ruled by a simple convention that you'll find easy to follow. First, new domain objects created in memory will be marked "new," thus set for further insertion; second, objects previously fetched from the storage mechanism and then modified in some way will be marked "dirty" and set for further update. Finally, objects explicitly marked "removed" will obviously be deleted.

In addition, it's possible to implement a fourth state called "clean." Objects tracked this way will be left untouched. Of course, client code consuming the UoW can swap these states at any time according to specific requirements; for now, don't feel concerned about how this will be done, as the topic will be discussed in depth in upcoming tutorials of this series.

Having outlined basically how a UoW keeps track of domain objects, it's time to start creating a class capable of performing the aforementioned tasks. Below I defined a basic, yet functional abstract UoW, whose partial implementation looks like this: 
  
(UnitOfWorkAbstract.php)

<?php

abstract class UnitOfWorkAbstract
{
    protected $_newEntities = array();
    protected $_cleanEntities = array();
    protected $_dirtyEntities = array();
    protected $_removedEntities = array();
    protected $_dataMapper;
   
    /**
     * Class constructor
     */
    public function __construct(DataMapperAbstract $dataMapper)
    {
        $this->_dataMapper = $dataMapper;
    }
   
    /**
     * Get the data mapper the Unit of Work uses
     */         
    public function getDataMapper()
    {
        return $this->_dataMapper;
    }
   
    /**
     * Mark an entity 'new'
     */
    public function markNew(EntityAbstract $entity)
    {
        $id = $entity->id;
        if ($id !== null) {
            if (array_key_exists($id, $this->_cleanEntities)) {
                unset($this->_cleanEntities[$id]);
            }
            if (array_key_exists($id, $this->_dirtyEntities)) {
                unset($this->_dirtyEntities[$id]);
            }
            if (array_key_exists($id, $this->_removedEntities)) {
                unset($this->_removedEntities[$id]);
            }
        }
        if (!in_array($entity, $this->_newEntities, true)) {
            $this->_newEntities[] = $entity;
        }
    }
   
    /**
     * Remove an entity previously marked 'new'
     */
    protected function _removeNew(EntityAbstract $entity)
    {
        if (in_array($entity, $this->_newEntities, true)) {
            $newEntities = array();
            foreach ($this->_newEntities as $_newEntity) {
                if ($entity !== $_newEntity) {
                    $newEntities[] = $_newEntity;
                }
            }
            $this->_newEntities = $newEntities;
        }
    }
   
    /**
     * Get all the 'new' entities
     */
    public function getNewEntities()
    {
        return $this->_newEntities;
    }
   
    /**
     * Mark an entity 'clean'
     */
    public function markClean(EntityAbstract $entity)
    {
        $this->_removeNew($entity);
        $id = $entity->id;
        if ($id !== null) {
            if (array_key_exists($id, $this->_dirtyEntities)) {
                unset($this->_dirtyEntities[$id]);
            }
            if (array_key_exists($id, $this->_removedEntities)) {
                unset($this->_removedEntities[$id]);
            }
            if (!array_key_exists($id, $this->_cleanEntities)) {
                $this->_cleanEntities[$id] = $entity;
            }
        }
    }
   
    /**
     * Get all the 'clean' entities
     */
    public function getCleanEntities()
    {
        return $this->_cleanEntities;
    }
   
    /**
     * Mark an entity 'dirty'
     */
    public function markDirty(EntityAbstract $entity)
    {
        $this->_removeNew($entity);
        $id = $entity->id;
        if ($id !== null) {
            if (array_key_exists($id, $this->_cleanEntities)) {
                unset($this->_cleanEntities[$id]);
            }
            if (array_key_exists($id, $this->_removedEntities)) {
                unset($this->_removedEntities[$id]);
            }
            if (!array_key_exists($id, $this->_dirtyEntities)) {
                $this->_dirtyEntities[$id] = $entity;
            }
        }
    }
   
    /**
     * Get all the 'dirty' entities
     */
    public function getDirtyEntities()
    {
        return $this->_dirtyEntities;
    }
   
    /**
     * Mark an entity 'removed'
     */
    public function markRemoved(EntityAbstract $entity)
    {
        $this->_removeNew($entity);
        $id = $entity->id;
        if ($id !== null) {
            if (array_key_exists($id, $this->_cleanEntities)) {
                unset($this->_cleanEntities[$id]);
            }
            if (array_key_exists($id, $this->_dirtyEntities)) {
                unset($this->_dirtyEntities[$id]);
            }
            if (!array_key_exists($id, $this->_removedEntities)) {
                $this->_removedEntities[$id] = $entity; 
            }
        }
    }
   
    /**
     * Get all the 'removed' entities
     */
    public function getRemovedEntities()
    {
        return $this->_removedEntities;
    }
}

Don't let the lengthy definition of the previous class overwhelm you; the logic implemented by its methods is easy to follow. As you can see from the above code snippet, each tracking process in domain objects is performed by four discrete methods called "markNew()," "markClean()," "markDirty()" and "markRemoved()," which store the marked objects (in this case entities) in separate arrays. Also, the methods have some complementary getters, which I'll skip over at this time, since they can be grasped in a snap.

Although the definition of this abstract UoW is incomplete right now, it should be clearer for you to see how it does its thing; each domain object is marked "new," "clean," "dirty" or "removed" via plain array manipulation. It's that simple. Also, you may have noticed that the class' constructor injects an instance of an abstract mapper. Once again, don't worry about the implementation of this collaborator, since it will be covered in detail in another installment. 

So far, so good. In its current incarnation, the UoW is capable of assigning different states to the inputted entities, which is all well and good. You're now wondering where this class implements the transactional model, right? Well, to be frank, this functionality hasn't been added yet, but it'll be delegated to a separate method that will be discussed in the following section. 

So click on the link below and keep reading.



 
 
>>> More PHP Articles          >>> More By Alejandro Gervasio
 

blog comments powered by Disqus
escort Bursa Bursa escort Antalya eskort
   

PHP ARTICLES

- Hackers Compromise PHP Sites to Launch Attac...
- Red Hat, Zend Form OpenShift PaaS Alliance
- PHP IDE News
- BCD, Zend Extend PHP Partnership
- PHP FAQ Highlight
- PHP Creator Didn't Set Out to Create a Langu...
- PHP Trends Revealed in Zend Study
- PHP: Best Methods for Running Scheduled Jobs
- PHP Array Functions: array_change_key_case
- PHP array_combine Function
- PHP array_chunk Function
- PHP Closures as View Helpers: Lazy-Loading F...
- Using PHP Closures as View Helpers
- PHP File and Operating System Program Execut...
- PHP: Effects of Wrapping Code in Class Const...

Developer Shed Affiliates

 


Dev Shed Tutorial Topics: