How to Separate Business Logic and Services
Have you ever been digging through a project and found a service you wanted to utilize for your feature, only to discover that the method you need in that service has one of those “gotchas” that means you can’t use it the way you intended? It’s certainly happened to me more than a few times, but rest assured, there is a nice solution to this problem. Let’s take a look at the command pattern and why it can be powerful when writing your applications, especially when you follow the service layers design pattern.
For the purpose of this example let’s pretend that I’m currently tasked with writing a new tool for our support team. The tool will allow our support reps to modify the plan a users subscription is on, so I’ll need to design an API for the tool to access.
I start my search for any relevant existing code, stumbling upon a class called SubscriptionService. Great, this should have all of the methods I need to work with subscriptions. I discover the following method signature:
A little scary simply based on the number of parameters, and even more so because of the strange and highly feature specific parameters we see here. If we look beyond that, there’s another “gotcha”:
What is this rule? Why is it in a service when that logic seems very specific to one use of this method? This code is certainly not what I was hoping to find so in a mildly annoyed state I set out to find a solution rather than compound the problem by sticking with this method and creating another pile of code that will be difficult to maintain.
At TextNow one of our main REST API’s leverages a service layer pattern to organize our code. Essentially the structure of the application is as follows:
Where this structure differs from other projects i’ve worked on is the UseCases directory. At TextNow, this is our implementation of the command pattern.
Basically a command (or “use case” as we call them) is a class with a simple interface built with the intent to be home to those business rules or feature specific pieces of code that just don’t belong anywhere else.
The interface for a use case is simple — all that’s needed is to create what is called a parameter bag (essentially a fancy wrapper for a dictionary of key values) to pass parameters into the use case, then instantiate the use case and run it. Most times this will look something like:
In this example we’re using our implementation of the command pattern, however we mimic the functionality of active interactions, a wonderful Ruby gem that implements the command pattern.
Great, we’ve got a place to store that pesky non-reusable code that captures our business logic and we can now keep our services generic and reusable! So what do the guts actually look like? Let’s take a look at the contents of the use case referenced above called UpdateSubscription that will serve as our newer cleaner approach to the scarier code referenced above.
Put simply, validateInputs does just that. We verify that we were given all the information we need to perform our task as well as verify that we’re allowed to do what is being asked. Sounds like a great place for a business rule like the example we looked at earlier.
Next we have the execute method. Execute is where we do the do, if you will. In this situation updating a subscription requires us to modify the subscription within our billing platform (an external service to this application), as well as make a few database updates to track the user’s subscription. The newer version in the use case will end up looking something like:
Now that looks much less scary to me! By taking the business logic out of our service layer and finding it a shiny new home in the use cases we can see that our interaction with the services is much cleaner. They can act in a much more utilitarian fashion and allow for much better reuse down the road!
One of the major benefits of the command pattern is the ability to reuse them. They have an agnostic entry point so we can utilize these from any entry point we want, such as a command line module, a REST API, or queued job from our message broker. I’ve personally found that utilizing a pattern like this has helped to clean up the code I write and make it far more manageable and I hope that you the reader do as well.
If you want to help solve “gotchas” with us at TextNow, please have a look at our job openings and apply!