If you’ve ever worked in Fintech — payments, trading, banking — you know the drill:
a single transaction doesn’t live alone. One payment can trigger:
- fraud detection,
- accounting records,
- user notifications,
- reporting dashboards,
- regulatory audits
Now, how do we design this?
Do we let the PaymentProcessor
directly call each service?
Or do we go with a more scalable approach?
The Naïve Approach: What Juniors Usually Do
If you ask a junior engineer to design and connect a PaymentProcessor
with other services like Fraud Detection, Accounting, and Notifications, chances are they’ll write something like this:
@Service
public class FraudDetectionService {
public void check(PaymentEvent event) {
if (event.getAmount() > 10000) {
log.info("🚨 Fraud Alert! " + event);
}
}
}
@Service
public class AccountingService {
public void record(PaymentEvent event) {
log.info("📘 Accounting recorded: " + event);
}
}
@Service
public class NotificationService {
public void send(PaymentEvent event) {
log.info("📩 Email sent for " + event);
}
}
And designed PaymentProcessor
like that:
@Service
public class PaymentProcessor {
private final FraudDetectionService fraudDetectionService;
private final AccountingService accountingService;
private final NotificationService notificationService;
PaymentProcessor(FraudDetectionService fraudDetectionService,
AccountingService accountingService,
NotificationService notificationService){
this.fraudDetectionService = fraudDetectionService;
this.accountingService = accountingService;
this.notificationService = notificationService;
}
public void processPayment(PaymentEvent event) {
log.info("✅ Processing payment: " + event);
// Hard-coded calls
fraudDetectionService.check(event);
accountingService.record(event);
notificationService.send(event);
}
}
This looks simple and clear — and for a junior engineer, it feels natural:
👉 “I just call the services I need, job done!”
But here’s the problem:
- Every new requirement (e.g., add a
ReportingService
) means editingPaymentProcessor
. - Removing or replacing a service? Same story.
- The class becomes a God Object that knows too much. It works in small demos, but in real fintech systems (where requirements change fast and services multiply), this approach quickly becomes a maintenance nightmare.
✅ The Observer Pattern: A Senior-Level Solution
Imagine a senior engineer at a Fintech company.
He’s been around long enough to see how regulations, compliance, and fraud rules change every few months.
One day, a product manager walks up to him:
“Hey, we need to add a new RegulatoryReportingService to capture every payment above $5,000. Can you add that quickly?”
A junior engineer might dive into the PaymentProcessor
class, add another direct call, and push the change. Done. ✅
But the senior engineer pauses.
He knows this is just the beginning. Tomorrow, there might be:
- A new AML (Anti-Money Laundering) check.
- A new Data Analytics Service.
- A new AI-based Fraud Detector.
If every new requirement forces him to edit
PaymentProcessor
, the system will soon collapse under its own weight.
So instead, he thinks differently:
👉 “What if I let PaymentProcessor focus only on payments… and let other services subscribe to events as they need?”
That thought leads him to the Observer Pattern
.
Now, new services can be plugged in or removed without ever touching the payment logic.
Step 1— The Observer Interface
public interface PaymentObserver {
void update(PaymentEvent event);
}
Step 2 — The Services (Observers)
@Service
public class FraudDetectionService implements PaymentObserver {
@Override
public void update(PaymentEvent event) {
if (event.getAmount() > 10000) {
log.info("🚨 Fraud Alert! " + event);
}
}
}
@Service
public class AccountingService implements PaymentObserver {
@Override
public void update(PaymentEvent event) {
log.info("📘 Accounting recorded: " + event);
}
}
@Service
public class NotificationService implements PaymentObserver {
@Override
public void update(PaymentEvent event) {
log.info("📩 Email sent for " + event);
}
}
Step 3 — The Subject (Observable)
Spring checks the context: “Do I have beans implementing PaymentObserver
?”
✅ Yes → It injects all of them into the list.
We don’t need to add each service dependency in constructor because all services
implement PaymentObserver
interface.
@Service
public class PaymentProcessor {
private final List<PaymentObserver> observers;
public PaymentProcessor(Liste<PaymentObserver> observers) {
this.observers = observers
}
public void processPayment(PaymentEvent event) {
log.info("✅ Processing payment: " + event);
notifyObservers(event);
}
private void notifyObservers(PaymentEvent event) {
for (PaymentObserver observer : observers) {
observer.update(event);
}
}
}
⚡ Why This Matters in Fintech
- Scalability → Add/remove services without touching
PaymentProcesso
r. - Extensibility → New compliance rules? Just plug in a new observer.
- Loose coupling → Services don’t need to know about each other.
- Resilience → Failure in one observer doesn’t stop others.
In real-world microservices
, frameworks like Spring events
and Kafka
implement this same Observer
idea but at scale:
-
PayementEvent
published to a Kafka topic. - Multiple services consume it independently.
Think Better!!
Top comments (0)