Next: , Previous: Checkpointing Conventional Program State, Up: Design Patterns


6.1 Persistent System Objects

The simplest design pattern supported by Elephant is the use of persistent objects in the place of standard objects. Typically you can just modify the old class definition to inherit the persistent-metaclass. Depending on your application, objects may need to have transient slots for performance reasons. We'll create a dummy class to illustrate:

     (defclass system-object ()
       ((appname :accessor system-appname :initarg :name)
        (url :accessor system-url :initarg :url)
        (state :accessor system-state :initarg :state :initform 'idle))
       (:metaclass persistent-metaclass))

When starting up your application you need to recover references to any persistent objects that were created in a prior session or initialize a new one.

If you are storing system objects in parameters, you can just call an initialization function on startup:

     (defparameter *system* nil)
     
     (defun initialize-system (appname)
       (let ((system-object (get-from-root '*system*)))
         (setf *system
               (if system-object system-object
                   (make-instance 'system-object :name appname)))))
     
     *system*
     => #<SYSTEM-OBJECT ...>

And now you can use your parameter as you did before. If you want to avoid calling initialization functions, you can just accesss system objects through functions instead of parameters.

     (defparameter *system* nil)
     
     (defun sys-object ()
       (unless *system
         (let ((appname (get-application-name))
               (url (get-system-url)))
           (setf *system* (make-instance 'system-object
                                         :name appname
                                         :url url))))
       *system*)
     
     (sys-object)
     => #<SYSTEM-OBJECT ...>

One constraint to keep in mind is that slot access will be slower as it has to synchronize to disk. This is usually not noticable for objects that are accessed on the order of seconds instead of milliseconds. For objects read constantly, but where you want to save any written values it helps to have a transient slot to cache values. You can override some methods to ensure that the persistent value is always updated, but that reads happen from the cached value and that the cached value is restored whenever the object is loaded.

     (defclass system-object ()
       ((appname :accessor system-appname :initarg :name)
        (url :accessor system-url :initarg :url)
        (laststate :accessor system-laststate :initarg :state
                   :initform 'idle)
        (state :accessor system-state :initarg :state :transient t)
       (:metaclass persistent-metaclass))
     
     (defmethod (setf system-state) :after (state (sys system-state))
       (setf (system-laststate sys) state))
     
     (defmethod initialize-instance :after ((sys system-state) &rest rest)
       (declare (ignore rest))
       (when (slot-boundp sys 'laststate)
         (setf (system-state sys) (system-laststate sys))))

And now you have an instant read cache for a slot value. This pattern is used several times within the Elephant implementation.