NFL Picks: Data Layer Design

posted on 2004-10-16 at 13:41:29 by Joel Ross

I'm starting at the bottom, and working my way up, and the database is done (at least for now). So, the next obvious choice is the data layer.

I'm going with OJB.NET as my O/R mapping tool. Why? Mainly because it was the only one I was somewhat familiar with. It was recommended to me by Mike Swanson. That's what he's using right now. Or at least was. I'm not sure if he still is. Either way, that's what I'm using right now.

I want to do this right, and am running into my first set of questions.

I guess I should back up. My thought is to have levels of inheritance. The farthest layer down would a purely data layer. Not data access, but data. The fields and properties on the object.

The next layer up would add data access to the data members. This would have the necessary information for CRUD, i.e. It would know how to access the database.

Ideally, the bottom two layers would be internal, while the top layer would be external, and expose only the public facing methods and properties.

The top level business layer would contain a method to save and delete the object, and have static factory methods for retrieving objects, getting a collection of objects, or anything that isn't reliant on an instance of the class.

So there's the ideal world. Now, let's get back to the real world. With OJB.NET, you need to have attributes on the properties for data retrieval and saving. So that starts to blur the line between the data entity and the data access layer. I think I'm ok with that, since I don't have to worry about the data layer, per se.

So, first question: How do you make a generic data layer that is "swappable"? My first thought is use an interface rather than an actual data entity layer. The interface would define how the object would look, but the implementation would still be wrapped up in the data access layer. Of course, that leads to another question: Do I need a swappable data layer at all? For this, probably not, but the idea is to learn how to do things right, not right now. I probably won't implement the ability have different types of databases, but I may want to experiment with other O/R mapping tools once I get done.

Now, the second question: If I split out the data layer implementation from the business layer implementation into separate assemblies, which I would want to do, how do you make it inaccessible from consumers of the business layer? I'm not sure that's a huge concern, but it doesn't allow me to hide the data access layer.

So that's where I'm at. I'm leaning right now towards an interface approach. The interface would define the Data entity. It would define what the properties on the object are, and possibly what the data access methods should like. That would be implemented by the data access class.

The business layer would inherit from the data layer, and add the static methods needed for retrieving instances.

And then on top of the above, I have been reading Code Complete, Second Edition, and, while I haven't gotten through too much of it, I have gotten to the section about inheritance. One thing it states is that you shouldn't have a base class unless more than one object is going to inherit from it. In my model, I would have an interface to define the data entities. That's fine, since the idea is that I can use the same data model with other O/R mappers. But the business layer by inheritance breaks that rule. Would containment be a better way? Or maybe better yet, an interface that is the data entity properties and the data layer methods, which is what the business layer would contain, rather than an actual object. That way, any data layer that implements that interface could be the actual object contained by the business layer. That object could be returned by a factory that can determine what data implementation to use.

Or maybe I'm way off base here. This is where I'm weak as a developer. I can code, but it's the design decisions that I struggle with. Usually, by talking through the issues, I can find the best way to do it - but is it the best? I'm not really sure, because just because I think it's the best way, doesn't mean it is. Most programming books are more about how to do something than why to do something. Code Complete seems to be completely about the why, but I just haven’t had the time to get through it.

I guess I should go check it out, huh?

Anyway, I still have some refactoring to do, but here's my User object. This (so far) is the only object I have working through OJB.NET.

     1: using System;
     2: using System.Collections;
     3: using Ojb.Net.Facade.Query;
     4: using Ojb.Net.Facade.Persist;
     5:  
     6: namespace Ross.NFLPicks.Data
     7: {
     8:     /// <summary>
     9:     /// A User who makes picks.
    10:     /// </summary>
    11:     public class User : Ojb.Net.Facade.Po.EditableObject
    12:     {
    13:         private string _name = string.Empty;
    14:  
    15:         /// <summary>
    16:         /// Default constructor
    17:         /// </summary>
    18:         public User() { }
    19:  
    20:         /// <summary>
    21:         /// A full constructor for a user
    22:         /// </summary>
    23:         /// <param name="id">The primary key for a user</param>
    24:         /// <param name="name">The name of the user</param>
    25:         public User(string id, string name) : base(id) {
    26:             this.Name = name;
    27:         }
    28:  
    29:         /// <summary>
    30:         /// The primary key of a user
    31:         /// </summary>
    32:         public string Id {
    33:             get { return (string) base.PrimaryKey[0]; }
    34:         }
    35:  
    36:         /// <summary>
    37:         /// The name of the user
    38:         /// </summary>
    39:         [Mutator]
    40:         public string Name {
    41:             get { return _name; }
    42:             set { _name = value; }
    43:         }
    44:  
    45:         /// <summary>
    46:         /// 
    47:         /// </summary>
    48:         /// <param name="userId"></param>
    49:         /// <returns></returns>
    50:         public static User GetUserById(string userId){
    51:             ArrayList primaryKeys = new ArrayList();
    52:  
    53:             primaryKeys.Add(userId);
    54:             
    55:             return (User) QueryFacade.FindObject(typeof(User), primaryKeys);
    56:         }
    57:  
    58:         public static User GetUserByName(string name){
    59:             Criteria criteria = new Criteria();
    60:  
    61:             criteria.AddEqualTo("_name", name);
    62:  
    63:             return (User) QueryFacade.FindObject(typeof(User), criteria);
    64:         }
    65:     }
    66: }

And here's the xml mapping file so far:

    1: <DescriptorRepository TransactionIsolationLevel="ReadCommitted" ConnectionString="server=127.0.0.1;uid=sa;pwd=sa;database=NFLPicks"
DefaultAssembly="Ross.NFLPicks.Data" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
    2:     <ClassDescriptor TypeName="Ross.NFLPicks.Data.User" TableName="Users">
    3:         <PrimaryKeyDescriptor>
    4:             <FieldDescriptor Id="User_UserId" FieldName="_primaryKey" ColumnName="UserId" DbType="String" /> 
    5:         </PrimaryKeyDescriptor>
    6:         <FieldDescriptor Id="User_UserName" FieldName="_name" ColumnName="UserName" DbType="String" /> 
    7:     </ClassDescriptor>
    8: </DescriptorRepository>

To me, that was a big step - I can now load users by name or id. I'll post more as I get the other classes loading - especially as I get to the classes with collections or references to other classes.

Categories: Football