Hopefully common object modeling problem

Hopefully common object modeling problem

Old forum URL: forums.lhotka.net/forums/t/10210.aspx


willrogers posted on Monday, March 28, 2011

Hello, and thanks in advance for your time.

We are using CSLA 3.5.1, although, ultimately, this problem may not have much to do with CSLA.

My company has gone a long way down the road of a common modeling error and is finally having to face it. I've simplified the domain objects below to clarify the problem:

We have 3 classes defined as such:

class Person<T> : BusinessBase<T>

class Student : Person<Student>

class Teacher : Person<Teacher>

We are using NHibernate for our ORM, and the Student and Teacher classes are joined subclasses of Person in our mappings (we have Person, Student, and Teacher tables), using the PersonID as a derived key.

The problem, as you may have anticipated, is now we have a situation where a Student can also be a Teacher. Under the current object and data models, this causes a primary key violation when we try to add a Student that is already a Teacher or vice-versa.

Question One: Is there any CSLA magic I can perform on the business object before saving the new record to prevent this situation?

If not...

In researching this problem, I've seen two suggestions to solve it, The first is to favor composition over inheritance. To my understanding, this means Student and Teacher would each contain a Person property.

Question Two:
In order to get this to work, is it correct to assume the Student and Teacher tables would need a foreign key into the Person table? I guess I don't have a complete understanding of this solution, and would like some guidance.

The second solution involves thinking of Student and Teacher as Roles a given person plays. From what I've seen, the Person class would need a Roles collection, and link table (PersonRoles?) is used to map the records in the Student and Teacher tables to the Person.

Question(s) Three: What do the Role base class and the PersonRoles table look like? I believe the Student and Teacher subclasses would just contain their appropriate properties. Is this correct?

Any opinions on a solution? If anyone can find a fleshed-out example on the web, I'd love to see it.

Thanks,

Will.

 

 

 

 

ajj3085 replied on Monday, March 28, 2011

I've found NHibernate to be too constraining when trying to directly map your BOs to the database, for this exact reason.  EF might be better in this regard, as I believe the mappings are a bit more advances (it looks to me like you map your tables, map your BOs, then a third map links the two), but I think there will simply always be things you can't just map away.

We hit similar pain at work, and other problems came up as a result of directly mapping our BOs with NH.  Our solution was to create classes to match the table structure, map those in NH, and then load our BOs from those classes.  When saving we did the opposite; rebuilding the data classes and having NH save those.

Basically you follow the same pattern as you would if you were using raw ado.net but instead of pulling each properties value out of a dictionary, you can pull it out of the data class' property.

willrogers replied on Monday, March 28, 2011

Thanks for your reply, Andy. Unfortunately, we're welded to NHibernate as we have 75 or more mappings complete and working with it. My gut tells me the problem is in the domain model, not in the data layer.

tmg4340 replied on Tuesday, March 29, 2011

Your gut is right.  Smile

CSLA objects should be modeled to fit the business process/use case, not the database design.  Business objects are there to do the job the application needs, regardless of how the data the application works with is stored.  Build your objects to fit the use case in question, and then create a separate mapping layer for NHibernate, like Andy did for his app.  Then you can populate your CSLA BO's from your mapping classes.

Also remember that while reuse is a goal of OOD, it isn't an overarching goal.  You'll find a lot of discussions on this topic here in the forum, but basically what it boils down to is that an inheritance hierarchy creates very tight coupling, so you need to be absolutely sure that the functionality in your base classes is used (and is correct) for all your subclasses.  But you also need a good definition of "functionality in your base classes".

If you read Rocky's posts, you'll find that things like properties are not "base class functionality", because they don't do anything - they're boilerplate.  You can also make an argument that common data-access code is not "base class functionality" as well.  That can a little more nebulous, because that code doesn't directly serve the business purpose - but without persisted data, your app may not be worth a whole heck of a lot.  You'll also find some people say that if you have to do the same thing to an object, rather than put it in a base class, you factor that out into a separate class and deal with it that way.  That is a form of composition, though not necessarily in the strict sense.

I can't speak for the majority of the group, but I find my CSLA object graphs to be pretty shallow.  I don't have a lot of base classes beyond the layer that Rocky recommends - which is to create a set of base classes that inherit from the CSLA types but don't add any functionality.  This provides a "seam" against change in the CSLA framework, and also provides you a place to put your common application-specific functionality.  But most of the time, that's it.  I don't create very deep object hierarchies anymore.

In terms of your specific problem, without knowing more about the problem domain, it's a little hard to suggest a solution.  From a database perspective, it appears that you've made the assumption that a Person record links with either one Student record or one Teacher record, and that's no longer the case.  Fixing that requires more code in your data layer, and possibly a database change as well, depending on how your keys are constructed.  There is, unfortunately, no "CSLA magic" that's going to help that.  But in terms of discussing whether you should have Student/Teacher classes, or Student/Teacher roles - well, that depends on the problem domain.  At first blush, I'd tend to go with the role concept, but a lot of that depends on what students and teachers can do, and what a "hybrid" person (someone who is both a student and a teacher) can do - because I can't necessarily assume that said hybrid can do everything.

Given the amount of road you've already walked down, you're also going to have to evaluate whether you have the time/money/etc. to potentially back up a significant amount to change your database (and domain object) structure...

- Scott

willrogers replied on Tuesday, March 29, 2011

Thanks for the input, Scott.

To expound a bit on our design, we've got a layer of Data Transfer Objects (DTOs) between the DB and the Business Objects (BOs). The NHibernate mappings map the DTOs to the database structure, and then we have mappings from our DTOs to our BOs. So we've structured flexibility into our design but unfortunately haven't taken good advantage of it.

Given how far along we are with this project, we're looking for a minimal refactor here. With the understanding that Student and Teacher are analogs to our domain-specific entities, the reality is (e.g.,) that our "Student" is a generalized and very widely used BO that we can't afford to refactor. Our "Teacher" class, however, is much more purposefully used and frankly is not referenced as often. Given all of this, it's safest for us is to concentrate our efforts on the Teacher class. Changes can be made to the Person class, I believe, as long as the Student class usage doesn't require refactoring.

So we've gone down the wrong path and are unable to completely undo it. Our hope is we can correct things in the Teacher-Person relationship, and follow this model with any future usage of the Person class.

I'm still not sure which direction we're going to take it. I'll be talking to the architect in the near future.

ajj3085 replied on Tuesday, March 29, 2011

Oh, so it sounds like you're not mapping your Csla based objects directly with NHibernate?  So you have a PersonData which is mapped, and a StudentData and TeacherData, which are also mapped (NHibernate subclass I'd imagine)?

Can you change the StudentData and TeacherData so that they no longer inherit PersonData, but instead point to a PersonData?  I don't believe that would change your database design (or you could still keep it working), but you would have to change code in your DataPortal_xyz methods to use the new relationship.  No reason you can't do this:

// In DP_insert

var studentDto = new StudentData { Person = new PersonData { PersonId = Id, FirstName = FirstName }, GPA = 3 };

Does that make sense?

This answer to your question on SO is what I was getting at:  http://stackoverflow.com/questions/5461564/need-domain-modeling-advice-hopefully-common-scenario/5464974#5464974

tmg4340 replied on Tuesday, March 29, 2011

Well... if you can't refactor Student, and you can't break the current inheritance chain, you're not left with a lot of options.

1. Duplicate the required Student functionality within Teacher.  Not a great idea, but it is probably the fastest solution.

2. Pull out the duplicate functionality into a separate class, and reference that class within both Student and Teacher.  That doesn't change the signature of Student - it just delegates to the separate class - but it eliminates the duplication (and potentially gets you the "favor composition over inheritance" buzzphrase as well.  Smile)  Whether you want to make that shared class an instance inside your Student and Teacher classes, or a kind of static service class that acts on your Student and Teacher instances, depends on what it needs to do, and your architectural preferences.

If you consider #2, I would not assume you can put the shared functionality within Person.  It's the obvious choice, and there's nothing intrinsically wrong with it.  Just be sure that if you ever create a different kind of Person subclass (GraduateAssistant?) that the functionality will be needed there - or that you build in the time to restructure your class hierarchies.  Smile

HTH

- Scott

Bowman74 replied on Wednesday, March 30, 2011

Will,

It sounds like you are generally dealing with your classes either as Students or Teachers but not generally directly as the type "Person". You have also stated that you want to do this with a minimum of refactoring of your existing code.

If this is the case then what you are really trying to maintain are the public properties and methods exposed by the Student and Teacher classes (including any inherited public methods). Or put another way, you don't want to refector the code that uses the Student class but changes could be done to the student class that are not visible externally.  There is a way to do this but I want to state upfront that there are several drawbacks and this is not how I would design it from scratch. Having said that, as you are well aware there is not always an intersection between the optimal design and the design project constraints allows you to do.

Ok, this is what you could do; use containment and delegation. Modify your Student and Teacher classes so they no longer inherit from Person. Then add all of the public Person property and method signatures to the Student and Teacher classes and add implementation code so that they call into a contained Person class. I.e. the Student and Teacher classes would be top level classes that call into a single child, a Person. Back in the COM days we used to jokingly refer to this as manual inheritance.  All of your external implementation code that deals with these classes won't know the difference as long as they were not dealing with them as type person.

You would also have to add a new constructor to create new Students and Teachers (to the correct classes) that are existing people in your system. These constructors would take a PersonId as an extra parameter for a new Student/Teacher that would load the appropriate contained class. So all the metadata specific to the Student/Teacher would be new but the contained Person object would not be (as you know this has a big impact on how they persist).

There are several drawbacks to this approach and this is not an exhaustive list by any means:

1) Your Student and Teacher classes will no longer to be as type Person, so if you are passing them around as type Person that means more refactoring that you are trying to stay away from.

2) Any new public properties/methods on the Person class will have to also be mirrored out on the Student and Teacher classes (hence, the manual inheritance joke).

3) You will still be dealing with the entity behavior wise as either a Student or a Teacher. They both can be the same underlying person but you either fetch them as one or the other. If this will be a problem will depend on your use cases.

I can't tell you if this will work for you as I don't know enough about your problem. At least it sounds like it will superficially meet your needs and avoid a lot of refactoring. This isn't what I would do from scratch but life sometimes is what it is.

Thanks,

Kevin

Copyright (c) Marimer LLC