Monday, November 16, 2009

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



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);


As you can see, 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


As you can see Ruby makes it even more succint and provides a lot more options for higher order functions such as monkey patching, lambdas/procs/methods, send, delegates/forwardables, etc.



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


Adaptive Object Model based on dynamic languages with support metaprogramming and generative programming provide powerful techniques to meet increasingly changing requirements and 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.