Maybe you heard about this weird thing called monad. Maybe you even tried to read more about the topic but you never use it in practice. Sometimes real usage examples can help to understand when it might be worth to look into monads and then try to pick a monad for our problem.
I am going to show you what my problem was while working on a payment handling system. I will leave the questions like what is the monad and what are examples of monads to the end of the article because there are already a lot of resources covering that.
Recurring payment system
A while ago I was working on integration with Braintree Payments and my website, so companies that are doing optimal test suite parallelisation in ruby with my tool can switch to paid monthly plan. Basically, the case was to have subscription-based billing.
I needed to handle a few things on the payment form:
- form has to be validated
- customer has to be created or his data should be updated in payment system (Braintree)
- subscription plan has to be created or updated with proper price in payment system
If we put that in pseudo code it would look like this:
As you can see there are a few cases when something can go wrong and we will have to show errors for the particular step that failed. You can imagine how
if structure builds up when you have more cases to handle.
Here you can see my real code example. It is just
create action from Ruby on Rails controller. Part of the responsibilities like
update for customer or subscription records in Braintree payment system was extracted to service objects called with suffix
Upsert. Those services are responsible for the operation of
update when the record already exists.
The flow in
billings controller is simple. When something is ok then continue. If something is wrong then stop and return an error. There is monad for this called
There is a great gem called dry-monads that have a few common monads for Ruby. We are going to use it.
Either monad has two type constructors:
is for everything went right cases and the
Left is used when something has gone wrong.
We are going to do one more thing. Extract the logic to separate service
Billing::Payment and keep controller more readable.
And here is the logic for service with Either monad.
When we run
call method on the service
Billing::Payment we will get
Left object as a result. We can call on the result the
success? method to check whether all was right or not. By calling method
value we get what was passed to
Left constructor. In the case of
Right the value will be
true because we set that. For
Left the value is the error for a step that failed. Simple as that. You can easily extend this by binding more cases if you need that.
To understand monads we need to first ask the question why do we even need monads. There is a great simple explanation on StackOverflow based on a problem with dividing by zero and applying a function on the result.
When you will grasp the idea behind monads then it is worth to check other related concepts like functors and applicatives. There is a great article about functors, applicatives, and monads in pictures.
If you want to learn more about monads in Ruby examples then definitely check presentation Refactoring Ruby with Monads by Tom Stuart.
Do not forget the dry-monads gem has other monads examples like
Try monad. Check the dry-monads docs!