Read Part II: Relationship Oriented Programming - Modeling the Romeo and Juliet Meta-Model
Introduction
We have foreign keys in databases and "is a kind of" and "has a" OOD concepts to model relationships. But is this sufficient
to describe the intricacies, intrigue, and drama of the relationships that occur in real life, such as when JSOP and his wife scheme to construct
carrot propelled weapons to control the population of lawn deer? I think not. At the risk of personal injury, JSOP's life seems relatively
mundane compared to, say, Romeo and Juliet. Let's investigate the "psychology of programming", as coined by my friend Mura,
because in models of the real world, relationships are where it's at. Every good story isn't about just Joe, it's about the ebb and flow of Joe's relationships
to people, things, and places. It's time to promote the relationship to a first class citizen!
Relationship Oriented Programming
Using the XTree application I recently wrote about, we're going to define the various entities and their relationships
in Romeo and Juliet.
The Families
We seem to have three families involved:
So, we need a Family entity.
Now, the question becomes, should Name be an attribute or an entity in itself? I think it should be an entity, as the attributes of a name are not clearly defined.
Consider, for example, a name can consist of first, last, middle initial, middle name, prefix (like Mr., Mrs., etc.), postfix (like "the Third"),
degrees ("BS, Ph.D", etc.), and so forth. Names are not trivial things.
So, we need a Name entity that has, well, as an attribute, a name.
We can now establish a relationship between Family and Name, which in this case is one-to-one (a nice simplistic view, that anyone with the same family name belongs to the same family.)
Now we've established a relationship of two entities, whose attributes we can expand upon later if necessary:
The People
Initially, we get introduced to several people:
- Sampson (a Capulet servant)
- Gregory (a Capulet servant)
- Abram (a Montague servant)
- Balthasar (a Montague servant)
- Lord Montague
- Benvolio (Lord Montague's nephew)
- Lord and Lady Capulet
- Tybalt (Lady Capulet's nephew)
- The Prince of Verona (of Escalus?)
- Romeo (Montague's son)
- Rosaline (Romeo is in love with her)
- Paris (kinsman of Prince of Verona)
- Juliet (of Capulet family)
- a nurse (no name, but Juliet's nurse)
- Mercutio (no idea what family he belongs to, we'll ignore him)
- officers (who try to break up a fight)
So, we have lots of people already entangled in all sorts of relationships:
- Spouse
- Nephew
- Servant
- Loves
- Acquaintance
- Kinsman
- Nurse (but no name for the nurse)
and of course, lots of fighting and falling in love. That's a lot to model, so let's start with the people.
People have names, they belong to families, and they are in various relations to each other, which is the eventual cause of all of this tragedy.
We have a couple more entities to model: the officers and the nurse, both of which are anonymous:
This covers the "Nurse" and the "officers".
Chaste, and In Love
We also have some interesting state information. For example:
- Romeo is in love with Rosaline
- Rosaline is chaste
- Later, Romeo falls in love with Juliet
- Juliet also falls in love with Romeo
- We assume that Romeo falls out of love with Rosaline
Here we have two kinds of state information. "Chaste" is a state of a person and is an excellent candidate as an attribute
because it is not a relational concept. However, "in love" (or "out of love") is a state bearing a relational concept. One is "in love"
with something/someone else (or oneself, but we won't go there, the point being that it is still a relational concept).
We encounter these things in real life as well. First name, last, name, etc., are all non-relational attributes. "Cost" is another example.
We force these non-relational attributes into separate entities for several reasons:
- It gets us thinking more abstractly
- The abstraction adds flexibility to the system
- We can implicitly acquire a history of changes (more on this later)
So. Chaste. We model chaste by creating an entity called "Sexual Behavior" (that's what Wikipedia calls "chaste") with one attribute,
"Behavior" that has a pick list of possibilities. A Person entity has a relationship with the "Sexual Behavior" entity. Sounds kinky.
Now, what about "in love"? This is a relational state, and furthermore, just because one person is in love with another person, doesn't mean
that the second person is in love with the first! We can model that!
I could have chosen to add a new "Person-Person" relationship of a different "type", but I think this makes it clearer: a person-person relationship
can have several "types", that of a relation (like father-son) and that of a love life, like "in love-not in love".
As you can see, there are modeling decisions that are not simple. I suppose, if this goes further, I'll need a "best practices" guide.
Activities
Shakespeare's plays tend to have only two activities: fighting and talking. Oh, and dying too, but we'll get to that later. An activity usually involves
a group of people (except for the dying parts, unless it is a war).
So, we have an Action entity with an Activity attribute, that has a picklist of activities. People are associated with Actions.
Oh yes. After dying, there is haunting. Forgot about that.
Places
The events take place in Verona, Italy.
But we also have less well described places, such as "at a party". One of the obvious relationships to a place is an action: activities occur somewhere,
even if it is only in your mind. People and objects also occupy space in various places: they live somewhere, they work somewhere, they are parked somewhere. But for the moment,
let's focus on Verona and the party.
- The various activities (fighting, talking, dying) in this tragedy occur in Verona.
- The party also is in Verona.
- We could say that the action taken by people at the party is "partying". Simple enough.
At the moment, a place simply has a name, and it is associated with an action.
The relationship is one-to-many because a particular activity might span many places. A car pursuit might be a candidate for some such concept: it begins somewhere,
ends somewhere else. It all depends on what information you want to keep track of.
Other Relationships and Corrections
We also need a relationship between anonymous groups and people to actions, as the Nurse talks to Juliet, the officers try to break up a fight, and so forth. We don't really
need the anonymous person entity because the name of the person is simply "Nurse". So, away it goes, but we do need a relationship between an anonymous group
and an action. Think Occupy Wall Street.
Oh, and places and actions have names. How did I forget that?
Graphviz is awesome, and being able to visualize the relationships is incredibly useful in debating architecture, even if it's a one man debate!
Relationships are Transitory
Here's a key thing about relationships, and why we model attributes like "Is Chaste" in an entity "Sexual Behavior" with a relationship
to a Person entity. We do this because we cannot guarantee that "is chaste" is a permanent state of that Person. Sure, we can implement
this as a flag, but we lose something very important: when did the person become chaste? When did they stop being chaste? One of the most useful features
of making relationships first class citizens is we can track the chronology of relational events.
Take "cost". An item has a cost. If we have a field called cost, we can only update the cost, and unless we have a change tracking mechanism,
we lose the relationship that item had to its cost for the duration of the time that cost was valid. If, however, we create a new relationship when the cost changes,
we now have a history (essentially built-in change tracking) of that item's cost.
These are simple examples of attributes that are not relational in nature but we are forcing them to be relational to obtain information about the history
of the values that the attribute can take on. In order for this to have meaning, it is important to realize that a relationship is never destroyed. Relationships never die,
they merely expire.
Where is the temporal information about an action, such as a party, or the date of death? It is in the relationship itself! And there are two types of temporal information:
- On or At, such as "born on this date" or "went to party at this time"
- Beginning and ending time: "was in love with Rosaline from 1635 to 1638"
Because the temporal information is built into the implementation of the relationship (basically, in the association table), we don't need to explicitly model a "When" entity.
Auditing
While we're at it, we might as well build in auditing: who created instances and relationships, and when. Trivial to do at this level of abstraction.
Some Interesting (Theoretical) Results
Queries
What can we do with this kind of abstraction? Well, for one, it makes it possible to construct some really interesting queries. For example, because dates and times
are managed in a single location, instead of date fields being managed as attributes to different entities, we can query "what are all the things that happened
on this day?". Because location is managed in a single place, we might ask, "what are the things that happened within a 5 mile radius
of this place?" We can filter that information even further: "Who are all the people that died on a particular date in this location?".
Attributes
You will notice that there are very few actual attributes used (other than "Name", which is implicit). Instead, the "knowledge" of this system
is in the relational information.
Association Tables
This architecture breaks the classical model of foreign key associations. Normally, a Person table would have foreign keys to Person,
Family, Place, Action. Instead, the associations are wildly freeform, requiring that the association table also keeps track of the association type. Is this association
to another Person, or a Family, or a Place, or an Action? We need to know this as part of the information that goes into the association table so that the correct data can
be extracted, displayed, and managed.
Information Management
And as mentioned earlier, an artifact of this approach is that data and relationships are never destroyed. You don't delete a relationship to a place just
because someone moves. You expire the relationship and create a new one. The idea of cascading deletes is moot because you never delete anything. The only
exception is if you accidentally created some erroneous information which you want to remove rather than change. Even the idea of changing information is different. If you have
information about the physical characteristics of a person, such as eye color, weight, length of hair, facial hair, etc., you don't change this information when you need to update
their characteristics. Instead, you create a new "Characteristics" record, expire the old relationship, and create a Person-Characteristics relationship
to the new record. Yes, we need a powerful UI to handle all of this so that the user doesn't have to know that all this is going on when they click the "Update" button!
Where's the Code?
Wow. I'm actually going to show you some code. Because the entire architecture is modeled using the XTree application, there isn't anything exciting to show you with regards to the tree.
There's a bunch of classes that are lightweight containers. I could have abstracted that out as well, but there are limits, even for me. The classes are so simple...
public class Entity : IHasCollection
{
[Category("Name")]
[XmlAttribute()]
[TypeConverter(typeof(EntityNameConverter))]
public string Name { get; set; }
[Browsable(false)]
public List<EntityAttribute> EntityAttributes { get; set; }
[XmlIgnore]
[Browsable(false)]
public Dictionary<string, dynamic> Collection { get; protected set; }
public Entity()
{
EntityAttributes = new List<EntityAttribute>();
Collection = new Dictionary<string, dynamic>() {
{"EntityAttribute", EntityAttributes},
};
}
}
...that there is really no point in talking about them. Trust me.
What I'll show you is the code that generates the graphs. Graphviz is cool!
Generating the Graph
Both the relationship and entity graphs call a particular method to generate the graph:
protected void Generate(StringBuilder sb)
{
string filename = Path.GetTempFileName() + ".dot";
File.WriteAllText(filename, sb.ToString());
string progFilePath = Environment.GetEnvironmentVariable("ProgramFiles");
string progFilex86 = Environment.GetEnvironmentVariable("ProgramFiles(x86)");
progFilePath = progFilex86 ?? progFilePath;
Process.Start(progFilePath + @"\Graphviz 2.28\bin\dotty.exe", filename);
File.Delete(@"c:\temp\graph.png");
var pr = Process.Start(progFilePath + @"\Graphviz 2.28\bin\dot.exe",
"-Tpng " + filename + @" -o c:\temp\graph.png");
while (!pr.HasExited)
{
Thread.Sleep(100);
}
Clipboard.SetText(sb.ToString());
}
A few things to note about this code:
- It expects to find Graphviz in a specific location, and since the folder name includes the version number, it is expecting version 2.28.
- The Graphviz DOT data is placed in the clipboard so you can inspect it.
Generating the Entity Relationships Diagram
protected void OnGenerateEntityRelationships(object sender, EventArgs e)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("digraph G {");
Schema.Instance.RelationshipsContainer.ForEach(t=>t.Relationships.ForEach(r=>
{
sb.AppendFormat(" {0} -> {1} [label={2}, taillabel={3}, headlabel={4}, labeldistance=2.0];\r\n",
r.EntityA.Quote(), r.EntityB.Quote(), r.Label.Quote(),
r.CardinalityALabel.Quote(), r.CardinalityBLabel.Quote());
}));
sb.AppendLine("}");
Generate(sb);
}
Generating the Entity Attribute Diagram
The "record" shape is limited in what you can do with styling, so based on feedback from the Graphviz folks, I'll eventually change this to emit HTML.
protected void OnGenerateEntityAttributes(object sender, EventArgs eventArgs)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("digraph G { graph [rankdir=\"LR\"];\r\n");
Schema.Instance.EntitiesContainer.ForEach(t => t.Entities.ForEach(e =>
{
sb.AppendFormat("{0} [ label = \"<f0> {1}", e.Name.Quote(), e.Name);
int n=1;
e.EntityAttributes.ForEach(a =>
{
sb.AppendFormat("| <f{0}> {1}", n.ToString(), a.Name);
++n;
});
sb.AppendLine("\" shape = \"record\"]; ");
}));
Schema.Instance.RelationshipsContainer.ForEach(t=>t.Relationships.ForEach(r=>
{
sb.AppendFormat(" {0} -> {1} [label={2}, taillabel={3}, headlabel={4}, labeldistance=2.0];\r\n",
r.EntityA.Quote(), r.EntityB.Quote(), r.Label.Quote(),
r.CardinalityALabel.Quote(), r.CardinalityBLabel.Quote());
}));
sb.AppendLine("}");
Generate(sb);
}
What's Next?
The rubber meets the road. The meat and potatoes. Actually implementing the underlying structures to support this kind of architecture
and UI's to manage the information. The UI's will of course have to be dynamically generated, but I also want the resulting UI to be editable for refining
the presentation to the user. That's what the next article will cover. And so far, I've only covered up to the party where Romeo and Juliet meet!
References
Special thanks to Dmitri Nesteruk who introduced me 2 years ago to Graphviz
when he added graphing to my project visualizer. I finally got around to learning more about Graphviz!