Modeling Inheritance in Oslo’s “M”

As I recently posted, I’m very intrigued by the potential of the Oslo platform to democratize the use of metamodel-driven software architectures. As a first step in exploring the capabilities of the latest Oslo CTP I took some time over the weekend to dive into M – particularly modeling types using M (formerly known as MSchema). As I’m particularly interested in Oslo’s ability to provide a metamodel runtime, my plan was to take a UML-based metamodel, convert it into M, and stick it into the Repository. With plan in place, I fired up Intellipad, pulled up Oslo’s SDK docs, and started coding.

Everything was going smooth. M’s syntax is relatively intuitive and Intellipad is a reasonable tool (although I have to admit a definite preference to a full-blown IDE). The T-SQL preview pane in Intellipad is also very helpful to understand the evolving “shape” of your model in real time. Then, out of the blue, an old friend reared its inelegant head – Object-Relational Mapping (ORM).

This shouldn’t have surprised me since M is, at base, relational in nature.

The reason why I’m dealing with ORM in M is due to my Object-Oriented bias – I just can’t imagine a non-trivial runtime consuming the Oslo metamodel that wasn’t OO in nature. As such, it just seems natural to me to define my M metamodel in a way where the generated Repository tables would support some core OO concepts, namely polymorphic inheritance.

A quick search of the ‘Net didn’t really reveal any sources outlining inheritance patterns in M, although I found a number of items from folks complaining that M doesn’t support inheritance out of the box.

I figured since I needed to model inheritance in M for ORM support, I would whip up a quick post on the subject in case someone else would find the content useful.

For those that are interested, Scott Ambler wrote an excellent article on ORM. The article provides an awesome background on the subject and I’m implicitly assuming this level of knowledge for the rest of the post.

 

NOTE – I’m by far from an M expert, so I would greatly appreciated any feedback on this post – especially any assistance around better modeling inheritance in M.

 

The Scenario

UML class diagram below illustrates our OO scenario to be modeled in M. Please note that the scenario is very much contrived (and I would argue bad design, since the Party Model is a far superior solution), but it will suit our purposes for this post:

OsloInheritance

It is worthy to note that the UML model defines the following constraints that our M modeling will attempt to replicate:

  1. Departments can have zero-to-many Employees
  2. Each Employee must have a Department
  3. An IndividualContributor must have a Manager
  4. A Manager may, or may not, have a Manager

 

Table per Class Hierarchy

Using this ORM pattern we will map the Person, Employee, Manager, and IndividualContributor classes to a single M type which we will then declare as an extent. First, though, we will start with our M for Department:

    1: type Department
    2: {
    3:     Id: Integer64 => AutoNumber();
    4:     Folder: Integer32 => 100;
    5:     Name: Text#64;
    6: } where identity Id;

 

Next is the M code for our combined Person type. Note that the constraints get pretty gnarly since we have to accommodate different types within the People extent via the Discriminator field. Note that the extent definitions at the end of the code snippet:

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

 

As the M code above illustrates, the Person type stands in for all classes in the hierarchy. As such, the Person type has a couple of optional fields that allow for the modeling of Managers and IndividualContributors. Additionally, the Person type defines a discriminator field that allows for the determination of the type (“Person”, “Manager”, “IndividualContributor”) of a particular Person instance.

For those folks that have battled the ORM dragon before, the M code for Person is very much old hat. In exchange for a simple model (a single table for the whole class hierarchy) we’ve had to give up some fidelity in terms of constraints that were defined in the UML class diagram – we’ll need to implement those constraints elsewhere (e.g., a DSL grammar), unfortunately.

The M code above generates the following T-SQL for Repository storage of the model:

    1:  
    2: set xact_abort on;
    3: go
    4:  
    5: begin transaction;
    6: go
    7:  
    8: set ansi_nulls on;
    9: go
   10:  
   11: create schema [TablePerClassHierarchyInheritance];
   12: go
   13:  
   14: create table [TablePerClassHierarchyInheritance].[Departments]
   15: (
   16:   [Id] bigint not null identity,
   17:   [Folder] int not null default 100,
   18:   [Name] nvarchar(64) not null,
   19:   constraint [PK_Departments] primary key clustered ([Id])
   20: );
   21: go
   22:  
   23: create function [TablePerClassHierarchyInheritance].[Check_People_Func]
   24: (
   25:   @_Id as bigint
   26: ,   @_Department as bigint
   27: ,   @_Discriminator as nvarchar(max)
   28: ,   @_FirstName as nvarchar(32)
   29: ,   @_Folder as int
   30: ,   @_LastName as nvarchar(32)
   31: ,   @_Manager as bigint
   32: )
   33: returns bit  as
   34:   begin
   35:     return case
   36:   when @_Discriminator in (N'Person',
   37: N'Manager',
   38: N'IndividualContributor') then (1)
   39:   else (0)
   40: end
   41:  
   42:   end
   43: go
   44:  
   45: create table [TablePerClassHierarchyInheritance].[People]
   46: (
   47:   [Id] bigint not null identity,
   48:   [Department] bigint null,
   49:   [Discriminator] nvarchar(max) not null,
   50:   [FirstName] nvarchar(32) not null,
   51:   [Folder] int not null default 100,
   52:   [LastName] nvarchar(32) not null,
   53:   [Manager] bigint null,
   54:   constraint [PK_People] primary key clustered ([Id]),
   55:   constraint [FK_People_Department_TablePerClassHierarchyInheritance_Departments] foreign key ([Department]) references [TablePerClassHierarchyInheritance].[Departments] ([Id]),
   56:   constraint [FK_People_Manager_TablePerClassHierarchyInheritance_People] foreign key ([Manager]) references [TablePerClassHierarchyInheritance].[People] ([Id]),
   57:   constraint [Check_People] check (([TablePerClassHierarchyInheritance].[Check_People_Func]([Id], [Department], [Discriminator], [FirstName], [Folder], [LastName], [Manager])) = 1)
   58: );
   59: go
   60:  
   61: commit transaction;
   62: go

 

What I really appreciate about M when I look at the T-SQL listing above is the fact that I can work at a higher level of abstraction. To be frank, I haven’t written any substantive T-SQL in quite a few years. Part of this is due to my move from a Developer role to an Architect role and part of it is due to the advent of higher level tools that have obviated the need for me to write substantive T-SQL (e.g., NHibernate). Given my desire to be able to metamodel to a persistent SQL Server data store that will be consumed by an OO runtime, and my relative lack of T-SQL skill, I look at the T-SQL code in the listing above and get excited about the long-term possibilities of metamodeling in Oslo.

Cool stuff.

 

Table per Concrete Class

Let’s switch gears to another classic ORM pattern - “Table per Concrete Class”. Using this ORM pattern we will map the Person, Manager, and IndividualContributor classes to their own types. For fun, we’ll use a Employee type that acts as a mix-in. Since the Department type is exactly the same as above I’ll leave it out this time. Here’s the M code for Person:

    1: type Person
    2: {
    3:     Id: Integer64 => AutoNumber();
    4:     Folder: Integer32 => 100;
    5:     FirstName: Text#32;
    6:     LastName: Text#32;
    7:     Discriminator: Text where value in {"Person","Manager","IndividualContributor"};
    8: } where identity Id;

 

The M code in line 7 above is worthy of a brief explanation. From an ORM pattern perspective, the Discriminator field is, strictly speaking, optional. I modeled Person in this way to make querying against the Person class hierarchy in the Repository easier. As we’ll see later on, the use of the Discriminator field is used to determine the type of join that is needed in the Repository to query for a derived instance of the Person class hierarchy.

Next is the code for the Employee mix-in. Since both Manager and IndividualContributor inherit from Employee in the UML model, we’ll model Employee using M to simulate Employee inheriting from Person as an abstract class:

    1: type Employee
    2: {
    3:     Person: Person where value in People;
    4:     EmployeeId: Integer16;
    5:     Department: Department where value in Departments;
    6:     JobTitle: Text#32;
    7:     
    8: } where identity Person;

 

The interesting code in the M listing above is in line 8. Using this code we’re modeling that any M type that leverages the Employee mix-in will have that type’s identity defined in terms of an instance of the Person type. As we’ll see later, the Repository ramifications of this M code is that the SQL tables for M types that leverage the Employee mix-in will have primary keys (PKs) defined in terms of a foreign keys (FKs) to the People SQL table. Additionally, it is worthy to note that the M code above requires that every Employee be assigned to a Department.

OK, now that we have modeled the Employee abstract type inheriting from the Person concrete type (aka extent), we can take a look at some M code for the Manager and IndividualContributor concrete types (and the associated extents):

    1: type Manager : Employee
    2: {
    3:     Manager: Manager? where value in Managers;
    4: } where value.Manager.Person.Discriminator == "Manager";
    5:  
    6:  
    7: type IndividualContributor : Employee
    8: {
    9:     Manager: Manager where value in Managers;
   10: } where value.Manager.Person.Discriminator == "Manager";
   11:  
   12:  
   13: // Extent definitions
   14: Departments: Department*;
   15: People: Person*;
   16: Managers: Manager*;
   17: IndividualContributors: IndividualContributor*;

 

As the list above illustrates, I’ve defined some constraints in the M code above to specify that only Manager instances can be Managers (at the risk of constraining the obvious ;-). While these constraints don’t directly affect the generated Repository T-SQL, they are important as specification information for anyone reading the M code directly (e.g., a Developer building the OO runtime to consume a metamodel).

The M code above is straightforward. A Manager optionally has a Manager, while IndividualContributors must have a Manager.

The M code in the listings above combine to produce the following T-SQL:

    1: set xact_abort on;
    2: go
    3:  
    4: begin transaction;
    5: go
    6:  
    7: set ansi_nulls on;
    8: go
    9:  
   10: create schema [TablePerConcereteClassInheritance];
   11: go
   12:  
   13: create table [TablePerConcereteClassInheritance].[Departments]
   14: (
   15:   [Id] bigint not null identity,
   16:   [Folder] int not null default 100,
   17:   [Name] nvarchar(64) not null,
   18:   constraint [PK_Departments] primary key clustered ([Id])
   19: );
   20: go
   21:  
   22: create function [TablePerConcereteClassInheritance].[Check_People_Func]
   23: (
   24:   @_Id as bigint
   25: ,   @_Discriminator as nvarchar(max)
   26: ,   @_FirstName as nvarchar(32)
   27: ,   @_Folder as int
   28: ,   @_LastName as nvarchar(32)
   29: )
   30: returns bit  as
   31:   begin
   32:     return case
   33:   when @_Discriminator in (N'Person',
   34: N'Manager',
   35: N'IndividualContributor') then (1)
   36:   else (0)
   37: end
   38:  
   39:   end
   40: go
   41:  
   42: create table [TablePerConcereteClassInheritance].[People]
   43: (
   44:   [Id] bigint not null identity,
   45:   [Discriminator] nvarchar(max) not null,
   46:   [FirstName] nvarchar(32) not null,
   47:   [Folder] int not null default 100,
   48:   [LastName] nvarchar(32) not null,
   49:   constraint [PK_People] primary key clustered ([Id]),
   50:   constraint [Check_People] check (([TablePerConcereteClassInheritance].[Check_People_Func]([Id], [Discriminator], [FirstName], [Folder], [LastName])) = 1)
   51: );
   52: go
   53:  
   54: create table [TablePerConcereteClassInheritance].[Managers]
   55: (
   56:   [Person] bigint not null,
   57:   [Department] bigint not null,
   58:   [EmployeeId] smallint not null,
   59:   [JobTitle] nvarchar(32) not null,
   60:   [Manager] bigint null,
   61:   constraint [PK_Managers] primary key clustered ([Person]),
   62:   constraint [FK_Managers_Person_TablePerConcereteClassInheritance_People] foreign key ([Person]) references [TablePerConcereteClassInheritance].[People] ([Id]),
   63:   constraint [FK_Managers_Department_TablePerConcereteClassInheritance_Departments] foreign key ([Department]) references [TablePerConcereteClassInheritance].[Departments] ([Id]),
   64:   constraint [FK_Managers_Manager_TablePerConcereteClassInheritance_Managers] foreign key ([Manager]) references [TablePerConcereteClassInheritance].[Managers] ([Person])
   65: );
   66: go
   67:  
   68: create table [TablePerConcereteClassInheritance].[IndividualContributors]
   69: (
   70:   [Person] bigint not null,
   71:   [Department] bigint not null,
   72:   [EmployeeId] smallint not null,
   73:   [JobTitle] nvarchar(32) not null,
   74:   [Manager] bigint not null,
   75:   constraint [PK_IndividualContributors] primary key clustered ([Person]),
   76:   constraint [FK_IndividualContributors_Person_TablePerConcereteClassInheritance_People] foreign key ([Person]) references [TablePerConcereteClassInheritance].[People] ([Id]),
   77:   constraint [FK_IndividualContributors_Department_TablePerConcereteClassInheritance_Departments] foreign key ([Department]) references [TablePerConcereteClassInheritance].[Departments] ([Id]),
   78:   constraint [FK_IndividualContributors_Manager_TablePerConcereteClassInheritance_Managers] foreign key ([Manager]) references [TablePerConcereteClassInheritance].[Managers] ([Person])
   79: );
   80: go
   81:  
   82: commit transaction;
   83: go

 

The interesting T-SQL code in the listing above resides in lines 54-65 (Managers table) and lines 68-79 (IndividualContributors table). The T-SQL exhibits the following ORM characteristics:

  1. All of the data fields defined in the Employee abstract class have manifested in the table definitions for Managers and IndividualContributors.
  2. Both the Managers and IndividualContributors tables have their primary keys (PKs) defined as foreign keys (FKs) to the People table.

As mentioned above,this T-SQL allows an OO runtime to pull data for a particular instance of the class hierarchy (say Managers) by doing a join between the People and Managers tables.

 

Which Pattern is Best?

From a Repository and OO runtime perspective, all of the tradeoffs described in Scott Ambler’s ORM article apply to the patterns discussed in this post. Rather than try to outline them, I would strongly suggest that interested readers hit Scott’s site since his work is widely considered definitive on the subject.

Unfortunately, from an Oslo runtime perspective the answer to the question of which pattern is best is a bit hard to quantify. I say this mainly because I haven’t yet explored the potential impact of the above ORM patterns on Oslo’s support for M graphs.

This is a critical question from my perspective as M graphs have a lot of potential in terms of allowing a number of very interesting potential Oslo platform capabilities – not the least of which is a potentially standardized mechanism for allow model instance-to-model instance transformations.

As such, the current focus for my next post is on exploring M graphs in greater detail in the context of M models leveraging ORM design patterns.

Stay tuned!