'Separating business logic

I have a customers Class with properties and methods defined. At present it contains methods for any type of task associated with Customers. For example, it contains a method "InsertOrUpdateCustomer". This method either inserts a new customer record to the database or facilitates editing of an existing customer record.

This class also contains some validation methods for customer fields.

I think that it is not a better way. I want to break it somewhat like this:

interface ICustomer
{
    string CustomerName;
    date FinancialYearStartDate;
    date FinancialYearEndDate;
    string TaxNo;
    string Address;
}

I want to implement this interface to another class, say Customers:

class Customers: ICustomer
{
    // Properties
    CustomerName { get; set; }
    FinancialYearStartDate { get; set; }
    FinancialYearEndDate  { get; set; }
    TaxNo { get; set; }
    Address { get; set; }

    // Constructor
}

I want to know:

  1. Where to add methods to Insert or Update new customer? Should I create another class or add methods to the above class?

  2. Breaking my old single class with the above given way is beneficial or not? What benefit an Interface has in the above code?

  3. I want to remove validation methods and use a Validation Framework instead. Do I need to create a different class "CustomerValidations" where I would do validations or I should use above class itself?



Solution 1:[1]

  1. For insert and update methods, I'd create a repository, with CRUD methods (e.g. ICustomerRepository)
  2. I don't see the immediate benefit of the ICustomer interface
  3. I would use the approach where validation is outside the business entity; e.g. in dedicated validation classes, or in a configuration file like spring.net validation.

Overall, I think it is a good idea to have a single responsibility per class - e.g. business state Customer, persistent storage NHibernateCustomerRepository : ICustomerRepository and validation CustomerValidator.

An example for the repository:

interface ICustomerRepository
{
  // Get by id
  Customer Get(int id);
  void Delete(Customer customer);
  IList<Customer> GetAll();
  // creates a new instance in datastore
  // returns the persistent identifier
  int Save(Customer customer);
  // updates if customer exists,
  // creates if not
  // returns persistent identifier
  int SaveOrUpdate(Customer customer);
  // updates customer
  void Update(Customer customer);

  // specific queries
  IList<Customer> GetAllWithinFiscalYear(DateTime year);
  // ...
}

As you can see, the first methods of this interface will be similar for most business entities and could be abstracted into:

interface IRepository<TId, TEntity>
{
  // Get by id
  TEntity Get(TId id);
  void Delete(TEntity entity);
  IList<TEntity> GetAll();
  // creates a new instance in datastore
  // returns the persistent identifier
  TId Save(TEntity entity);
  // updates if customer exists,
  // creates if not
  // returns persistent identiefier
  TId SaveOrUpdate(TEntity entity);
  // updates customer
  void Update(TEntity entity);
}

interface ICustomerRepository : IRepository<int, Customer>
{
  // specific queries
  IList<Customer> GetAllWithinFiscalYear(DateTime year);
}

Solution 2:[2]

My answers:

  1. I'd put Insert method in the class that will contain ICustomers (I suppose there will be one or more). As for the Update, it depends on what this method does. If you are updating some Customer's inner fields/properties it should go with ICustomers;

  2. One of the greatest benefit, IMHO, is that it's much easier to unit test code that depends on Customers since you can easily mock/stub it.

  3. I'd use the class itself.

Solution 3:[3]

An action such as 'InsertOrUpdateCustomer' is usually part of a customer entity service (in adaptor model) (i.e. in another class, as you suggest)

The way to think of it is: 'whose responsibility is it to save the customer?'

One possibility is to 'inject' a ICustomerValidator into the Save method.

Solution 4:[4]

Introducing an interface for a class that essentially is a data container is rarely beneficial. Instead you might want to create two classes with database persistence and validation roles respectively. There an interface might give you the chance to make the classes interchangeable for testing or different storage/validation strategies.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Simone
Solution 3
Solution 4 Manrico Corazzi