Here at RJMetrics, we like to experiment — especially with ideas that have proven successful for other companies. For this reason, like many startups before us, we have adopted Google’s 20% time policy. Huzzah! Because this is an experiment, we defined a fixed trial period of three months. We also cut it down from 20% to 10%. This fits well with our 10 day sprint cycles, and seemed like a good way to get started. After three months, we will assess the results and decide where to go from there.

What is 20% time?

In case you haven’t heard:

Google engineers are encouraged to spend 20% of their work time on projects that interest them. Some of Google’s newer services, such as Gmail, Google News, Orkut, and AdSense originated from these independent endeavors. (Wikipedia)

Although big, amazing things are always a possibility, I don’t expect any of us to develop the next Gmail (RJmail?). I do expect lots of little, amazing things to come from this, such as small improvements and optimizations of our code and simple utilities that solve daily annoyances.

What did I do with my 10% time?

I experimented with implementing a general lazy loading solution in PHP for properties of model objects that reference other objects. Nothing I did was groundbreaking. But these are the sort of optimizations that can easily be overlooked in a fast-paced development environment where there are many competing priorities. That is, until 10% time comes along with a respite from the team todo list.

What’s the problem?

As you might expect, our codebase contains many class definitions for model objects, and by that I mean, objects whose primary purpose is to hold data and map roughly to a record in a database table. Given our business, an obvious example is the Chart object. It has some simple scalar properties such as name, time range and color scheme. But Charts also have relationships to other models, such as:

  • the user who owns the chart
  • the client whose data is displayed by the chart
  • the trend that provides the data

The question is: does the Chart object have a property that directly references each of these related model objects? And if so, does that mean we must load and construct each of these objects in order to build a Chart object? These questions are answered by the implementation details of our Data Access Objects (DAO, pronounced “Day-O”, like in Harry Belafonte’s Banana Boat song).

Data Access Objects (or “mappers”, as we affectionately call them at RJMetrics) handle the process of constructing model objects from the data in our database, and also saving the data back to the database. If one model object relates to another we might load them at the same time, which allows us to do this:

But, this can be expensive. Imagine we want to display a list of Charts. We might be loading many, many Charts and only using the ‘name’ property for each. In this scenario, also loading up each of the user, client and trend model objects is unnecessary and may introduce undesirable delays.

If we have implemented our mappers to not load related objects with the Chart object initially, but we are in a situation where we do need these objects, we might end up with code like this:

Look at all those mappers! What a mess. The previous code example is much cleaner. Can I please have my cake and eat it too? Probably!

There are corresponding changes that need to be made both in the model objects and mappers, but the core of this solution is stupid simple. Here’s the PHP:

What is going on here? Memoization! I dislike saying this word almost as much as I love the concept. The above example defines a class that encapsulates a single property which is either a value or a closure that provides a value. If it is a closure, the first time the LazyLoader is invoked, the result is memoized, stored in memory for later reuse. The closure is discarded and never called again. This is fine as long as we don’t expect updates to the database record to automatically propagate to this property.

How do we use it?

To enable lazy loading for a property, we must make some simple changes to the model object and corresponding mapper. Here’s an absurdly simple example:

Above we have a simplified Chart class with two public properties, “name” and “user”, with getters and setters for each. This demonstrates the difference between a typical property implementation and a lazy one. Instead of storing a value, we store a LazyLoader object and it handles the details of memoization for us.

The other end of our modifications lie in the DAO, the ChartMapper class.

We’ve hidden away the call to the userMapper in the closure that is passed to the LazyLoader by the Chart object. Now this object is loaded on demand without having to mess with additional mappers in the calling code. Success! We can enjoy eating our cake that we still have.

Things get more fun when you realize the power of stringing lazily-loaded property calls together.

The line of code above might be used to get a list of all the users a given chart could be shared with. Incidental complexity has been stripped away. Who knows how many mappers are involved in the line above? Not me. That’s a detail that need not concern me as long as the line is functioning and performing well.

Tags:
  • Naji

    Wow, i just love this article!

    I tried to figure out how to decouple my dao and entities (like doctrine) while having non-fetched properties to be lazy-loaded.

    As the LazyLoader class just perfectly fits in my open source project (https://github.com/rejinka/xbmc-api), i’ve added it. I wrote a comment, which indicates, that you are the author. Could you please tell me if it is okay to just include your class?

    Thanks anyway!

    • http://rjmetrics.wpengine.com rjmetrics

      Sure, go for it!

  • Marek

    Great article, but I don’t see any lines with code 🙁 I use Firefox, but the same is on Chrome, Opera, IE.

    • Bill Piel

      Fixed it!

      • sphoid

        It seems to be unfixed. I don’t see any code.

      • Frano

        I dont see code too 🙁

      • pon stan

        Hi, I see the code but they are all the same copy for each part so I could not follow the evolution of your code story.