X

Modeling people and organizations: Employees

Modeling employees seems to be easy when we get started but it quickly turns more complex than we first expect. Specially if we need good and flexible model we don’t have to replace time by time. This post is continuation to my writing Modeling people and organizations: Party generalization and it extends the previous model with support to employees.

Popular misleading example

We have all probably seen examples like this in internet when we start looking for information describind how to model employees.

And guess what – it’s a lie. When modeling employees this way will get to the dead-end street. And even worse – after first real-life requirements we have to abandon this model. Before focusing on problems this model comes with we need to know one basic generalization.

Party generalization

Let’s jump back to my writing about Party generalization. This post described how to get classes for people and organizations to same inheritance chain by using general Party class that other classes extend. Party class is abstract (in most cases) and for each type of party separate concrete class is needed.

Using this construct it was possible to handle companies and people in code using Party class where they need to be handled as same type. Here’s the code to illustrate party generalization.

var parties = new List<Party>
{
    new Company { Name = "The Very Big Corporation of America" },
    new Person { FirstName = "John", LastName = "Doe" },
    new Person { FirstName = "Jane", LastName = "Doe" }
};

foreach(var party in parties)
{
    Console.WriteLine(party.DisplayName);
}

When run it writes to console window display names of all parties in list.

Who is employee?

Let’s first bomb down the first model shown above. This model doesn’t survive some things from everyday’s life.

  • Employee leaves the job and applies later succesfully again. We can change field values for employee to reflect new situation but all information about previous employment will be lost. Also if we have data related to employee we may need to remove it too making data loss even bigger.
  • Employee is promoted to manager position. We can create new manager instance, copy values there from employee and remove instance of employee. It’s everything else but not the reason why object-oriented programming was invented.

It turns out that modelling employee to party inheritance chain is not valid solution.

Modeling employees

What we can do with employees then? Well, there’s better and more flexible approach for employees. In our model employee as person can be just one Party. Manager, cleaning lady, office assistant – these all are job positions. Do you understand where I’m going with my ideas? In data model I want to replace employee with employment contract and say that all those parties who have valid employment contract are our employees.

Let’s draw out some classes to visualize the idea.

Here are C# classes for the new model.

public abstract class Party
{
    public int Id { get; set; }
    public abstract string DisplayName { get; internal set; }
}

public class Company : Party
{
    public string Name { get; set; }
   
    public override string DisplayName
    {
        get { return Name; }
        internal set { }
    }
}

public class Person : Party
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public override string DisplayName
    {
        get { return LastName + ", " + FirstName; }
        internal set { }
    }
}

public class EmploymentContract
{
    public int Id { get; set; }
    public Person Employee { get; set; }
    public string JobPosition { get; set; }
    public DateTime ValidFrom { get; set; }
    public DateTime ValidThru { get; set; }
}

This model is flexible enough to support many cases related being employee. Some examples:

  • John was our employee two years ago and we hired him again.
  • John was previously accountant but now he is chief of accounting office.
  • Mary is part-time employee working at kitchen year around.
  • At summer Mary doesn’t have school and she will work for us helping catering services. For this we will sign additional contract with her. So, Mary has sometimes two parallel contracts with us.
  • We hire Bill for projects where we can apply his knowledge and skills. In average we have three contracts per year with Bill. Sometimes those contracts run in parallel.

When considering the list above I would say this model will support almost everything we need in business applications.

Employee hierarchies

There’s one more thing to cover. Those who have worked with Active Directory have probably seen something like Reports To. This is the way to express that who is whose boss. By example, Mary’s boss at kitcen is head chef Ronald and when working for catering Mary gets tasks from Lisa.

To support hierarchies we need to add new property to Employee class – ReportsTo.

It can be empty as by model CEO or board members doesn’t report to anybody although in real life they are reporting to stock markets, tax office and investors. Often our models for company personnel doesn’t cover these cases.

Here’s the updated version of EmploymentContract class.

public class EmploymentContract
{
    public int Id { get; set; }
    public Person Employee { get; set; }
    public string JobPosition { get; set; }
    public DateTime ValidFrom { get; set; }
    public DateTime ValidThru { get; set; }
    public EmploymentContract ReportsTo { get; set; }
}

NB! If company has hierarchies defined for job positions then it would be better to use job positions to detect who is employee reporting to. It adds complexity to model but it makes it easier to manage employee data.

Printing job position hierarchy

Let’s jump to code and write simple console application that uses test data to write out hierarchy of job positions.

var john = new Person { FirstName = "John", LastName = "Doe" };
var mary = new Person { FirstName = "Mary", LastName = "Shadows" };
var bill = new Person { FirstName = "Bill", LastName = "Smiths" };
var roland = new Person { FirstName = "Roland", LastName = "Simmons" };
var lisa = new Person { FirstName = "Lisa", LastName = "Bates" };

var johnContract1 = new EmploymentContract
{
    Id = 1,
    Employee = john,
    ValidFrom = new DateTime(2016, 2, 1),
    ValidThru = new DateTime(2018, 10, 4),
    JobPosition = "Accountant"
};

var johnContract2 = new EmploymentContract
{
    Id = 2,
    Employee = john,
    ValidFrom = new DateTime(2019, 1, 10),
    ValidThru = new DateTime(2024, 1, 10),
    JobPosition = "Head of Accounting Office"
};

var rolandContract = new EmploymentContract
{
    Id = 8,
    Employee = roland,
    ValidFrom = new DateTime(2015, 1, 5),
    ValidThru = new DateTime(2025, 1, 5),
    JobPosition = "Chef cook"
};

var lisaContract = new EmploymentContract
{
    Id = 9,
    Employee = lisa,
    ValidFrom = new DateTime(2016, 5, 5),
    ValidThru = new DateTime(2021, 5, 5),
    JobPosition = "Head of Catering"
};

var maryContract1 = new EmploymentContract
{
    Id = 3,
    Employee = mary,
    ValidFrom = new DateTime(2018, 2, 1),
    ValidThru = new DateTime(2023, 2, 1),
    JobPosition = "Cook, assistant",
    ReportsTo = rolandContract
};

var maryContract2 = new EmploymentContract
{
    Id = 4,
    Employee = mary,
    ValidFrom = new DateTime(2018, 6, 1),
    ValidThru = new DateTime(2018, 8, 31),
    JobPosition = "Catering staff",
    ReportsTo = lisaContract
};

var maryContract3 = new EmploymentContract
{
    Id = 5,
    Employee = mary,
    ValidFrom = new DateTime(2018, 6, 1),
    ValidThru = new DateTime(2018, 8, 31),
    JobPosition = "Catering staff",
    ReportsTo = lisaContract
};

var billContract1 = new EmploymentContract
{
    Id = 6,
    Employee = bill,
    ValidFrom = new DateTime(2019, 4, 12),
    ValidThru = new DateTime(2019, 6, 22),
    JobPosition = "Analyst"
};

var billContract2 = new EmploymentContract
{
    Id = 7,
    Employee = bill,
    ValidFrom = new DateTime(2019, 6, 12),
    ValidThru = new DateTime(2019, 8, 12),
    JobPosition = "Analyst"
};           

var parties = new List<Party>
{
    john, mary, bill,
    roland, lisa
};

var contracts = new EmploymentContract[]
{
    johnContract1, johnContract2,
    rolandContract, lisaContract,
    maryContract1, maryContract2, maryContract3,
    billContract1, billContract2
};

As we have now data to play with it’s time to write out employees.

Console.WriteLine("Name\t\tValidFrom\tValidThru\tPosition\t\tReportsTo");
foreach(var contract in contracts)
{
    Console.Write(contract.Employee.DisplayName);
    Console.Write("\t");
    Console.Write(contract.ValidFrom.ToShortDateString());
    Console.Write("\t");
    Console.Write(contract.ValidThru.ToShortDateString());
    Console.Write("\t");
    Console.Write(contract.JobPosition);
    Console.Write("\t\t");
    Console.WriteLine(contract.ReportsTo?.Employee.DisplayName);
}

When we run test application we will get the following result.

We can use these classes also for data models that are persisted in database.

Wrapping up

We started with popular example of Person, Manager and Employee just to get stuck with pretty basic requirements from real life. With just two examples of original model weakness we got to so much troubles that it was clear we need some better model. After applying Party generalization and moving from employee to employment contract in data model we gained a lot of flexibility. And even better – we can always go further with extending the final model provided in this post. In code it’s more complex to work with contract object than with employee that inherits from person but the flexibility of employment contract will make it up for sure.

Liked this post? Empower your friends by sharing it!

View Comments (0)

Related Post