Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Design a generic business entity and still be OO?

I am working on a packaged product that is supposed to cater to multiple clients with varying requirements (to a certain degree) and as such should be built in a manner to be flexible enough to be customizable by each specific client. The kind of customization we are talking about here is that different client's may have differing attributes for some of the key business objects. Also, they could have differing business logic tied in with their additional attributes as well

As an very simplistic example: Consider "Automobile" to be a business entity in the system and as such has 4 key attributes i.e. VehicleNumber, YearOfManufacture, Price and Colour.

It is possible that one of the clients using the system adds 2 more attributes to Automobile namely ChassisNumber and EngineCapacity. This client needs some business logic associated with these fields to validate that the same chassisNumber doesnt exist in the system when a new Automobile gets added.

Another client just needs one additional attribute called SaleDate. SaleDate has its own business logic check which validates if the vehicle doesnt exist in some police records as a stolen vehicle when the sale date is entered

Most of my experience has been in mostly making enterprise apps for a single client and I am really struggling to see how I could handle a business entity whose attributes are dynamic and also has a capacity for having dynamic business logic as well in an object oriented paradigm

Key Issues

  • Are there any general OO principles/patterns that would help me in tackling this kind of design?

I am sure people who have worked on generic / packaged products would have faced similar scenarios in most of them. Any advice / pointers / general guidance is also appreciated.

My technology is .NET 3.5/ C# and the project has a layered architecture with a business layer that consists of business entities that encompass their business logic

like image 209
Jagmag Avatar asked Jan 29 '11 19:01

Jagmag


1 Answers

This is one of our biggest challenges, as we have multiple clients that all use the same code base, but have widely varying needs. Let me share our evolution story with you:

Our company started out with a single client, and as we began to get other clients, you'd start seeing things like this in the code:

if(clientName == "ABC") {
    // do it the way ABC client likes
} else {
    // do it the way most clients like.
}

Eventually we got wise to the fact that this makes really ugly and unmanageable code. If another client wanted theirs to behave like ABC's in one place and CBA's in another place, we were stuck. So instead, we turned to a .properties file with a bunch of configuration points.

if((bool)configProps.get("LastNameFirst")) {
    // output the last name first
} else {
    // output the first name first
}

This was an improvement, but still very clunky. "Magic strings" abounded. There was no real organization or documentation around the various properties. Many of the properties depended on other properties and wouldn't do anything (or would even break something!) if not used in the right combinations. Much (possibly even most) of our time in some iterations was spent fixing bugs that arose because we had "fixed" something for one client that broke another client's configuration. When we got a new client, we would just start with the properties file of another client that had the configuration "most like" the one this client wanted, and then try to tweak things until they looked right.

We tried using various techniques to get these configuration points to be less clunky, but only made moderate progress:

if(userDisplayConfigBean.showLastNameFirst())) {
    // output the last name first
} else {
    // output the first name first
}

There were a few projects to get these configurations under control. One involved writing an XML-based view engine so that we could better customize the displays for each client.

<client name="ABC">
    <field name="last_name" />
    <field name="first_name" />
</client>

Another project involved writing a configuration management system to consolidate our configuration code, enforce that each configuration point was well documented, allow super users to change the configuration values at run-time, and allow the code to validate each change to avoid getting an invalid combination of configuration values.

These various changes definitely made life a lot easier with each new client, but most of them failed to address the root of our problems. The change that really benefited us most was when we stopped looking at our product as a series of fixes to make something work for one more client, and we started looking at our product as a "product." When a client asked for a new feature, we started to carefully consider questions like:

  • How many other clients would be able to use this feature, either now or in the future?
  • Can it be implemented in a way that doesn't make our code less manageable?
  • Could we implement a different feature that what they are asking for, which would still meet their needs while being more suited to reuse by other clients?

When implementing a feature, we would take the long view. Rather than creating a new database field that would only be used by one client, we might create a whole new table which could allow any client to define any number of custom fields. It would take more work up-front, but we could allow each client to customize their own product with a great degree of flexibility, without requiring a programmer to change any code.

That said, sometimes there are certain customizations that you can't really accomplish without investing an enormous effort in complex Rules engines and so forth. When you just need to make it work one way for one client and another way for another client, I've found that your best bet is to program to interfaces and leverage dependency injection. If you follow "SOLID" principles to make sure your code is written modularly with good "separation of concerns," etc., it isn't nearly as painful to change the implementation of a particular part of your code for a particular client:

public FirstLastNameGenerator : INameDisplayGenerator
{
    IPersonRepository _personRepository;
    public FirstLastNameGenerator(IPersonRepository personRepository)
    {
        _personRepository = personRepository;
    }
    public string GenerateDisplayNameForPerson(int personId)
    {
        Person person = _personRepository.GetById(personId);
        return person.FirstName + " " + person.LastName;
    }
}

public AbcModule : NinjectModule
{
     public override void Load()
     {
         Rebind<INameDisplayGenerator>().To<FirstLastNameGenerator>();
     }
}

This approach is enhanced by the other techniques I mentioned earlier. For example, I didn't write an AbcNameGenerator because maybe other clients will want similar behavior in their programs. But using this approach you can fairly easily define modules that override default settings for specific clients, in a way that is very flexible and extensible.

Because systems like this are inherently fragile, it is also important to focus heavily on automated testing: Unit tests for individual classes, integration tests to make sure (for example) that your injection bindings are all working correctly, and system tests to make sure everything works together without regressing.

PS: I use "we" throughout this story, even though I wasn't actually working at the company for much of its history.

PPS: Pardon the mixture of C# and Java.

like image 174
StriplingWarrior Avatar answered Oct 12 '22 02:10

StriplingWarrior