Feature 1 – onboarding an employee

In our problem statement, we did not clearly define what happens when a new employee joins. We would detail that out now so that we have something meaningful to work with. Let's say, the following needs to happen when a new employee joins:

  • Employee is awarded 21 sick leaves and 24 paid leaves
  • A skill enhancement allowance of £1,000 is allocated
  • Employee record is saved in database

First two items are clearly a part of business logic. We can say that this logic can live within the Employee class. Third item is not a domain concern but more of an infrastructure concern. So we would want to push it out to one of the outer layers of onion architecture. It is difficult to say which layer this items belongs to exactly. So instead of trying to answer that question now, let's work with what we have got and see where that takes us.

First of all, we would need a controller and an action defined on the controller that accepts a POST request for onboarding a new employee. We will not go into the details of controllers since it is not very important for our discussion. Besides, if you have used ASP.NET MVC for some time, you would know enough about writing controller actions. Now let's go back to the two items that we said to be fitting for business logic. We would implement this business logic in the Employee class in the form of a public method. In this method we create new instances of appropriate Benefit entities, set property values, and add them to the Benefits collection on Employee. Following is how this code looks:

public virtual void Onboard()
{
  AddBenefit(new Leave
  {
    AvailableEntitlement = 21,
    Type = LeaveType.Sick
  });

  AddBenefit(new Leave
  {
    AvailableEntitlement = 24,
    Type = LeaveType.Paid
  });

  AddBenefit(new SkillsEnhancementAllowance
  {
    Entitlement = 1000,
    RemainingEntitlement = 1000
  });
}

There are two missing pieces now. We need somewhere to call this method from and then persist this employee instance of database. Call to the Onboard method can be placed in controller action but hosting the persistence logic inside controller action is wrong. First of all, it is not the responsibility of controller action to carry out this coordination logic. Second, we will be promoting an anti-pattern called thick controller. A controller's job is to coordinate between model and view. When controllers start housing more logic than that, then they start becoming thick and hence the name thick controller. Thick controllers are like monolithic software - difficult to test, maintain, and change.

We need something that sits between domain entities and UI layer. During the introduction of onion architecture, we talked about domain services. Domain services is where we can write business logic that cannot be fitted into domain layer. Let's introduce a domain service layer and see where that takes us. We begin by adding a new project into our solution and name this as DomainService. This is a usual class library project. This project depends on the Domain project. We then add a class EmployeeOnboardingService to this project with a method Execute, as shown next:

using Domain;

namespace DomainService
{
  public class EmployeeOnboardingService
  {
    public void Execute(Employee employee)
    {
      employee.Onboard();
      //Persit employee instance
    }
  }
}

Note

You may be wondering that we are adding layers unnecessarily. But as we progress, it will become clearer that a domain service layer brings benefits that are not possible to achieve without such a layer. Also, the business logic we have got now is very simple. It is possible for you a feel that all these layers are not needed for such a simple logic. But remember, this is just an example and in real life you may be dealing with logic that is more complex than this.

This has taken us a few steps further but we still do not seem to have solved the original problem of where do we write code to actually persist the employee instance to database. We could call ISession.Save from the preceding code but that would mean the EmployeeOnboardingService class needs to take dependency on ISession. That would further lead to the DomainService layer taking dependency on NHibernate. This goes against the onion architecture. The DomainService layer should not be dependent on infrastructure code, NHibernate in this case. So how do we solve this conundrum? The answer is in inversion of control and explicitly declaring required capabilities. The EmployeeOnboardingService class requires a capability to persist employee instance to database. It does not need to bother itself with who offers that capability or how is it actually fulfilled. If made available, it just uses that capability.

At this point, allow me to detour into discussion of a design pattern called repository pattern. Thoughtful implementation of repository pattern can provide the capability that the EmployeeOnboardingService class needs right now.

Repository pattern

On one hand, using design patterns is a good thing whereas on the other hand, too many patterns from the start could lead to unnecessary complexity and too much code to maintain. But some software problems and their solutions in a particular design pattern are so widely used that they have gone through good test of time. Repository pattern is one such design pattern to address common issues around data access. I am not saying that repository pattern is the best solution to the problem but it is the easiest one to get started. Repositories have their own shortcomings and come with their own problems. We would look at these and alternatives in the next chapter. For now, I want to introduce you to this pattern so that you can make an educated choice.

Repository pattern abstracts away the details of specific database operations and the persistence API used to carry out those operations. This includes infrastructure code or any interaction with the database. If I need to save an entity, I give that entity to a repository and trust that repository would save the entity in database for me. The simplest implementation of such a repository could be as follows:

public class Repository<T> where T :EntityBase<T>
{
  public void Save(T entity)
  {
    //Code to persist entity to database
  }
}

Here we have added a generic parameter T so that the Save method is typesafe. We have also added a constraint that T must be a type that inherits from EntityBase<T>. This limits the types that can be passed to domain entities only.

Right now there is nothing in the Save method. Let's try to add some code to persist the supplied entity instance to database. We would need to get hold of current session object for that. Since ISession is a capability that this class needs, we would add a constructor that takes ISession as a parameter and stores it in a field. Following is how our repository looks now:

public class Repository<T> where T :EntityBase<T>
{
  private readonly ISession session;

  public Repository(ISession session)
  {
    this.session = session;
  }

  public void Save(T entity)
  {
    session.SaveOrUpdate(entity);
  }
}

Nothing complex here. We have added a call to ISession.SaveOrUpdate and passed the supplied entity parameter. Now that our basic repository skeleton is ready, next question is where in our architecture should we add repository?

Where does the repository belong?

We started with EmployeeOnboardingService needing a repository. We now have a repository that we can use but can we put the repository class next to the service class? The answer is clearly a no but let's contemplate why:

  • Repository<T> is abstracting away NHibernate behavior. This qualifies Repository<T> as infrastructure code. Following onion architecture principle, we do not want any infrastructure code in domain or domain service layer. So it better fits into the Persistence project of our solution.
  • The Repository<T> class is a concrete implementation. As per IoC principle no class should depend on a specific implementation but on a more generic contract. By that logic it would not be prudent to make the EmployeeOnboardingService class take dependency on the Repository<T> class. Moreover, if we have decided to keep the Repository<T> class in the Persistence project, then the EmployeeOnboardingService class does not know about it (no compile time dependency) and hence cannot take a dependency on it.

Taking the preceding two points into consideration, we would need to create an interface out of the Respository<T> class so that EmployeeOnboardingService can take a dependency on that interface. We also need to move this interface into the DomainService project so that it is visible from that project. We would leave the Repository<T> in the Persistence project. Following is how the code looks now:

namespace DomainService.Capabilities
{
  public interface IRepository<T> where T : EntityBase<T>
  {
    void Save(T entity);
  }
}

namespace Persistence
{
  public class Repository<T> : IRepository<T>
  where T :EntityBase<T>
  {
    //Same as before
  }
}

Note that nothing has changed inside the class Repository<T> other than it implementing interface IRepository<T>. A shorter version of this class is presented here to save space. Also, the interface IRepository<T> is moved in a namespace DomainService.Capabilities. This is because IRepository<T> exposes a capability that domain service layer needs. This goes in line with what we discussed in the beginning. Every layer explicitly declares (using interfaces or abstract classes) the capabilities that they need someone else to provide.

We have a basic repository implementation in place which looks finished for what we need now. Let's go back to finishing the employee onboarding functionality.

Back to onboarding an employee

A repository is the capability that our domain service class needs and it can express this requirement by taking dependency on the IRepository<T> interface from the DomainService.Capabilities namespace. We add the following code to the EmployeeOnboardingService class to satisfy the dependency requirement:

private readonly IRepository<Employee> employeeRepository;

public EmployeeOnboardingService(
IRepository<Employee> employeeRepository)
{
  this.employeeRepository = employeeRepository;
}

Here we have added a new constructor that takes a parameter of type IRepository<Employee> and saves the supplied value in a private field.

Let's use this repository capability to finish the onboarding logic:

public void Execute(Employee employee)
{
  employee.Onboard();
  employeeRepository.Save(employee);
}

A call to the IRepository<Employee>.Save method is all we need to save the employee instance. This all looks fine and dandy but what about unit of work? What about executing the whole operation as single unit? We might just implement unit of work somehow and be done with it but there are some peripheral concepts around unit of work that are interesting and important to understand.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.79.20