Real World Example

Swapping Payment and SMS Providers

Indian payment and notification providers change often (Razorpay vs PayU vs Cashfree, MSG91 vs Twilio for SMS). Apps like Swiggy keep business code independent of the provider by depending on interfaces and letting Spring inject the right implementation.

CheckoutService --needs--> PaymentGateway (interface)
                                 ^                ^
                                 |                |
                       RazorpayGateway      FakeGateway
                       (prod profile)       (local / tests)

ShopEase Checkout Wiring

public interface SmsSender {
    void send(String phone, String text);
}

@Component
@Profile("prod")                                    // only in production (profiles: see Configuration)
class Msg91SmsSender implements SmsSender {
    public void send(String phone, String text) { /* call MSG91 HTTP API */ }
}

@Component
@Profile("!prod")                                   // everywhere else: just log it
class LoggingSmsSender implements SmsSender {
    private static final Logger log = LoggerFactory.getLogger(LoggingSmsSender.class);
    public void send(String phone, String text) { log.info("SMS to {}: {}", phone, text); }
}

@Service
public class CheckoutService {
    private final PaymentGateway gateway;
    private final SmsSender sms;

    public CheckoutService(PaymentGateway gateway, SmsSender sms) {
        this.gateway = gateway;
        this.sms = sms;
    }

    public void checkout(long orderId, int amount, String phone) {
        gateway.charge(orderId, amount);
        sms.send(phone, "ShopEase: order #" + orderId + " confirmed. Amount Rs." + amount);
    }
}

Example

Running locally (no prod profile) uses the logging sender:

./mvnw spring-boot:run

Example output

... INFO  c.s.notify.LoggingSmsSender : SMS to 98XXXXXX10: ShopEase: order #101 confirmed. Amount Rs.2998

Key Takeaways

  • Depend on interfaces, inject implementations through the constructor.
  • Use stereotype annotations for your classes and @Bean for library classes.
  • Keep singleton beans stateless.
  • Changing a provider means adding one class, not editing every service.