Shahzad Bhatti

November 16, 2009

Applying Adaptive Object Model using dynamic languages and schema-less databases

Filed under: Java — admin @ 3:10 pm

Introduction to Adaptive/Active Object Model

Adaptive or Active Object Model is a design pattern used in domains that requires dynamic manipulation of meta information.
Though, it is quite extensive topic of research, but general idea from original paper of
Ralph Johnson is to treat meta information such as attributes,
rules and relationships as a data. It is usually used when the number of sub-classes is huge or unknown upfront and the system requires adding new functionality without downtime.
For example, let’s say we are working in automobile domain and we need to model different type of vehicles. Using an object oriented design would result in vehicle hierarchy such as follows:

In above example, all type hierarchy is predefined and each class within the hierarchy defines attributes and operations. Adaptive Object Modeling on the other hand use Object Type pattern, which treats classes like objects. The basic Adaptive Object Model uses type square model such as:

In above diagram, EntityType class represents all classes and instance of this class defines actual attributes and operations supported by the class. Similarly, PropertyType defines names and types of all attributes. Finally, instance of Entity class will actual be real object instance that would store collection of properties and would refer to the EntityType.

Java Implementation

Let’s assume we only need to model Vehicle class from above vehicle hierarchy. In a typical object oriented language such as Java, the Vehicle class would be defined as follows:

  1 /*
  2  * Simple Vehicle class
  3  * 
  4  */
 
  5 package com.plexobject.aom;
  6 
  7 import java.util.Date;
  8 
 
  9 public class Vehicle {
 10 
 11     private String maker;
 12     private String model;
 
 13     private Date yearCreated;
 14     private double speed;
 15     private long miles;
 
 16     //... other attributes, accessors, setters
 17 
 18     public void drive() {
 19         //
 
 20     }
 21 
 22     public void stop() {
 23         //
 
 24     }
 25 
 26     public void performMaintenance() {
 27         //
 28     }
 
 29     //... other methods
 30 }
 31 
 32 
 33 
 

As you can see all attributes and operations are defined within the Vehicle class. The Adaptive Object Model would use meta classes such as Entity, EntityType, Property and PropertyType to build the Vehicle metaclass. Following Java code defines core classes of type square model:

The Property class defines type and value for each attribute of class:

  1 /*
  2  * Property class defines attribute type and value
  3  * 
  4  */
 
  5 package com.plexobject.aom;
  6 
  7 public class Property {
 
  8 
  9     private PropertyType propertyType;
 10     private Object value;
 11 
 12     public Property(PropertyType propertyType, Object value) {
 
 13         this.propertyType = propertyType;
 14         this.value = value;
 15     }
 16 
 17     public PropertyType getPropertyType() {
 
 18         return propertyType;
 19     }
 20 
 21     public Object getValue() {
 22         return value;
 
 23     }
 24     //... other methods
 25 }
 26 
 27 
 

The PropertyType class defines type information for each attribute of class:

  1 /*
  2  * PropertyType class defines type information
  3  * 
  4  */
 
  5 package com.plexobject.aom;
 
  6 
  7 public class PropertyType {
  8 
  9     private String propertyName;
 
 10     private String type;
 11 
 12     public PropertyType(String propertyName, String type) {
 13         this.propertyName = propertyName;
 14         this.type = type;
 
 15     }
 16 
 17     public String getPropertyName() {
 18         return propertyName;
 19     }
 
 20 
 21     public String getType() {
 22         return type;
 23     }
 24     //... other methods
 
 25 }

The EntityType class defines type of entity:

  1 /*
  2  * EntityType class defines attribute types and operations
  3  * 
  4  */
  5 package com.plexobject.aom;
 
  6 
  7 import java.util.Collection;
  8 import java.util.HashMap;
  9 import java.util.Map;
 
 10 
 11 public class EntityType {
 12 
 13     private String typeName;
 14     private Map<String, PropertyType> propertyTypes = new HashMap<String, PropertyType>();
 
 15     private Map<String, Operation> operations = new HashMap<String, Operation>();
 16 
 17     public EntityType(String typeName) {
 
 18         this.typeName = typeName;
 19     }
 20 
 21     public String getTypeName() {
 22         return typeName;
 
 23     }
 24 
 25     public void addPropertyType(PropertyType propertyType) {
 26         propertyTypes.put(propertyType.getPropertyName(),
 27                 propertyType);
 
 28     }
 29 
 30     public Collection<PropertyType> getPropertyTypes() {
 31         return propertyTypes.values();
 
 32     }
 33 
 34     public PropertyType getPropertyType(String propertyName) {
 35         return propertyTypes.get(propertyName);
 36     }
 
 37 
 38     public void addOperation(String operationName, Operation operation) {
 39         operations.put(operationName, operation);
 40 
 41     }
 
 42 
 43     public Operation getOperation(String name) {
 44         return operations.get(name);
 45     }
 46 
 
 47     public Collection<Operation> getOperations() {
 48         return operations.values();
 49     }
 50     //... other methods
 
 51 }
 52 
 53 
 

The Entity class defines entity itself:

  1 /*
  2  * Entity class represents instance of actual metaclass
  3  * 
  4  */
  5 package com.plexobject.aom;
 
  6 
  7 import java.util.Collection;
  8 import java.util.Collections;
  9 
 
 10 public class Entity {
 11 
 12     private EntityType entityType;
 13     private Collection<Property> properties;
 
 14 
 15     public Entity(EntityType entityType) {
 16         this.entityType = entityType;
 17     }
 18 
 19     public EntityType getEntityType() {
 
 20         return entityType;
 21     }
 22 
 23     public void addProperty(Property property) {
 
 24         properties.add(property);
 25     }
 26 
 27     public Collection<Property> getProperties() {
 28         return Collections.unmodifiableCollection(properties);
 
 29     }
 30 
 31     public Object perform(String operationName, Object[] args) {
 32         return entityType.getOperation(operationName).perform(this, args);
 
 33     }
 34     //... other methods
 35 }

The Operation interface is used for implementing behavior using Command pattern:

  1 /*
  2  * Operation interface defines behavior
  3  * 
  4  */
  5 package com.plexobject.aom;
 
  6 
  7 public interface Operation {
  8 
  9     Object perform(Entity entity, Object[] args);
 
 10 }

Above meta classes would be used to create classes and objects. For example, the type information of Vehicle class would be defined in EntityType and PropertyType and the instance would be defined using Entity and Property classes as follows. Though, in real applications, type binding would be stored in XML configuration or will be defined in some DSL, but I am binding programmatically below:

  1 /*
  2  * an example of binding attributes and operations of Vehicle
  3  * 
  4  */
 
  5 package com.plexobject.aom;
  6 
  7 import java.util.Date;
  8 
 
  9 
 10 public class Initializer {
 11 
 12     public void bind() {
 
 13         EntityType vehicleType = new EntityType("Vehicle");
 14         vehicleType.addPropertyType(new PropertyType("maker",
 15                 "java.lang.String"));
 
 16         vehicleType.addPropertyType(new PropertyType("model",
 17                 "java.lang.String"));
 18         vehicleType.addPropertyType(new PropertyType("yearCreated",
 
 19                 "java.util.Date"));
 20         vehicleType.addPropertyType(new PropertyType("speed",
 21                 "java.lang.Double"));
 22         vehicleType.addPropertyType(new PropertyType("miles",
 
 23                 "java.lang.Long"));
 24         vehicleType.addOperation("drive", new Operation() {
 25 
 26             public Object perform(Entity entity, Object[] args) {
 
 27                 return "driving";
 28             }
 29         });
 30         vehicleType.addOperation("stop", new Operation() {
 
 31 
 32             public Object perform(Entity entity, Object[] args) {
 33                 return "stoping";
 34             }
 35         });
 
 36         vehicleType.addOperation("performMaintenance", new VehicleMaintenanceOperation());
 37 
 38 
 39         // now creating instance of Vehicle
 40         Entity vehicle = new Entity(vehicleType);
 
 41         vehicle.addProperty(new Property(vehicleType.getPropertyType("maker"),
 42                 "Toyota"));
 43         vehicle.addProperty(new Property(vehicleType.getPropertyType("model"),
 
 44                 "Highlander"));
 45         vehicle.addProperty(new Property(vehicleType.getPropertyType("yearCreated"),
 46                 new Date(2003, 0, 1)));
 
 47         vehicle.addProperty(new Property(vehicleType.getPropertyType("speed"), new Double(120)));
 48         vehicle.addProperty(new Property(vehicleType.getPropertyType("miles"), new Long(3000)));
 
 49         vehicle.perform(
 50                 "drive", null);
 51 
 52     }
 53 }
 
 54 
 55 
 

The operations define runtime behavior of the class and can be defined as closures (anonymous classes) or external implementation such as VehicleMaintenanceOperation as follows:

  1 /*
 
  2  * an example of operation
  3  * 
  4  */
 
  5 package com.plexobject.aom;
  6 
  7 class VehicleMaintenanceOperation implements Operation {
 
  8 
  9     public VehicleMaintenanceOperation() {
 10     }
 11 
 12     public Object perform(Entity entity, Object[] args) {
 
 13         return "maintenance";
 14     }
 15 }
 16 
 17 
 
 

In real applications, you would also have meta classes for business rules, relationships, strategies, validations, etc as instances. As, you can see AOM provides powerful way to adopt new business requirements and I have seen it used successfully while working as consultant. On the downside, it requires a lot of plumbing and tooling support such as XML based configurations or GUI tools to manipulate meta data. I have also found it difficult to optimize with relational databases as each attribute and operation are stored in separate rows in the databases, which results in excessive joins when building the object. There are a number of alternatives of Adaptive Object Model such as code generators, generative techniques, metamodeling, and table-driven systems. These techniques are much easier with dynamic languages due to their support of metaprogramming, higher order functions and generative programming. Also, over the last few years, a number of schema less databases such as CouchDB, MongoDB, Redis, Cassendra, Tokyo Cabinet, Riak, etc. have become popular due to their ease of use and scalability. These new databases solve excessive join limitation of relational databases and allow evolution of applications similar to Adaptive Object Model. They are also much more scalable than traditional databases. The combination of dynamic languages and schema less databases provides a simple way to add Adaptive Object Model features without a lot of plumbing code.

Javascript Implementation

Let’s try above example in Javascript due to its supports of higher order functions, and prototype based inheritance capabilities. First, we will need to add some helper methods to Javascript (adopted from Douglas Crockford’s “Javascript: The Good Parts”), e.g.

  1 
  2 if (typeof Object.beget !== 'function') {
 
  3     Object.beget = function(o) {
  4         var F = function() {};
  5         F.prototype = o;
 
  6         return new F();
  7     }
  8 }
  9 
 
 10 Function.prototype.method = function (name, func) {
 11     this.prototype[name] = func;
 12     return this;
 13 };
 
 14 
 15 
 16 Function.method('new', function() {
 17     // creating new object that inherits from constructor's prototype
 
 18     var that = Object.beget(this.prototype);
 19     // invoke the constructor, binding -this- to new object
 
 20     var other = this.apply(that, arguments);
 21     // if its return value isn't an object substitute the new object
 
 22     return (typeof other === 'object' && other) || that;
 23 });
 24 
 
 25 Function.method('inherits', function(Parent) {
 26     this.prototype = new Parent();
 27     return this;
 
 28 });
 29 
 30 Function.method('bind', function(that) {
 31     var method = this;
 
 32     var slice = Array.prototype.slice;
 33     var args = slice.apply(arguments, [1]);
 34     return function() {
 35         return method.apply(that, args.concat(slice.apply(arguments,
 
 36             [0])));
 37     };
 38 });
 39 
 40 // as typeof is broken in Javascript, trying to get type from the constructor
 
 41 Object.prototype.typeName = function() {
 42     return typeof(this) === 'object' ? this.constructor.toString().split(/[\s\(]/)[1] : typeof(this);
 
 43 };
 44 
 45 
 

There is no need to define Operation interface, Property and PropertyType due to higher order function and dynamic language support. Following Javascript code defines core functionality of Entity and EntityType classes, e.g.:

  1 
  2 var EntityType = function(typeName, propertyNamesAndTypes) {
 
  3     this.typeName = typeName;
  4     this.propertyNamesAndTypes = propertyNamesAndTypes;
  5     this.getPropertyTypesAndNames = function() {
 
  6         return this.propertyNamesAndTypes;
  7     };
  8     this.getPropertyType = function(propertyName) {
 
  9         return this.propertyNamesAndTypes[propertyName];
 10     };
 11     this.getTypeName = function() {
 12         return this.typeName;
 
 13     };
 14     var that = this;
 15     for (propertyTypesAndName in propertyNamesAndTypes) {
 
 16         that[propertyTypesAndName] = function(name) {
 17             return function() {
 18                 return propertyNamesAndTypes[name];
 
 19             };
 20         }(propertyTypesAndName);
 21         
 22     }
 
 23 };
 24 
 25 
 26 
 27 var Entity = function(entityType, properties) {
 28     this.entityType = entityType;
 
 29     this.properties = properties;
 30     this.getEntityType = function() {
 31         return this.entityType;
 32     };
 
 33     var that = this;
 34     for (propertyTypesAndName in entityType.getPropertyTypesAndNames()) {
 35         that[propertyTypesAndName] = function(name) {
 
 36             return function() {
 37                 if (arguments.length == 0) {
 38                     return that.properties[name];
 39                 } else {
 
 40                     var oldValue = that.properties[name];
 41                     that.properties[name] = arguments[0];
 42                     return oldValue;
 43                 }
 44             };
 
 45         }(propertyTypesAndName);
 46         
 47     }
 48 };
 
 

Following Javascript code shows binding and example of usage (again in real application binding will be stored in configurations):

  1 
  2 var vehicleType = new EntityType('Vehicle', {
 
  3     'maker' : 'String',              // name -> typeName
  4     'model' : 'String',
 
  5     'yearCreated' : 'Date',
  6     'speed' : 'Number',
  7     'miles' : 'Number'
 
  8 });
  9 
 10 var vehicle = new Entity(vehicleType, {
 11     'maker' : 'Toyota',
 
 12     'model' : 'Highlander',
 13     'yearCreated' : new Date(2003, 0, 1),
 14     'speed' : 120,
 
 15     'miles' : 3000
 16 });
 17 
 18 vehicle.drive = function() {
 19     }.bind(vehicle);
 
 20 
 21 vehicle.stop = function() {
 22     }.bind(vehicle);
 23 
 24 vehicle.performMaintenance = function() {
 
 25     }.bind(vehicle);

A big difference with dynamic languages is that you can bind properties operations to the objects at runtime so you can invoke them as if they were native. For example, you can invoke vehicleType.maker() to get maker property of the vehicle-type or call vehicle.drive() to invoke operation on vehicle object. Another difference is that a lot of plumbing code disappears with dynamic languages.

Ruby Implementation

Similarly, above example in Ruby may look like:

  1 require 'date'
 
  2 require 'forwardable'
  3 class EntityType
  4   attr_accessor :type_name
 
  5   attr_accessor :property_names_and_types
  6   def initialize(type_name, property_names_and_types)
  7     @type_name = type_name
 
  8     @property_names_and_types = property_names_and_types
  9   end
 10   def property_type(property_name)
 11     @property_names_and_types[property_name]
 
 12   end
 13 end
 14 
 15 
 16 class Entity
 
 17   attr_accessor :entity_type
 18   attr_accessor :properties
 19   def initialize(entity_type, attrs = {})
 
 20     @entity_type = entity_type
 21     bind_properties(entity_type.property_names_and_types)
 22     attrs.each do |name, value|
 23       instance_variable_set("@#{name}", value)
 
 24     end
 25   end
 26   def bind_properties(property_names_and_types)
 27     (class << self; self; end).module_eval do
 
 28       property_names_and_types.each do |name, type|
 29         define_method name.to_sym do
 30           instance_variables_get("@#{name}")
 
 31         end
 32         define_method name.to_sym do
 33           instance_variables_set("@#{name}", value)
 
 34         end
 35       end
 36     end
 37   end
 38 end
 
 39 
 66 
 67 
 68 
 

We can then use Singleton, Lambdas and metaprogramming features of Ruby to add Adaptive Object Model support, e.g.

  1 vehicle_type = EntityType.new('Vehicle', {
 
  2     'maker' => 'String',             # class.name
  3     'model' => 'String',
 
  4     'yearCreated' => 'Time',
  5     'speed' => 'Fixnum',
 
  6     'miles' => 'Float'});
  7 
  8 
  9 vehicle = Entity.new(vehicle_type, {
 
 10     'maker' => 'Toyota',
 11     'model' => 'Highlander',
 12     'yearCreated' => DateTime.parse('1-1-2003'),
 
 13     'speed' => 120,
 14     'miles' => 3000});
 15 class << vehicle
 
 16   def drive
 17     "driving"
 18   end
 19   def stop
 
 20     "stopping"
 21   end
 22   def perform_maintenance
 23     "performing maintenance"
 
 24   end
 25 end
 26 
 27 
 

Ruby code is a lot more succint and as Ruby supports adding or removing methods dynamically, you can invoke properties and operations directly on the objects. For example, you can invoke vehicleType.maker() to get maker property of the vehicle-type or call vehicle.drive() to invoke operation on vehicle object. Also, Ruby provides a lot more options for higher order functions such as monkey patching, lambdas/procs/methods, send, delegates/forwardables, etc. Finally, Ruby provides powerful generative capabilities to build DSL that can bind all properties and operations at runtime similar to how Rails framework work.

Schema-less Databases

Now, the second half of the equation for Adaptive Object Model is persisting, which I have found to be challenge with relational databases. However, as I have been using schemaless databases such as CouchDB, it makes it trivial to store meta information as part of the plain data. For example, if I have to store this vehicle in CouchDB, all I have to do is create a table such as vehicles (I could use Single Table Inheritance to store all types of vehicles in same table):

 curl -XPUT http://localhost:5984/vehicles
 curl -XPUT http://localhost:5984/vehicle_types
 

and then add vehicle-type as

 curl -XPOST http://localhost:5984/vehicle_types/ -d '{"maker":"String", "model":"String", "yearCreated":"Date", "speed":"Number", "miles":"Number"}'
 

which returns

 {"ok":true,"id":"bb70f95e43c3786f72cb46b372a2808f","rev":"1-3976038079"}
 

Now, we can use the id of vehicle-type and add vehicle a follows

 curl -XPOST http://localhost:5984/vehicles/ -d '{"vehicle_type_id":"bb70f95e43c3786f72cb46b372a2808f", "maker":"Toyota", "model":"Highlander", "yearCreated":"2003", "speed":120, "miles":3000}'
 

which returns id of newly created vehicle as follows:

 {"ok":true,"id":"259237d7c041c405f0671d6774bfa57a","rev":"1-367618940"}
 

Summary

It is often said in software development that you can solve any problem with another level of indirection. Adaptive Object Model uses another level of indirection to create powerful applications that meet increasingly changing requirements. When it is used with dynamic languages that support metaprogramming and generative programming, it can be used build systems that can be easily evolved with minimum changes and downtime. Also, Schema-less databases eliminates drawbacks of many implementations of AOM that suffer from poor performance due to excessive joins in the relational databases.

Powered by WordPress