Refactoring to Adaptive Object Modeling: Discussions on Practical Project usefulness of design.
Introduction
Adaptive Object Modeling is a relatively new concept in object oriented design that has found some roots in advanced development processes. It allows that applications are in continual flux (or their requirements are) from adjustments to business rules, environments and user requirements. It's precepts are that using metadata for business objects, rules and process flows, instead of hard coded, static method code, makes the application more flexible to user/client needs. The AOM design model also requires that certain domain 'facts' are maintained, i.e. that there are controlled interpretations of the effects of the 'immediate' changes from user-generated metadata.
To better understand and practice the techniques of AOM design, I am writing this and several other articles that explain the usage and conversion (or refactoring effort) from a static code model to AOM design.
This article will deal with how to make useful in actual projects the AOM pattern methodology. I skipped ahead a bit to write this article (relationship and interpreter articles will come later) because I saw a real lack of understanding revolving around real world applications and usage of this model. This article will deal with application, implementation and conversion techniques for current application code to the AOM Model. Don't get confused. This will not be a regurgitation of the current writings on the methodology, but (I hope) a different approach to describing the methodology intended to help build competencies in the pattern. When I am done you may think this is not AOM at all, but what I am trying to get across is some relevant ideas that the model suggests. I will pick apart different pieces of the overall model in this article, so I can better illustrate the different ways you might use the disparate pieces in projects.
BackGround
Let us first put aside some common misconceptions. First, this is not the holy grail. It is a pattern, and the usefulness of a pattern is how it can be applied to real world coding. While the pattern may appear overly complex, we will attempt in this article to only use pieces of the puzzle in different ways, in hopes we might shed some light on the cognizant whole.
Here we see a version of the Type Square Adaptive Object Modeling class pattern UML based on the ECOOP & OOPSLA 2001 Yoder, Balaguer, Johnson model. Notice that the Entity object accepts and has specific operations (or strategies) and attributes (or properties) associated with it. This is not the exact UML of course for type square, but it is an accurate representation for this article of what I would like to present. We have class types called entities, entity types that include operational strategies, and attributes as properties. We have left off entity relationships at this time, so we can better illustrate some pieces that I feel hold more value. Not that the Entity-Relationship pattern should be ignored, it is just out of scope, for the time being, of this particular article.
How to use the code
As we saw above in the UML we have linked operational methods and attributes
to a class, and they are not part of the class itself, but exist as component
parts linked through the class EntityType
. This is the most useful
and interesting piece of the pattern I have found, due to it's unique flexibility
of design. Imagine, you build a class, then your business user tells you that
need to add some more functionality to the class. Well normally you would shake
your head, and go and recompile the class code with the changes. This is costly
in build time, as well you have to factor in the impact to your current design.
But what if you could add any attributes and/or methods you wished without the
recompile? What if you could link entire assemblies together at runtime, simply
by changing some configuration data? I will show you how easy it is.
First lets look at the most poignant aspect I found of the AOM model. We will
attempt to re-write classes with immutable compiled methods into Entity and
EntityType patterns, and define these same methods in another assembly. We start
out with the class EmployeeJob
, which has two methods DoSomeWork
and GoofOff
.
public class EmployeeJob { public EmployeeJob() { } public void DoSomeWork(string typeOfWork) { //some functional code here } public void GoofOff() { //some functional code here } }
Lets say we wanted this class to have 4 new methods: GoToLunch(string
restaurant)
, TakeABreak()
, WriteAReport(string reportName)
and ClockOut()
. We could add them to the class itself, but we have
no way of telling when we will have to add methods again, and we also need to
add these methods to a new class we will create: ExecutiveJob
.
We could create a parent class containing the common methods and subclass EmployeeJob
and ExecutiveJob
, but if the code base grows very large and we
have a large variety of job types, this can become unmanageable. Let's say that
we know that sometime in the future fifty new job types will be added, each
with it's own specific needs for functionality. That would mean (at least) fifty
new subclasses just like EmployeeJob
and ExecutiveJob
.
Wow! How do we deal with all those classes. AOM can help here. With the AOM
model as our base we can create virtual classes at runtime with all
the necessary attributes and methods we need for each class, using configuration
meta-data. Meta-data as we know is machine understandable data that
describes object states (not values). Lets look at how we start.
Lets first start by providing a way to retrieve operational data from any assembly.
Below we see an interface and a collection that will hold these interfaces.
The interfaces will be a link to all the possible methods in any assembly, that
exists in a class that inherits from IOperation
and implements
the method OperationMethod
. We give OperationMethod
a very generic parameter list object[] parameters
, which allows
us to pass arguments in any number and type to the method. This gives us the
flexibility to define any method strategy desired in the implementation class.
We also have a collection object which will hold these operational strategy
methods inside the specific EntityType.
NOTE: We might replace this later with a delegate pointer in the interface to the method on the implementation class, to better type the actual method call.
public interface IOperation { void OperationMethod(object[] parameters); } //Collection of attributes public class OperationsCollection { public void Add(IOperation obj) { //.....addition code to collection } }
Our meta-data is pulled for this example from a config file (xml). Notice that
each operation definition has xml attributes: name, assembly and type. The assembly
xml attribute defines which assembly (or .dll) you wish to pull the method from.
The type xml attribute tells us which actual class to use, that inherits from
IOperation
and implements the method OperationMethod
.
We now need to build each class in it's own assembly that we want as a strategy,
and implement the interface method OperationMethod
. The config
file excerpt below shows how we want to define the classes, and their container
assemblies.
<entityOperations>
<entityOperation name="DoSomeWork"
assembly="OperationalStrategies"
type="OperationalStrategies.DoSomeWork" />
<entityOperation name="GoofOff"
assembly="OperationalStrategies"
type="OperationalStrategies.GoofOff" />
<entityOperation name="GoToLunch"
assembly="OperationalStrategies"
type="OperationalStrategies.GoToLunch" />
<entityOperation name="TakeABreak"
assembly="OperationalStrategies"
type="OperationalStrategies.TakeABreak" />
<entityOperation name="WriteAReport"
assembly="OperationalStrategies"
type="OperationalStrategies.WriteAReport" />
<entityOperation name="ClockOut"
assembly="OperationalStrategies"
type="OperationalStrategies.ClockOut" />
</entityOperations>
NOTE:The above xml is considered meta-data for this application example.
Each one of these method strategies can be added dynamically to the class type via it's EntityType class at runtime.
We now need to create functional method objects inheriting from IOperation
,
which define exact method operations we will allow to be adapted to our entity
object. Having the interface allows us to tie that interface to any class method
we wish, as long as that class implements IOperation
.
NOTE: The operational methods could be contained in other assemblies, the namespace
encapsulation becomes unimportant, because you can add attributes or methods
via the metadata, without concern to it's source at compile time. Also the operation
method interface IOperation
could be changed to dynamically define
strongly type parameters as well, but this will be saved for either another
article or your own invention. Remember, reflection is used heavily as we will
see in the factory class, and is the key to dynamic representation of contractual
interfaces.
public class DoSomeWork : IOperation { void IOperation.OperationMethod(object[] parameters) { Console.WriteLine("..." + this.GetType().Name + ".OperationMethod method called."); foreach(object obj in parameters) Console.WriteLine("... parameter Type:" + obj.GetType() + " Value:" + obj); } } public class GoofOff : IOperation { void IOperation.OperationMethod(object[] parameters) { Console.WriteLine("..." + this.GetType().Name + ".OperationMethod method called."); foreach(object obj in parameters) Console.WriteLine("... parameter Type:" + obj.GetType() + " Value:" + obj); } } .....other methods
Now that we have built out both our meta-data and the interface links to our classes, we can define our EntityTypes, which are like templates for our different class types. The EntityType classes allow us to map groupings of types to a specific entity. EntityType is part of Entity and there is a one to one relationship. Think of entities as virtual classes, and entity types virtual container definitions for all possible attribute and operational data available to a specific entity.
Consider this example. ExecutiveJobEntityType
does not exist as
compiled code, but gives us a relative type template for defining an entities
relationships, methods and data. It contains mappings to a number of attributes
and methods, so when we add our fifty more classes, we can now map them to a
specific type, giving us a generalized template, without the need for sub classing
or inheritance. The xml excerpt below shows how the relationships merge. We
have attributes and methods, all tied into a nice generalized template. We have
two types, based on job level, and from these types we can further define the
classes, or entities.
Notice the two xml attributes in the diagram below, attributes
and operations
. These two attributes contain the name keys to distinguish
which operations and attributes to associate with the entity types. This is
how we link up the different classes and their assemblies, and build our 'parent'
definitions. Just think of the meta-data that builds entity type as a template.
<entityTypes>
<entityType name="ExecutiveJobEntityType"
attributes="FullName,HireDate,IsActive,JobLevel"
operations="DoSomeWork,GoToLunch,TakeABreak,WriteAReport,ClockOut" />
<entityType name="EmployeeJobEntityType"
attributes="FullName,HireDate,IsActive,JobLevel"
operations="DoSomeWork,GoToLunch,TakeABreak,WriteAReport,ClockOut,GoofOff" />
</entityTypes>
The actual EntityType
class is defined below. The class itself
is considered a meta class. It basically acts as a holder for all the
relationships, attributes and operational methods we want for a specific template.
public abstract class EntityType { private string _typeName; private Type _type; private EntityAttributeCollection _attributes = new EntityAttributeCollection(); private OperationsCollection _operations = new OperationsCollection(); public EntityType(string name, Type type) { _typeName = name; _type = type; } public string Name { get{return _typeName;} set{_typeName = value;} } public Type TypeOf { get{return _type;} set{_type = value;} } public EntityAttributeCollection Attributes { get{return _attributes;} set{_attributes = value;} } public OperationsCollection Operations { get{return _operations;} set{_operations = value;} } }
The Entity
class is defined below. The class itself is considered
a meta class. This is the class that virtually replaces your
EmployeeJob
and ExecutiveJob
and all fifty class types
we mentioned above. It holds a composite of the EntityType
class,
and has a one to one relationship with that class.
public class Entity { private EntityType _entityType; public Entity(EntityType entityType) { _entityType = entityType; Console.WriteLine("..." + this.GetType().Name + " initialized with EntityType " + _entityType.Name); } public EntityType EntityTypeOf { get{return _entityType;} } }
The actual classes are not compiled, but instead exist at runtime as virtual
meta-classes built from the configuration meta-data. Below we see the xml excerpt
that allows us to define how we build this class. If you look at the xml, you
will see a type definition, so we know from where we want to grab our particular
entity definition, and entity type identifier, and two other xml attributes:
extendedAttributes
and extendedOperations
. These two
attributes lets us define specific class definitions based on the entity
type template. This replaces the need for inheritance and sub classing, by allowing
the meta-data to extend the entity. The two xml attributes let us add on specific
extra attributes and operational strategies (or subtract ones from the current
entity, which is out of scope for this example, but perfectly viable for this
model).
<entities>
<entity name="ExecutiveJob"
type="AOMImpl.EntityImpl.Entity"
entityType="ExecutiveJobEntityType"
extendedAttributes="JobLevel"
extendedOperations="WriteAReport" />
<entity name="EmployeeJob"
type="AOMImpl.EntityImpl.Entity"
entityType="EmployeeJobEntityType"
extendedAttributes=""
extendedOperations="WriteAReport,GoofOff" />
</entities>
The attributes for the entity types and entities work exactly the same way, as far as their grouping goes.
Below we see the definition for EntityAttribute
. We see it holds
data for it's name, the object for the attribute or value, and it's Type. This
class is also a meta-class, and it's actual object data can be loaded
from any source, probably a database. But the definition of it's type is set
up in meta-data.
public class EntityAttribute { private string _attributeName; private object _attributeObject; private Type _attributeType; public EntityAttribute(string name, object obj, Type type) { _attributeName = name; _attributeObject = obj; _attributeType = type; } public string Name { get{return _attributeName;} set{_attributeName = value;} } public object Value { get{return _attributeObject;} set{_attributeObject = value;} } public Type TypeOf { get{return _attributeType;} set{_attributeType = value;} } }
The meta-data from the configuration file show us the key name (xml attribute 'name') the Type of the attribute value (xml attribute 'attributeType') and the actual object value (xml attribute attributeValue'), which is not meta-data, and should not be included in your project as such, but is done for this example.
<entityAttributes>
<entityAttribute name="FullName"
attributeType="System.String"
attributeValue="(This is Not META-DATA)" />
<entityAttribute name="JobLevel"
attributeType="System.Int32"
attributeValue="1" />
<entityAttribute name="IsActive"
attributeType="System.Boolean"
attributeValue="True" />
<entityAttribute name="HireDate"
attributeType="System.DateTime"
attributeValue="03/01/2005" />
</entityAttributes>
Now lets take a look at how the example runs, and go through the flow. When
we run the example we first see that we load up our specific entities loaded
with their entity types. This is done in any order in your Interpreter
pattern (for this example the order is different than is displayed) that works
best to allow the dynamic creation of your classes. Our interpreter is the AOMFactory
,
which creates and loads our different relationships from the meta-data.
Next we see the specific entities and their operational methods and attribute
calls displayed. We do this to illustrate as a test that the expected operations
and attributes are part of each class definition. Below we define the EmployeeJob
class, and see the methods and attributes we defined in the meta-data for it's
entity type, as well as it's specific methods and attributes are displayed.
Notice methods WriteAReport
and GoffOff
are added
to it's method definition.
Next we see that ExecutiveJob
class, also has the methods and
attributes we defined in the meta-data for it's entity type, as well as it's
specific methods and attributes are displayed. Notice method WriteAReport
is added to it's method definition.
Next we see that ContractorJob
class, also has the methods and
attributes we defined in the meta-data for it's entity type, as well as it's
specific methods and attributes are displayed. Since this is a contractor job
type it gets only the basic methods.
Meta-Data and Configuration
Now lets talk about who gets to configure the meta-data, and build the applications process flow. Depending on your usages for the code, you might want to allow a power-user, who has some advanced knowledge of the business processes, to modify the meta-data. However you might allow certain pieces of meta-data to be modified by ordinary users. For example, a user might have the ability to build a process flow for his piece of the application, for his particular profile. Suppose one of your business users was a manager, and he needed to build a process that allowed his subordinates to only have certain functionality, and see certain data for an application. He could be allowed to modify the meta-data for his process and build certain methods and attribute types to be displayed on a web page. He might display data, buttons to certain functions, or other links to the background functionality of the operations, based on his settings in the meta-data.
Project Suggestions
Well now hopefully I have explained some functional flows and usage of the AOM model, so how does this apply to your projects? The AOM model may not make sense for all projects, but it is a very interesting model nonetheless. Several design types come to mind, including a dynamic content generated web site, a process engine for approvals, etc. The first thing you need to do to find relevant uses in your project is anaylize your current structure and see if it suggests usage of the AOM model. You can do this by seeing if you have any excessive class structures, or mabey some algorithms that are duplicated in several places. Also if you have excessive subclassing, or just want to have an application that is more flexible and responsive to ever changing business requirements.
Another aspect to consider is that the competency level of your development team needs to be rather high for this model to work, and a strong design patterns methodology and mindset needs to be in place in your development group. To use this model, you need to have some knowledge of design pattern methodology, a good understanding of reflection and object typing, and a general knowledge of high level architecture and design. Remember, this is not rocket science, just a model based on reflection, so don't get intimidated. Focus on the pieces, not the whole and you should do fine.
Points of Interest
This is the fourth installment in the series I am writing on real world adaptive object modeling. All examples and the bulk of this article are taken from my professional experience as an architect. The examples given are templates only, and the designer must keep in mind that they are the ones who must decide where different patterns, if any, may be best used in their code.
Deciding to perform a refactoring effort from existing code to a pattern or enhanced design model must be weighed on the necessity and need of the code itself. Patterns and Adaptive Models are only design templates, helpers to accommodate better overall design. I might stress that making the effort to use advanced design methodologies will strengthen your overall design ability, but like your basic coding skills, it is something learned and cultivated.
Related Articles
Other articles in this series include:
- Type-Object
- Properties
- Strategies
- Entity-Relationships and Accountability/Cardinality
- Ghost Loading, Data Mapping...
- Interpreter methodologies
- Discussions on Practical Project usefulness of design.
- Discussions on the AOM framework, UI, and Advanced Concepts
History
This is the second revision and is the first installment in a series.
Credits
Thanks to: Joe Yoder, Ralph Johnson, for their various articles on the AOM pattern, Martin Fowler for his interest in my articles and his work on the Accountability pattern, and Doga Oztuzun, for his insight and sharing of his relevant project data. Also to anyone else I have forgotten.