Doctors, Patients, Mechanics and Tax Evaders

XAF Team Blog
20 March 2008

In business applications, there's a situation we encounter regularly: a class hierarchy alone apparently doesn't allow us to model the complex reality of everyday life. Of course there's a lot to be said about this basic issue, but the problem I'd like to talk about here is the fact that an entity can "be" several things at once, in other words, it can have multiple roles.

Doctors and Patients

In our forums, an example was recently given involving people: assuming "people" are divided into three groups "Doctors", "Patients" and "all the rest", Oliver might be a Doctor and Dan might be a Patient. There's a chance that Oliver injures his arm drinking lots of Guinness and so becomes a Patient as well. On the other hand, Dan might go to med school and become a Doctor. A good example (although of course I would never drink so much Guinness ), and there are lots more: Dan could be a Mechanic in addition to being a Patient, and anybody who's a Doctor, a Patient and/or a Mechanic could be a TaxEvader.

Given this example, it seems clear that being a Doctor or a Patient has to be modelled in a different way than other properties, like for instance being a Man or a Woman. Deriving classes for Man and Woman from Person and distinguishing the two this way obviously makes sense, but using the same approach for Doctor, Patient, Mechanic or TaxEvader is equally obviously not going to do the trick, since these are not mutually exclusive and thus don't fit a simple hierarchical model.

Flags...

A very simple idea to solve the problem, which was also mentioned in the forum thread, is to use a common class for Doctors and Patients - perhaps the Person class - and to introduce flags (boolean fields) that say whether a certain instance of that class is considered to be a Doctor or a Patient or both. For simple cases this is probably worth a consideration, but this doesn't look like the best idea as soon as you'd need a few more than just two such flags.

... or Roles

It's not too hard to come up with a better way to implement this: roles, just like they are used in many security systems (the one delivered with XAF does this as well). I even used the word "roles" before, since it really seems to be a very useful non-technical language term to describe what Doctor, Patient, Mechanic and TaxEvader really are. A Person (or a Man or Woman, since they remain derived from Person) can get a bunch of roles assigned, which makes any combination possible. Of course there might be restrictions in reality pertaining to the combinations of roles that can be used - this would require some business logic code, based on the validation system in XAF. Since a Person can have any number of Roles and a Role can be assigned to any number of Person objects, there's naturally a many-to-many relationship here - see below for reasons to choose a different approach. XAF supports many-to-many relationships out of the box, so that there shouldn't be too much work required to get good UI functionality supporting this structure.

What's in a Role?

A question that remains is what exactly is actually in the Role? So far, the fact that a certain Role is assigned to a Person instance is simply a marker, denoting the Person object in question to be one of a certain kind. At this point it seems alright to just have a class Role with a Title property, and create a few instances of it with the different Titles "Doctor", "Patient" and so on. This might be a workable scenario for many cases, but sometimes there will be business logic associated with being a Doctor, for example, and so it may be a good idea to derive specific classes for each of the Role types, which can then add the necessary business functionality. On the other hand, this approach doesn't allow for new Roles to be created at runtime - it really depends on the requirements which approach is preferable.

A third possible approach would be one where the Roles are used for data storage, either in addition to business logic or without it. That would mean that instead of creating a set of Roles that don't change at runtime - the many-to-many scenario above - one could configure a one-to-many relationship instead and instantiate new Role instances (or instances of the derived classes, if this is combined with the business logic approach) as they are assigned to the Person instances.

As you can see, there are a lot of possible variations on this. In the end, the result is the same: a class structure that allows the Person to be associated with a collection of Roles. There's no limit to the number of Roles any given Person can have, and depending on requirements Roles can be created in application initialization code or at runtime. It is also easy to query Person instances that are associated with a particular Role.

Basics are Basics

At this point I would like to stress that so far the whole concept has not related to the UI in any way. Yes, I made a reference at some point saying that since XAF supports many-to-many out of the box, that makes it a good choice. But in any case this is a structure that is based solely on analysis of the situation and the requirements. When I started writing the test program that accompanies this article, I was pretty sure that XAF would either be able to deal with the structure out of the box, or would otherwise be easy to extend to deal with it. In theory, if I had found something that was actually impossible, I would have added a user story to our XAF plans for urgent consideration. The target is simple: if your business class structure is sound and follows good practices, it should be supported by XAF. We believe we've done a good job implementing this support up to this point, but we're always willing to learn - show us a business class structure that fits the description and that's not supported, and we'll try our best to help you!

Right, moving on to some examples. Based on the thoughts above, I implemented the following classes (and a few others for the other roles):

  public class RolePerson : Person {
public RolePerson(Session session) : base(session) { }

[Association("RolePerson-Roles")]
public XPCollection Roles {
get { return GetCollection("Roles"); }
}
}

[DefaultClassOptions]
public class Woman : RolePerson {
public Woman(Session session) : base(session) { }
}

[DefaultClassOptions]
public class Man : RolePerson {
public Man(Session session) : base(session) { }
}

public class Role : BaseObject {
internal Role(Session session) : base(session) { }

private string title;
public string Title {
get { return title; }
set { SetPropertyValue("Title", ref title, value); }
}

private string description;
public string Description {
get { return description; }
set { SetPropertyValue("Description", ref description, value); }
}

[Association("RolePerson-Roles")]
public XPCollection RolePersons {
get { return GetCollection("RolePersons"); }
}
}

public class DoctorRole : Role {
internal DoctorRole(Session session) : base(session) { }

public override void AfterConstruction( ) {
base.AfterConstruction( );
base.Title = "Doctor";
}
}

Also, in my Updater class I implemented the following methods to create the initial data (I'm going for the "separate classes for different roles, but no data storage" approach):

  public override void UpdateDatabaseAfterUpdateSchema( ) {
base.UpdateDatabaseAfterUpdateSchema( );

EnsureRoleSetup(Session);
}

private void CreateRoleIfNeeded(Session session, Type roleType) {
Role role = (Role) Session.FindObject(roleType, null);
if (role == null) {
if (typeof(Role).IsAssignableFrom(roleType)) {
Role newRole = (Role) Activator.CreateInstance(roleType,
BindingFlags.CreateInstance | BindingFlags.NonPublic | BindingFlags.Instance,
null, new object[] {session}, null);
newRole.Save( );
}
}
}

private void EnsureRoleSetup(Session session) {
CreateRoleIfNeeded(session, typeof(DoctorRole));
CreateRoleIfNeeded(session, typeof(PatientRole));
CreateRoleIfNeeded(session, typeof(MechanicRole));
CreateRoleIfNeeded(session, typeof(TaxEvaderRole));
}

And that's it, pretty much.

Of course it would be possible to create further UI around this functionality, such as special actions that assign certain roles automatically. One could also customize the UI to be a bit more end user friendly - perhaps the term Role is not the best for all types of applications (I do find it quite intuitive when talking about people, but in other use cases it might not fit so well). It's also thinkable to create an entire custom dialog for the purpose of assigning Roles, but given the standard implementation I think that's a bit over the top. I gave some extra thought to the issue of the UI since this was mentioned in the forum thread that triggered this research, but I don't find the standard implementation to be particularly lacking.

I'm going to post a forum thread pointing to this blog post in a little while. Please direct any discussion that requires answers to the forum instead of the comments on this post, as it's easier to deal with structured discussion there.

Finally, here's the download with the demo project I created: XAFRoles.zip

Tags
11 comment(s)
Robert Fuchs
Robert Fuchs

Good points, Oliver.

BTW, the demo project doesn't start.

I posted a screenshot in the XAF forum.

20 March, 2008
Oliver Sturm (DevExpress)
Oliver Sturm (DevExpress)

Robert,

Thanks for pointing that out. I've updated the post as well as the download with a change to account for the non-public constructor on the Role classes. That goes to show the perils of reflection, lack of unit tests for samples like this and too little testing - I did run the application in the status I posted, of course, but at that point the data in my database already existed and the line in question wasn't executed. Never mind, nothing to do with the topic at hand - perhaps I shouldn't have refactored the initialization code so much :-)

21 March, 2008
Mehul Harry (DevExpress)
Mehul Harry (DevExpress)

"There's a chance that Oliver injures his arm drinking lots of Guinness ..."

-- I can say that this is nearly impossible. Wink

21 March, 2008
Oliver Sturm (DevExpress)
Oliver Sturm (DevExpress)

You mean because of that artificial limb I use, right? Right?

21 March, 2008
Mehul Harry (DevExpress)
Mehul Harry (DevExpress)

What artificial limb?!? Oh ya, right, that's why. Smile

21 March, 2008
Anonymous
eXpress App Framework Team

Yesterday I blogged about a particular business class modelling problem pertaining to people who can

21 March, 2008
Ray Navasarkian (DevExpress)
Ray Navasarkian (DevExpress)

Oliver doesnt do Guinness anymore - he's a kamikaze man now...those are easy to lift.

22 March, 2008
Oliver Sturm (DevExpress)
Oliver Sturm (DevExpress)

It depends on the company I have, Ray :-)

23 March, 2008
James Smyth
James Smyth

So you have a Patient and of course a Patient has a doctor. How would you handle that in XAF with your structure? If you have a property in the patient role that refers to a Role Person then how would you restrict the user from picking other than a doctor?

23 March, 2008
Ray Navasarkian (DevExpress)
Ray Navasarkian (DevExpress)

True Oliver - You even drink the red wines I order ;-)

24 March, 2008
Oliver Sturm (DevExpress)
Oliver Sturm (DevExpress)

James -

As usual, the answer depends on a lot of considerations... there are two major ways, I think:

1. Create a RolePerson reference (or several of them) in the RolePerson, so you can assign other RolePersons to any given instance. Or perhaps, so you don't have to pollute the RolePerson with lots of these references, derive a class from it for this. Filter the list of possible selections on Doctor property to show only Doctors, and the other way round for a Patient property. The example in the "revision 2" article shows how to do the filtering.

2. Go for the one-to-many scenario (described above in the paragraph "what's in a role") and add a collection of patients to the doctor role, plus a doctor reference (or even a collection again) to the patient role. At a glance I think that this is probably the better way to go as soon as you find yourself needing to store information that is specific to the person's role as a doctor or patient.

24 March, 2008

Please login or register to post comments.