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

Free DevExpress Products - Get Your Copy Today

The following free DevExpress product offers remain available. Should you have any questions about the free offers below, please submit a ticket via the DevExpress Support Center at your convenience. We'll be happy to follow-up.
Tags
No Comments

Please login or register to post comments.