Oslo “M” Graphs and Inheritance

In my last post I explored the subject of applying Object-Relational Mapping (ORM) concept to modeling in M. What I discovered was that M handles ORM pretty well and allows for the modeling of polymorphic inheritance within M metamodels – thereby enabling rich Object-Oriented runtimes to consume Oslo metamodels.

At the end of the post I briefly discussed the question of which of the two ORM patterns discussed in the post was best – at which point I quickly punted on the question.

My reasoning for punting was that a final accounting would need to wait until I explored the M graph implications of each of the ORM patterns.

This post will be that final accounting.

 

Table per Class Hierarchy Graphs

In the ORM post I first took a look at using a single M type to create an extent that would model an entire class hierarchy in the SQL Server Repository. This is ORM pattern has been an effective design for relatively simple class hierarchies where the classes don’t change much. For reference, here’s the M for the types and extents covered in the post:

    1: type Department
    2: {
    3:     Id: Integer64 => AutoNumber();
    4:     Folder: Integer32 => 100;
    5:     Name: Text#64;
    6: } where identity Id;
    7:  
    8:  
    9: type Person
   10: {
   11:     Id: Integer64 => AutoNumber();
   12:     Folder: Integer32 => 100;
   13:     Discriminator: Text where value in {"Person","Manager","IndividualContributor"};
   14:     FirstName: Text#32;
   15:     LastName: Text#32;
   16:     Department: Department? where value in Departments;
   17:     JobTitle: Text#32?;
   18:     Manager: Person? where value in People;
   19: } where identity Id;
   20:  
   21:  
   22: // Extent definitions
   23: Departments: Department*;
   24: People: Person*;

In terms of M graphs to instantiate the model, the code is really straightforward:

    1: // M graphs
    2: Departments
    3: {
    4:     Department1 { Name => "Department#1" },
    5:     Department2 { Name => "Department#2" }
    6: }
    7:  
    8: People
    9: {
   10:     JohnDoe { FirstName => "John", LastName => "Doe", Discriminator => "Person"  },
   11:     JoeManager { FirstName => "Joe", LastName => "Manager", Discriminator => "Manager", Department => Departments.Department1, JobTitle => "Manager", Manager => People.SteveDirector },
   12:     SteveDirector { FirstName => "Steve", LastName => "Director", Discriminator => "Manager", Department => Departments.Department1, JobTitle => "Director" },
   13:     FredWorker { FirstName => "Fred", LastName => "Worker", Discriminator => "IndividualContributor", Department => Departments.Department1, JobTitle => "Manager", Manager => People.JoeManager }
   14: }

As we can see from the code above, instantiating Person instances in the model is just a matter of providing the correct data based on the type being instantiated (as defined by the Discriminator field).

What I found pretty cool is that Intellipad actually gives you Intellisense when you’re authoring M graphs. This was very handy when referencing items in other collections. Here’s a screenshot illustrating the coolness:

MGraphIntellisense

Table per Concrete Class Graphs

The second ORM pattern relies on a M type and an extent for each concrete class in the hierarchy. Within the Oslo Repository this ORM pattern manifests as a collection of SQL Server tables where the tables representing derived classes (Manager and IndividualContributor) have Primary Keys (PKs) that are Foreign Keys (FKs) to the table representing the Person class. In using this ORM pattern, a join is required to the People table whenever a Manager or IndividualContributor is retrieved from the SQL Server Repository.

Here’s the M code for the ORM pattern:

    1: type Department
    2: {
    3:     Id: Integer64 => AutoNumber();
    4:     Folder: Integer32 => 100;
    5:     Name: Text#64;
    6: } where identity Id;
    7:  
    8: type Person
    9: {
   10:     Id: Integer64 => AutoNumber();
   11:     Folder: Integer32 => 100;
   12:     FirstName: Text#32;
   13:     LastName: Text#32;
   14:     Discriminator: Text where value in {"Person","Manager","IndividualContributor"};
   15: } where identity Id;
   16:  
   17:  
   18: type Employee
   19: {
   20:     Person: Person where value in People;
   21:     EmployeeId: Integer16;
   22:     Department: Department where value in Departments;
   23:     JobTitle: Text#32;
   24:     
   25: } where identity Person;
   26:  
   27:  
   28: type Manager : Employee
   29: {
   30:     Manager: Manager? where value in Managers;
   31: } where value.Manager.Person.Discriminator == "Manager";
   32:  
   33:  
   34: type IndividualContributor : Employee
   35: {
   36:     Manager: Manager where value in Managers;
   37: } where value.Manager.Person.Discriminator == "Manager";
   38:  
   39:  
   40: // Extent definitions
   41: Departments: Department*;
   42: People: Person*;
   43: Managers: Manager*;
   44: IndividualContributors: IndividualContributor*;

And the M graphs to instantiate the model:

    1: // M graphs
    2: Departments
    3: {
    4:     Department1 { Name => "Department1" }
    5: }
    6:  
    7: People
    8: {
    9:     JaneDoe { FirstName => "Jane", LastName => "Doe", Discriminator => "Person" }
   10: }
   11:  
   12: Managers
   13: {
   14:     SteveDirector { Person => { FirstName => "Steve", LastName => "Director", Discriminator => "Manager" }, EmployeeId => 1234, Department => Departments.Department1, JobTitle => "Director" },
   15:     JoeManager { Person => { FirstName => "Joe", LastName => "Manager", Discriminator => "Manager" }, EmployeeId => 4321, Department => Departments.Department1, JobTitle => "Manager", Manager => Managers.SteveDirector }
   16: }
   17:  
   18: IndividualContributors
   19: {
   20:     FredWorker { Person => { FirstName => "Fred", LastName => "Worker", Discriminator => "IndividualContributor" }, EmployeeId =>1122, Department => Departments.Department1, JobTitle => "Worker", Manager => Managers.JoeManager }
   21: }

The M graph code above illustrates the effects of this ORM pattern on model instantiation – notably the “in-graph” instantiations of Person instances within the Manager and IndividualContributor instances.

 

The Verdict

I would argue that the Table per Concrete Class ORM pattern, overall, is the better solution for modeling polymorphic inheritance in M models. My argument rests mainly on the following three factors:

  1. I like the explicitness of having to code up the “Person-ness” in the M graphs for Managers and IndividualContributors. The OO bigot in me is drawn very much to the clean separation between what Managers and IndividualContributors inherit from Person and what they extend from Person.
  2. The use of this ORM pattern allows for the explicit expression of the constraints in the original design (see the UML in the previous post for further details).
  3. The use of three separate types, and extents, allows for greater flexibility in terms of extension of the classes in the hierarchy (especially for the derived classes).

The only downside to the pattern is that the joins could become a performance bottleneck for deep/complicated inheritance hierarchies – although I’m not quite sure if this would be a problem in “normal” usage (I would tend towards an architecture that cached metamodel data at runtime, but that’s just me).

 

Next Time

This little investigation into M graphs has gotten me very excited about the possibilities of the Oslo platform.

As such, I’m going to be focusing my efforts now on actually working with M graphs at runtime with C# code.

Until next time, any thoughts/comments would be greatly appreciated.