Monday, July 31, 2006

BNL 02 - The Problem

01 - Introduction
02 - The Problem
03 - The Solution
04 - DAMP BNLs
05 - Multiple Contexts

The Problem
To show the value of Business Natural Languages we will start with an existing ruby application and then introduce the Business Natural Language. This approach should provide a good example of how to include a Business Natural Language in an application.

For the sample application imagine you've been contracted to replace a bonus calculation system for a pharmacy. The pharmacy chose to custom develop the software for calculating bonuses because of the varying bonus packages. They have given you profiles of 3 employees and the business rules required to generate bonus compensation.

Profiles:
John Jones
bonuses: $10,000 if the company posts a profit of 1 million
$20,000 if the company posts a profit of 2 million
$30,000 if the company posts a profit of 3 million or more
5% of toothbrush profits
bonus pay month: January


Jackie Johnson
bonuses: 1% of profits
2% of drug profits
bonus pay month: February

Joe Noone
bonuses: 1% of profits or $10,000, whichever is greater
bonus pay month: March

The application need only be run monthly; therefore it is okay if the process is executed manually. The existing application is executed as a rake task. It also has every employee's information hard-coded as rules in the application. Last years profit information is available in the data warehouse. For our purposes lets add some dummy warehouse data. This migration should give you the all the data you will need:
class Profit < ActiveRecord::Base
end

class WarehouseData < ActiveRecord::Migration
def self.up
create_table :profits do |table|
table.column :toothbrush_in_cents, :integer
table.column :drug_in_cents, :integer
end

Profit.new(:toothbrush_in_cents=>125050000, :drug_in_cents=>410040050).save

end

def self.down
drop_table :profits
end
end
The rake task that executes the current payroll process is pretty straightforward:

File: lib/tasks/payroll.rake
namespace :payroll do
desc "run payroll"
task :run => :environment do
output_file = "#{File.expand_path(File.dirname(__FILE__))}/../../tmp/#{Time.now.year}-#{Time.now.month}-bonus.log"
File.open(output_file,'w') {}
[JohnJones, JackieJohnson, JoeNoone].each do |employee|
employee.append_bonus_to(output_file)
end
end
end
Each employee ruby file is also fairly straightforward. They contain the logic to calculate a bonus, expressed as ruby.

File: app/models/john_jones.rb
class JohnJones
def self.append_bonus_to(bonus_log)
if Time.now.month == 7
profit = Profit.find(:first)
bonus = profit.total > 100000000 ? 1000000 : 0
bonus += 1000000 if profit.total > 200000000
bonus += 1000000 if profit.total > 300000000
bonus += profit.toothbrush_in_cents * 0.05

File.open(bonus_log,'a') do |file|
file << "Jim Jones bonus: #{bonus.round} cents\n"
end
end
end
end
File: app/models/jackie_johnson.rb
class JackieJohnson
def self.append_bonus_to(bonus_log)
if Time.now.month == 7
profit = Profit.find(:first)
bonus = profit.total * 0.01
bonus += profit.drug_in_cents * 0.02

File.open(bonus_log,'a') do |file|
file << "Jackie Johnson bonus: #{bonus.round} cents\n"
end
end
end
end
File: app/models/joe_noone.rb
class JoeNoone
def self.append_bonus_to(bonus_log)
if Time.now.month == 7
profit = Profit.find(:first)
bonus = (profit.total * 0.01).round

File.open(bonus_log,'a') do |file|
file << "Joe Noone bonus: #{bonus > 1000000 ? bonus : 1000000} cents\n"
end
end
end
end
Profit is a simple ActiveRecord::Base inheritor with total being it's only behavior

File: app/models/profit.rb
class Profit < ActiveRecord::Base
def total
toothbrush_in_cents + drug_in_cents
end
end
The existing application works; however, the level of maintainability is low. Unfortunately, employees are required to re-negotiate their bonus structure every year. Following these negotiations a programmer must be involved to change the employees bonus logic. Also, any time a new employee is hired, a new file must be introduced into the system to calculate that employee's bonus.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.