Extracting side effect from Service Objects
Introduction In the first chapter of "Functional Programming in Scala", it is discussed how to extract side effect from your logic and why it is good. In the example, they refactor a code of buying a coffee. The original code was doing two things. One is to make a Coffee object and another is to ...
Introduction
In the first chapter of "Functional Programming in Scala", it is discussed how to extract side effect from your logic and why it is good. In the example, they refactor a code of buying a coffee. The original code was doing two things. One is to make a Coffee object and another is to charge to a credit card.
class Cafe { def buyCoffee( cc: CreditCard, p: Payments ): Coffee = { val cup = new Coffee() p.charge(cc, cup.price) cup } }
In this code p.charge() has a side effect. It will send a payment request to an external service. They said it is not good and refactored this code so that it does not have any side effect like the following.
class Cafe { def buyCoffee( cc: CreditCard ): (Coffee, Charge) = { val cup = new Coffee() val charge = Charge(cc, cup.price) (cup, charge) } }
The new code just creates two object cup and charge and return them to the caller. Absolutely no side effect. Purely functional. It vanishes all side effect from our code. Wow. Great. Awesome.
So, can you tell me, what is great indeed?
Why side effect matters
In the above example it seems that it just kicked out the side effect to the caller. buyCoffee method does not have a side effect anymore, it is true. But we still have it somewhere. It just delayed the side effect. So there isn't any big difference, is it? So what is great? Actually it is not very easy to understand it if you only look at this one use case because the advantages of this code are reusability, composability and testablity.
In the example above, the buyCoffee method just create two objects. So it is easy to reuse this code for other purposes. For example, when you want to show users the price before they really buy it, we can show them the charged amount. And it is composable because we can combine two charges into one charge and merge two external payment requests to one request. And it is easier to test than the original code because original one has side effect and depends on the external services, but still the main business logic which needs a test is in the buyCoffee method.
So the key idea is to separate the Planning part and Execution part. In the above example, buyCoffee is a planning about what buyer will get and how much he will pay. And the p.charge() method is the execution part. If we divide like this way, all the complexity related to business logic is in the Planning part and all the complexity between external resouce is in the Execution part. So two kinds of complexities are not mixed together. And execution part is rather simple because all complicated business logic is already done in the Planning part. Execution just executes the plan.
Example: ActiveRecord
ActiveRecord is one typical example of this design. In the age of Rails 1, we only have simple API for query like this:
Model.find( :all, :conditions => ["id < 3000 and deadline < ?", Date.today.next_month], :order => "id desc")
It immediately returns the list of found models. So it is not reusable nor composable.
But now we can query like this
Model.where("id < 3000").where("deadline < ?", Date.today.next_month).order("id desc")
What where method returns is a kind of query object, which is a plan of query. The real query to the database (execution) is delayed until we call to_a. And as you know, it is highly reusable and composable.
In this example, it is easy to see that all complexity of building query is decoupled from execution of query.
Application to Service Objects
So now let's move our eyes on Service Objects. This is the purpose of this article. I have an idea (actually just an idea with no practice