Friday, January 4, 2019

Repository Pattern

The repository pattern is used to create an abstraction layer between the DAL (data access layer) and the BAL (business access layer) to perform CRUD operations

If an application does not follow the Repository Pattern, it may have the following problems:

Duplicate database operations codes
Need of UI to unit test database operations and business logic
Need of External dependencies to unit test business logic
Difficult to implement database caching, etc.
Using the Repository Pattern has many advantages:

Your business logic can be unit tested without data access logic;
The database access code can be reused;
Your database access code is centrally managed so easy to implement any database access policies, like caching;
It’s easy to implement domain logic;
Your domain entities or business entities are strongly typed with annotations;

We have an entity model

public class PersonModel
{
    public string Name { get; set; }
    public int Age { get; set; }
}

The service that is loading a person out of the database is ICompanyLogic. It consists of the following method definition.

public interface ICompanyLogic
{
    PersonModel GetPersonByName(string name);
}

The implementation of the ICompanyLogic is handled by CompanyLogic.

public class CompanyLogic: ICompanyLogic
{
    private IPersonDataContext _personDataContext;
    public PersonService(IPersonDataContext personDataContext)
    {
        _personDataContext= personDataContext;
    }

    public PersonModel GetPersonByName(string name)
    {
        using(var ctx = _personDataContext.NewContext())
        {
            var person = ctx.People.First(p => p.Name.Equals(name));
            return person;
        }
    }
}


So far, this isn't so bad. We have a business service CompanyLogic that can retrieve a single person from the database.

But then we have a new requirement that says we also need a way to load a company from another database. So we need to add a new method and extend CompanyLogic.

CompanyModel represents the model stored in the company database.

public class CompanyModel
{
    public string Name { get; set; }
    public int Size { get; set; }
    public bool Public { get; set; }
}

We extend CompanyLogic to have a method that returns a company by name.


public class CompanyLogic: ICompanyLogic
{
    private IPersonDataContext _personDataContext;
    private ICompanyDataContext _companyDataContext;
    public PersonService(IPersonDataContext personDataContext, 
                         ICompanyDataContext companyDataContext)
    {
        _personDataContext= personDataContext;
        _companyDataContext = companyDataContext;
    }

    public PersonModel GetPersonByName(string name)
    {
        using(var ctx = _personDataContext.NewContext())
        {
            var person = ctx.People.First(p => p.Name.Equals(name));
            return person;
        }
    }

    public CompanyModel GetCompanyByName(string companyName)
    {
        using(var ctx = _companyDataContext.NewContext())
        {
            var person = ctx.Company.First(c => c.Name.Equals(companyName));
            return person;
        }
    }
}

Now we are starting to see the problems with this initial solution. Here is a short list of things that are not ideal.

CompanyLogic, knows how to access two different databases.
We have duplicated code with our using statements.
Our logic knows how people and companies are stored.
GetPersonByName and GetCompanyByName cannot be reused without bringing in all of CompanyLogic.
In addition to all of these things, how do we test CompanyLogic in its current state? We have to mock the data context for people and companies to have literal database records.

Implementing Repository Pattern
The repository pattern adds an abstraction layer over the top of data access. 

Let's begin by creating our IPersonRepository interface and its accompanying implementation.

public interface IPersonRepository
{
    PersonModel GetPersonByName(string name);
}

public class PersonRepository: IPersonRepository
{
    private IPersonDataContext _personDataContext;
    public PersonRepository(IPersonDataContext personDataContext)
    {
        _personDataContext= personDataContext;
    }

    public PersonModel GetPersonByName(string name)
    {
        using(var ctx = _personDataContext.NewContext())
        {
            return ctx.People.First(p => p.Name.Equals(name));
        }
    }
}

Then we can do something very similar for companies. We can create the ICompanyRepository interface and its implementation.

public interface ICompanyRepository
{
    PersonModel GetCompanyByName(string name);
}

public class CompanyRepository: ICompanyRepository
{
    private ICompanyDataContext _companyDataContext;
    public CompanyRepository(ICompanyDataContextcompanyDataContext)
    {
        _companyDataContext= personDataContext;
    }

    public CompanyModel GetCompanyByName(string name)
    {
        using(var ctx = _companyDataContext.NewContext())
        {
            return ctx.Company.First(p => p.Name.Equals(name));
        }
    }
}

We now have two separate repositories. PersonRepository knows how to load a given person by name from the person database. CompanyRepository can load companies by name from the company database. Now let's refactor CompanyLogic to leverage these repositories instead of the data contexts.

public class CompanyLogic: ICompanyLogic
{
    private IPersonRepository _personRepo;
    private ICompanyRepository _companyRepo;
    public PersonService(IPersonRepository personRepo, 
                         ICompanyRepository companyRepo)
    {
        _personRepo= personRepo;
        _companyRepo= companyRepo;
    }

    public PersonModel GetPersonByName(string name)
    {
        return _personRepo.GetPersonByName(name);
    }

    public CompanyModel GetCompanyByName(string companyName)
    {
        return _companyRepo.GetCompanyByName(companyName);
    }
}

Look at that, our logic layer no longer knows anything about databases. We have abstracted away how a person and a company are loaded. So what benefits have we gained?

The repository interfaces are reusable. They could be used in other logic layers without changing a thing.
Testing is a lot simpler. We mock the interface response so we can focus on testing our logic.
Database access code for people and companies is centrally managed in one place.
Optimizations can be made at a repository level. The interface is defined and agreed upon. The developer working on the repository can then store data how she sees fit.

 The moral of the story is that data access should be a single responsibility interface. This interface can then be injected into business layers to add any additional logic.

No comments:

Followers

Link