2
\$\begingroup\$

I attempted the Business Rules Kata. Here's a video overview.

However, I am not confident that going functional is a good strategy for the following objective:

How can you tame these wild business rules? How can you build a system that will be flexible enough to handle both the complexity and the need for change? And how can you do it without condemning yourself to years and years of mindless support?

Is there an alternative FP approach that satisfies the objective stated above?

module PaymentSystem

(*Types*)
type ProductId =          ProductId of string
type MemberId =           MemberId  of string
type Email =              Email     of string
type Agent =              Agent
type RoyaltyDepartment =  RoyaltyDepartment

type PackingSlip = {
    MemberId:MemberId
    ProductId:ProductId
}

type PhysicalProducts =
    | Book
    | Video
    | Other

type MembershipType =
    | Membership of MemberId
    | Upgrade    of MemberId

type PaymentFor =
    | PhysicalProduct of PhysicalProducts * PackingSlip
    | Membership      of MembershipType

type PackingSlipOptions =
    | PackingSlip       of PackingSlip
    | DuplicateSlips    of PackingSlip
    | WithFirstAidVideo of PackingSlip

type PaymentResponse =
    | PackingSlip        of PackingSlipOptions
    | ActivateMembership of MemberId
    | UpgradeMembership  of MemberId
    | EmailOwner         of MembershipType
    | CommissionPayment  of Agent

(*Functions*)

let publish payload = ()       // Stub
let getAgent productId = Agent // Stub

let respondTo (payment:PaymentFor) =

    match payment with
    | PhysicalProduct     (kind , packingSlip) -> 

        publish (CommissionPayment (getAgent packingSlip.ProductId))

        match kind with
        | Book  -> publish (DuplicateSlips    packingSlip)
        | Video -> publish (WithFirstAidVideo packingSlip)
        | Other -> publish packingSlip

    | Membership kind ->

        publish(EmailOwner kind)

        match kind with
        | MembershipType.Membership memberId -> publish(ActivateMembership memberId)
        | MembershipType.Upgrade    memberId -> publish(UpgradeMembership  memberId)    
\$\endgroup\$

1 Answer 1

5
\$\begingroup\$

The original requirement

How can you tame these wild business rules? How can you build a system that will be flexible enough to handle both the complexity and the need for change? And how can you do it without condemning yourself to years and years of mindless support?

seems, to me, to insinuate some sort of generic rules engine. This is, I believe, a red herring. The problem with rules engines, in my experience, is that the selling point is always that 'business users' can maintain them without involving developers.

This is a pipe dream, because if business rules can be arbitrarily complex, you're going to need a rules engine that can handle all of this complexity. In other words, you're going to need a programming language, and then it becomes too technical for business users.

This is called the inner platform effect and is, in my opinion, best avoided.

So you might as well use a programming language to implement the rules, and I see no reason you can't use a functional language like F#.

Here's a sketch of some of the rules. Like in the real world, some of the rules are vaguely defined, so I wasn't sure how to interpret them...

type Membership = Basic | Gold

type Good =
| PhysicalProduct of string
| Book of string
| Video of string
| Membership of Membership
| Upgrade

type Command =
| Slip of string * (Good list)
| Activate of Membership
| Upgrade
| PayAgent

These are simply some types, but given these types, you can implement various business rules, e.g.

// Good -> Command list
let slipForShipping = function
| PhysicalProduct name
| Book name
| Video name -> [Slip ("Shipping", [PhysicalProduct name])] 
| _ -> []

// Good -> Command list
let slipForRoyalty = function
| Book name -> [Slip ("Royalty", [Book name])]
| _ -> []

// Good -> Command list
let activate = function | Membership x -> [Activate x] | _ -> []

// Good -> Command list
let upgrade = function | Good.Upgrade -> [Upgrade] | _ -> []

You'll notice that all these rules have the same type, which means that you can collect all of them:

// ('a -> 'b list) list -> 'a -> 'b list
let handle handlers good = handlers |> List.collect (fun h -> h good)

// Good -> Command list    
let handleAll = handle [slipForShipping; slipForRoyalty; activate; upgrade]

Example:

> handleAll (Book "The Annotated Turing");;
val it : Command list =
  [Slip ("Shipping",[PhysicalProduct "The Annotated Turing"]);
   Slip ("Royalty",[Book "The Annotated Turing"])]

As far as I can tell, this would be fairly maintainable, because if you need to add a new rule, you'll have to add a new function, and add that function to handleAll.

\$\endgroup\$
4
  • 4
    \$\begingroup\$ Regarding the Inner Platform Effect, this blog post may also be of interest: mikehadlow.blogspot.co.uk/2012/05/… "Soon enough you’ll find that there’s little difference in the length of time it takes between changing a line of code and changing a line of configuration. Rather than a commonly available skill, such as coding C#, you find that your organisation relies on a very rare skill: understanding your rules engine or DSL." \$\endgroup\$ Dec 12, 2016 at 10:13
  • \$\begingroup\$ Feels like the Decorator Pattern... \$\endgroup\$ Dec 12, 2016 at 17:20
  • 1
    \$\begingroup\$ @ScottNimrod Not really; it's the Composite pattern... \$\endgroup\$ Dec 12, 2016 at 17:22
  • 2
    \$\begingroup\$ @ScottNimrod FWIW, I've now elaborated on the relationship to the Composite design pattern. \$\endgroup\$ May 17, 2018 at 6:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.