This post is part of a series which discusses the journey I took building a smart client membership application using Microsoft's Smart Client Software Factory (SCSF). You can navigate the entire series by going to the introduction.
Several years ago I developed a .NET code generation tool based on XSLT. It evolved over time and even had a front-end GUI to enable selection of various templates as well as database schema browsing. I had to hack my way through the INFORMATION_SCHEMA output of databases and developed an XML schema representation of the data model to drive the XSL templates. Of course, about six months after I had a working version Kathleen Dollard published her book Code Generation in Microsoft .NET and I could've saved myself a lot of effort if I'd just waited.
The generator was "nice" but the real effort was the model and architecture of the code being generated. Eventually I developed an "internal" architecture for use on in-house projects which consisted of a two-tier inheritance model and an "external" architecture for use on client consulting projects that was a simpler one-tier model which I could leave behind. I used my templates at one client site to generate the initial code and later a fellow consultant, Phil Denoncourt, took that generated code and built templates using CodeSmith - yup, he reverse-engineered my generated code back into another code generator. I liked CodeSmith so much that I purchased a license, rebuilt the XSL templates using their ASPX-like syntax and went on adding features to the generated architecture.
Fast-forward to today and once again I'm looking at tweaking the code generation templates. As I mentioned already one of the architectural features of the membership application is the ability to work with an embedded database. Unfortunately, SQL Server 2005 Compact Edition does not support stored procedures - only embedded SQL statements. This led me back to the templates to add an option to the existing support for stored procedures (and generation of them) with a second option of generated embedded, parameterized SQL statements. Of course, this change ripples outward by causing changes to the C# code closest to the SQL calls. After several hours of effort over the course of nights and weekends I was getting back into business - basic CRUD behavior was working again using embedded SQL.
At this point, time is marching on and I'm still not working on the business problem per se, but rather the toolsets and "noise" necessary to build a robust application. Thinking there must be a better way, I went searching for CodeSmith templates that might do what I needed. Instead, I came across EntitySpaces and fell in love. EntitySpaces is a low-cost, high-featured code generator built as templates for the MyGeneration open source generator. Mike Griffin, who co-authored the MyGeneration tool, and team have done a tremendous job providing a commercial offering. It support MS Access, MySQL, Oracle, SQL Server and SQL CE and can generate either stored procedures or embedded SQL - just what the doctor recommended. I took it for a test drive and quickly plopped down $149. At the time I was also considering an upgrade to CodeSmith 4 but that would've cost more and I still would be faced with writing and supporting the templates. Incidentally EntitySpaces 2008 will also ship with CodeSmith templates as well as MyGeneration - this blog entry has some nice diagrams showing the architecture. Anyway, now I can go back to working on the membership application and leave those guys to focus on what they do.
I know it was a long-winded digression but I needed to get that off my chest and wanted to show where I chose to spend time and where I didn't. Remember, this is just the data layer - there's a whole application that sits on top of it. EntitySpaces produces a data layer that consists of hierarchical objects and collections which are first class .NET 2.0 citizens. You can read more about it on their site. For this application, I've now got a Person class which exposes properties such as FirstName, MiddleInitial, LastName and has methods like AddNew(), Load(), LoadByPrimaryKey(), and Save() as well as a PersonCollection which is bindable, enumerable and uses generics. Think about it, if you were charging $50 per hour it would only take three hours (okay, four if you count overhead) to recoup the cost. Now, how many hours would it take to design, build, test, and support all that functionality? Run, don't walk, and go find a tool like EntitySpaces!
Like my home-grown tool originally, EntitySpaces generates a two-tier model of base classes (theirs are prefixed with "es" before the table name) and derived classes that are "empty" of properties and logic. You should never change the generated base classes as they will be regenerated when you re-run the tool as your model changes and evolves. This screen shot shows the derived Person class generated for the Person table:
Notice the red arrow indicating the "Generated" sub-folder - this is where the base generated classes are located. One example of customizing the generated code is show here in the derived Person class - I added a property called FormalName built dynamically from the base properties (columns) of Salutation, FirstName, MiddleInitial, LastName, Suffix.
The SCSF introduction has a nice graphic about halfway down showing the factory layers (it's some kind of bluish-purple color). Like many examples, the data layer sits closest to the database and typically models the database very closely. The generated classes I'm using do so as well - at a very granular level, one logical class per table - and I put them in their own assembly called...DataLayer.
I've implemented the notion of business entities a little differently though. The SCSF Hands On Labs show creating and transforming business entities right in the client layer the "raw" entities returned by the web service layer. You could place the business entities and their translators in a business module which you've created or in the pre-built Infrastructure projects - entity in Infrastructure.Interface.BusinessEntities and translator in Infrastructure.Library.Translators.
My first attempt was to follow the SCSF guidance - I put the DataLayer assembly behind a web service, created business entities and translators in the UI layer and it worked. However, for my standalone model with an embedded database, I'd need the data layer on the client as well as the web server. Plus, I didn't like the inheritance model, e.g. Person and Household inheriting from Contact, to be exposed and carried/spread around my application world. Looking at the guidance approach and building on past experience with multi-tiered applications, I decided to refactor things a bit.
I created two separate assemblies (foundational modules in SCSF parlance) to represent the business layer - one contains the business entity schema and the other contains the business entity logic for transforming the underlying data layer to the business entity. The business layer sits logically between the main application and the back-end database/data layer as shown here:
One important point relates to the web service layer. So many times I see books, articles, how-to's, and samples which make out the web service layer to be so much more than it is. In my opinion, a web service is nothing more than a facade over a wire protocol - it's XML over HTTP, that's it...SOAP. My web service layers have nothing in them but code to call the back-end applications and return results to the caller. No data access code, no ADO.NET connections, nothing, nada, nil. As you can see from the above diagram, the web service layer can be removed and another protocol or none in the case of a local database can be used without disrupting the rest of the application stack.
The business entity schema layer is nothing more than class definitions containing properties which can be serialized as shown here:
Notice that I "flattened" the underlying hierarchy by including in the Person the inherited properties of the Contact table. In other words, my business entities are things like Person and Household while the underlying data model's connecting or "linkage" tables and relations are kept hidden.
The entity layer contains the logic for retrieving and flattening the model:
This will also be the place where additional business logic lives - raising events, logging actions, etc.
Finally, as I said before, the web service layer is simply a facade that provides a SOAP interface so you'll find no logic or data access code here:
The serialization to/from a string is no doubt causing some Microsoft purists to groan, jump up and down and wave their arms :) I chose to do this so I could simply reference the same business entity schema on the client side and deserialize the results back into a Person. If I had returned a Person class, it would've been a Person in the web service's namespace which creates unnecessary hassles. Plus, I get the added benefit of allowing easy encryption/compression of the data flowing across the wire (a feature for later), the client only needs to reference the schema assembly (and the web service of course), and it'll fit well later on when I introduce the SCSF service layer which will hide the conversion to/from a serialized XML string.