Tuesday, January 13, 2015

Can my business object / entity / model class contain Save()?

This is a very old question. Lets try to answer it with the mind of a passionate programmer who wants his code to be the best quality code. Here we will be examining different possibilities and understand why some techniques are not recommended.

What if the Save() is part of my entity class?

Lets think in the way that, the Save() is part of the business object. What are the pros and cons

Pros

  1. Data and the functions manipulating it are in single unit and its encapsulated.

Cons

  1. If we want to change the data persistence mechanism from SQL Server database to XML, we want to modify all the entity classes.
  2. Violation of SRP - The class will change for 2 reasons. If a new property added or persistence mechanism changed.

What if the Save() is moved to Repository class

Here the Save() will be part of another class. Lets call that class as Repository. Pros and cons below

Pros

  1. Underlying storage mechanism can change without a change in the entity class.

Cons

  1. Repository might need separate methods to save different entities such as SaveEmployee() SaveOrder() etc...Otherwise one method which accepts base class of entity classes and use reflection to identity the properties to be saved. Need to keep track of what property maps to what field in database or node in xml.
  2. The consumer of the entity classes needs to remember one more class name to work with entities. API cannot be explored by developers. Need documentation.

What if the Save() is available in entity and repository and they are loosely connected?


In this technique, Save() will be available in entity class as well as repository class. The difference here is, the entity class method body will not contain any logic to persist the data. It just pass the execution to the repository. The repository knows how to persist. Some code snippet below

Core classes

public abstract class EntityBase
    {
        public abstract void Save();
    }
    interface IRepository
    {
        void Save(EntityBase entity);
    }
    static class RepositoryFactory
    {
        public static IRepository Get()
        {
            IRepository repository=default(IRepository);
            //Logic to decide repository based on config or any other criteria.
            return repository;
        }
    }

Below are the application specific classes.

    public class Account:EntityBase
    {
        public string Name { getset; }
        public decimal balance { getset; }
        public override void Save()
        {
            RepositoryFactory.Get().Save(this);
        }   
    }
 
    class SqlServerRepository:IRepository
    {
        void IRepository.Save(EntityBase entity)
        {
            //Use reflection to get the properties to be saved.
            //Or need to have separate methods like SaveAccount(), SaveCustomer() in the repository.
        }
    }
    //The UI parts. It can be the controller in case of MVC.
    class AccountViewmodel
    {
        Account acc = new Account() { Name = "Sales", Balance = 30000 };
        void SaveCommand()
        {
            acc.Save();
        }
    }

I know this is not a production ready code. But this gives idea how the Save() method can be written by honoring the SRP and keeping the easiness of API usage.