Lazy load is a pattern to get data the data at the last moment, when it is actually required. This pattern is applied, when loading the data can have negative performance impact. One of the techniques to tackle this problem is pattern "Lazy load".
This is the quote from his book:
An object that doesn't contain all of the data you need but knows how to get it.
Martin fowler
Though Marting is describing the use-cases of using this pattern within the scope of interactions with the database, we can apply this pattern in other situations, where loading is expensive. For example resolve data in the UI via the REST API, if the payload is big, or if loading the full object may required the sophisticated graph of child objects to be resolved. If we do not need this data immediately, we can utilize lazy load pattern. This
This pattern has multiple implementations:
- Lazy initialization - this pattern pattern described first by a Kent Beck. Using this pattern allows to have object without all values. Values are resolved on demand in the "getter methods".
- Virtual Proxy - this pattern also allows object not to have all values. Proxy object lookups values in the real object, if values are absent "Proxy" knows how to resolve the values.
- Value Holder - it is a separate object from the value object. It holds the value or
null
If the value is absent, "Value Holder" knows how to load it. Use this pattern when you cannot use the partial data, or having the partial data is bigger overhead, than a simple value holder. - Object Ghost - I do not like this pattern, and will not describe it intentionally here. In the Martin Fowler describes this pattern in detail in his book(referenced below).
Saving this value for later, is the same as caching in memory. Be aware that you need to think about the cache invalidation in case you are saving the value in memory.
These are code samples for all of the implementation described above:
type Order = {} const resolveOrders = () => [{}] // Lazy initialization class Customer { _orders: Order[] private resolveOrders: () => Order[] constructor(resolveOrders: () => Order[]) { this.resolveOrders = resolveOrders } get orders() { if(this._orders) return this.orders this._orders = this.resolveOrders() return this._orders } } const lazyInitCustomer = new Customer(resolveOrders) // Virtual proxy interface CustomerData { orders: Order[] } const proxyCustomer = new Proxy<CustomerData>({orders: undefined as any}, { get(target, p) { if (p === 'orders') { if(target.orders) return target.orders target.orders = resolveOrders() return target.orders } }, }) // Value holder class ValueHolder<V> { private value: V | undefined private resolveValue: () => V constructor(resolveValue: () => V) { this.resolveValue = resolveValue } resolve(): V { if (this.value) return this.value this.value = this.resolveValue() return this.value } } const ordersHolder = new ValueHolder<Order[]>(resolveOrders)
Patterns of Enterprise Application Architecture: Martin Fowler, with Dave Rice, Matthew Foemmel, Edward Hieatt, Robert Mee, and Randy Stafford
Note: The content in this article is a summary/interpretation inspired by them. Proper attribution is given to the respective authors.