My struggles in understanding and learning about Object Oriented design, and the tools and knowledge I've taken from them.

Sunday, January 23, 2011

Another Layers of Programming Post

The first blog I wrote on the layers of programming, I feel like I really faltered on describing what "Business Logic" is.

Part of the reason for this is because the concrete definition of it wasn't as clear in my mind as it is now, and part of it was that I had more of an affinity towards the data access layer at the time, and I didn't find explaining business logic to be as exciting.

Times have changed, and I find business logic to be much more exciting than I did then. I also find this concept of "n-tier programming" to be one of the most challenging concepts to get right in object oriented programming.

So, I'll do a quick recap of the three main tiers in n-tier programming:

Data Access Layer: The code responsible for getting data from the database and delivering it to the business logic layer.

User Interface Layer: The code responsible for rendering output to the end-user. The user interface layer is mostly composed of user interface controls that render a user interface -- these controls can be the parent form that other controls are attached to, textboxes, comboboxes, radio buttons, and checkboxes for user input, buttons for user submission, list boxes and data grids for outputting collections of data...you get the idea.

Business Logic Layer: The code responsible for solving the business problems that software is being built to solve.

There are all kinds of implications for what the business logic layer is. Because object oriented programming helps us represent reality so well, I usually have my business logic layer define the concepts of the system I'm building.

For instance, suppose we're building a customer relationship management software (CRM).

Suppose this CRM is supposed to:
1. Keep track of our customers
2. Help us manage our inventory
3. Keep sales history for our customers

Obviously we could add more features to this CRM, but I think the above is adequate for describing what Business Logic does.

There's a lot of ways (methodologies) to build software - usually software is built after requirements have been written.

So, I'll preface these definitions with the fact that I'm building this business logic without having a requirements document.

But in this CRM, we need concepts defined, such as: what is a customer? what is a product? What is a sale? How do these concepts interact with one another?

The answers to the above questions really define what business logic is. For instance, one of the classes in my business logic layer may be Customer. The customer class would look like this:

public class Customer
{
private string _firstName;

private string _lastName;

private string _emailAddress;

private string _address;

public string FirstName
{
get { return _firstName; }
set { _lastName = value; }
}

public string LastName
{
get { return _lastName; }
set { _lastName = value; }
}

public string EmailAddress
{
get { return _emailAddress; }
set { _emailAddress = value; }
}

public string Address
{
get { return _address; }
set { _address = value; }
}
}

The above is a truncated version of what the Customer class would look like, but the above would also be the first lines of code that would come in the business logic layer, because all of the features of the CRM focuses around the concept of "Customer."

The next concept to define in our Business Logic is the concept of an Inventory Product. I would imagine that an inventory product would look a bit like the code below:


public class InventoryProduct
{
private string _partNumber;

private string _productName;

private bool _isActive;

public string PartNumber
{
get { return _partNumber; }
set { _partNumber = value; }
}

public string ProductName
{
get { return _productName; }
set { _productName = value; }
}

public bool IsActive
{
get { return _isActive; }
set { _isActive = value; }
}
}

So, the next question to answer is how does a Customer interact with an InventoryProduct? I would argue that a class called "ItemPurchase" would need to get created. An ItemPurchase would basically be a link between a Customer and an InventoryProduct, with a couple other attributes, such as a PurchaseDate and a Quantity, and maybe a couple other things, depending on what kind of information is getting collected about it.

Purchase would look as follows:

public class ItemPurchase
{
private Customer _customer;
private InventoryProduct _product;
private int _quantity;
private DateTime _date;

public ItemPurchase(Customer customer, InventoryProduct product, int quantity)
{
_customer = customer;
_product = product;
_quantity = quantity;
_date = DateTime.Now;
}

///Other methods to save the purchase
}

This link between Customer and InventoryProduct is great, but we've got another problem: the problem is that the ItemPurchase object only covers one item. Obviously, a customer can purchase multiple things at one time, so we need another collection class. Maybe it could be called "ShoppingCart." I'll exclude the code for this because this is getting to be kind of a long blog entry, but I think you get the idea.

The shopping cart would serve to link an entire purchase to a customer...which leads us to another collection - PurchaseHistory. A PurchaseHistory object would be a list of ShoppingCart objects, and this PurchaseHistory object would get linked to a Customer object, and the Customer class would HAVE-A PurchaseHistory object in it. This relationship in the business logic also lines up very well with how the database would be designed (not covering database design in this post).

So, since I'm creating an PurchaseHistory object, which is essentially a List of ShopingCart objects, AND since I don't want to violate Open-Closed principle, I would start by creating a InventoryProductCollection object, and have PurchaseHistory inherit from that:

public class ShoppingCartCollection : List <ShoppingCart>
{

}

public class PurchaseHistory : ShoppingCartCollection
{
Customer _customer;

public PurchaseHistory(Customer customer)
{
_customer = customer;
this.populate();
}

private void populate()
{
///Go to the data access layer to grab information about this customer's purchase history
}
}

Notice in PurchaseHistory, the default constructor has a parameter of Customer. This makes sense to me because a PurchaseHistory, as we've defined it so far, does not seem to exist outside the context of a Customer. There's a lot of conversation that can be had around this, and how a PurchaseHistory will exist, but for this example, we'll just assume this will always be the case.

Because a customer will HAVE-A PurchaseHistory, I would modify the Customer class as follows:

public class Customer
{
///All of the code above

private PurchaseHistory _purchases;

public PurchaseHistory Purchases
{
get
{
if(_purchases == null)
{
_purchases = new PurchaseHistory(this);
}
return _purchases;
}
}
}

Notice in the getter in Customer, I check to see if the _purchases member is null - this is a trick called "Lazy initialization," which helps me avoid instantiating a PurchaseHistory object until I need it. This may be the right thing to do or the wrong thing to do, depending on all kinds of other factors (size and optimization of database indexes, how often a Customer's PurchaseHistory will be accessed, how often a Customer's PurchaseHistory will be changed, etc, etc, etc).

So, the business logic we've defined will:

1. Get its data from the database
2. Deliver it to the user interface
3. The data will (presumably) be changed in the user interface
4. The request to save to the database will come from the user interface
5. Prompting our business logic to tell the data access layer to save the changed data to the database.

That's business logic as I see it, and this is where a programmer generally earns his (or her) wage. The ability to see the business concepts as relationships, and then represent them in code is a real skill that takes years of tweaking to get right. There's all kinds of pitfalls and obstacles that come with almost any strategy one uses when building this stuff, ranging from scalability issues, extensibility issues, code management issues because of poor design or overly complicated design, performance problems because of use (or lack thereof) of lazy initialization, and a host of other things.

But, this is the world as I see it, and I hope it helps.

1 comment:

Moose said...

I know this is just a short and sweet example, but for the "lazy initialization" you could have mentioned using the Singleton Design Pattern for initialization in order to keep your code elegant. Although for the example you are doing what you did is completely fine.

Followers

Search This Blog

Powered by Blogger.