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) 
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Dec 11, 2016 at 15:56
\$\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.

answered Dec 11, 2016 at 18:50
\$\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\$ Commented Dec 12, 2016 at 10:13
  • \$\begingroup\$ Feels like the Decorator Pattern... \$\endgroup\$ Commented Dec 12, 2016 at 17:20
  • 1
    \$\begingroup\$ @ScottNimrod Not really; it's the Composite pattern... \$\endgroup\$ Commented Dec 12, 2016 at 17:22
  • 2
    \$\begingroup\$ @ScottNimrod FWIW, I've now elaborated on the relationship to the Composite design pattern. \$\endgroup\$ Commented May 17, 2018 at 6:56

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.