Single responsibility doesn't mean single action


Last week, I got into a friendly argument with a friend of mine about the SOLID principles. He mentioned that these ideas aren’t really practical for real-world code — that they’re more like something you’d find in a textbook than in an actual project. However, I felt that there are many misconceptions about these principles, and they can actually work in real projects if you understand them correctly.
Single responsibility does not mean single action
So, let’s imagine a scenario and build the rest of this article around it.
Imagine an API that lets your customers withdraw money from your app to their bank accounts. There are many steps that you need to take under the withdrawal flow, like KYC checks, limitations, or some restriction checks. Now, map this API to a real-world workflow, like a facility with multiple departments, where each department has its own responsibilities. Imagine there is a law department.
Of course, the law department isn’t just for justifying business contracts; they help the company in many ways that require someone who knows the LAW very well. They may have multiple teams for different types of work, and each team has different duties. But if you think about it like software design, the department isn’t responsible for just one action; they handle multiple related jobs within the same area.
If a secretary is acting as an orchestrator, they manage the workflow step by step and coordinate with multiple departments—Law, Finance, IT, or others. The workflow has a specific way it needs to be done, but is the secretary responsible for what happens inside the Finance department? Of course not!
The secretary is just an orchestrator, managing the process based on the job at hand.
A class/module/function should have one reason to change.
But what does that mean? Does it actually happen in practice? Well, yes and no.
If the business decides to change some logic—like the order of KYC checks, restriction checks, or other withdrawal limits—does that mean we’re modifying the withdraw API for multiple reasons? No. Because we’re domain-specific, the withdrawal flow depends on your domain logic, which might need to add or remove certain checks. So, you still have just one reason to change the withdrawal method or class.
How does SRP work in practice?
In the example below, the withdraw method is also handling exception logic from other modules. Now, what if the business wants clearer, more specific error messages for certain exceptions? You’d have to manage all those exceptions in the same place — and that’s exactly what we want to avoid.
def withdraw(user_id, amount):
# KYC check
if not user_kyc_verified(user_id):
raise Exception("KYC not completed")
# Restriction check
if has_withdrawal_restrictions(user_id):
raise Exception("Withdrawal restricted")
# Process withdrawal
wallet = get_wallet(user_id)
if wallet.balance < amount:
raise Exception("Insufficient funds")
wallet.balance -= amount
send_money_to_bank(wallet, amount)
But how can we have a clean version?
In my opinion, maybe the below code adds more abstraction or complexity, but this is clearer somehow :))
class KYCService:
def verify(self, user_id):
if not user_kyc_verified(user_id):
raise Exception("KYC not completed")
def user_kyc_verified(user_id):
...
class WithdrawalService(FinanceService):
def process(self, user_id, amount):
wallet = get_wallet(user_id)
wallet.balance -= amount
send_money_to_bank(wallet, amount)
def check(self, user_id, amount):
wallet = get_wallet(user_id)
if wallet.balance < amount:
raise Exception("Insufficient funds")
if has_withdrawal_restrictions(user_id):
raise Exception("Withdrawal restricted")
kyc_service = KYCService()
withdrawal_service = WithdrawalService()
def withdraw_api(user_id, amount):
kyc_service.verify(user_id)
withdrawal_service.check(user_id, amount)
withdrawal_service.process(user_id, amount)
If you found this article helpful or have suggestions to make it better, please let me know! Your feedback really helps me improve future posts. Thanks!
Subscribe to my newsletter
Read articles from May T directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

May T
May T
This is May T or Mehdi or Mahdi or however you would like to call it.