High-level modules should not depend on low-level modules. Both should depend on abstractions.
BookingService directly creates a PostgresSeatInventory and StripePaymentGateway. Switching databases or payment providers requires modifying BookingService.
BookingService depends on SeatInventoryPort, PaymentPort, NotificationPort — interfaces defined by the high-level module. Low-level implementations (Postgres, Stripe, Twilio) implement these ports.
// ─── EXAMPLE 1 ──────────────────────────────────────────────────────────────
// WHAT WE ARE IMPLEMENTING:
// A user manager service decouples business logic from specific database
// engine classes.
//
// WHERE THE PRINCIPLE FITS IN:
// High-level UserService depends on the Database abstraction interface rather
// than concrete MySQLDatabase or MongoDBDatabase implementations, enabling
// seamless driver swaps.
// ────────────────────────────────────────────────────────────────────────────
// --- High-level module defines the PORTS (abstractions) ---
interface SeatInventoryPort {
boolean reserve(String seatId, String userId);
void release(String seatId);
}
interface PaymentPort {
boolean charge(String userId, double amount);
void refund(String transactionId);
}
interface NotificationPort {
void send(String userId, String message);
}
// --- High-level business logic depends ONLY on abstractions ---
class BookingService {
private final SeatInventoryPort inventory;
private final PaymentPort payment;
private final NotificationPort notif;
public BookingService(SeatInventoryPort inv, PaymentPort pay, NotificationPort notif) {
this.inventory = inv;
this.payment = pay;
this.notif = notif;
}
public boolean bookTicket(String userId, String seatId, double price) {
if (!inventory.reserve(seatId, userId)) {
System.out.println(" [Booking] Reservation failed: " + seatId);
return false;
}
boolean paid = payment.charge(userId, price);
if (!paid) {
inventory.release(seatId);
System.out.println(" [Booking] Payment failed, released seat");
return false;
}
notif.send(userId, "Booking confirmed for seat " + seatId);
System.out.println(" [Booking] Success: " + seatId);
return true;
}
}
// --- Low-level implementations (injected, not instantiated) ---
class PostgresSeatInventory implements SeatInventoryPort {
public boolean reserve(String seatId, String userId) {
System.out.println(" [Postgres] Reserv seat=" + seatId + " user=" + userId);
return true;
}
public void release(String seatId) {
System.out.println(" [Postgres] Released seat=" + seatId);
}
}
class StripePaymentGateway implements PaymentPort {
public boolean charge(String userId, double amount) {
System.out.println(" [Stripe] Charged " + amount + " for " + userId);
return true;
}
public void refund(String transactionId) {
System.out.println(" [Stripe] Refunded " + transactionId);
}
}
class TwilioNotificationService implements NotificationPort {
public void send(String userId, String message) {
System.out.println(" [Twilio] SMS to " + userId + ": " + message);
}
}
// --- Composition root (wire dependencies) ---
public class Main {
public static void main(String[] args) {
SeatInventoryPort inv = new PostgresSeatInventory();
PaymentPort pay = new StripePaymentGateway();
NotificationPort notif = new TwilioNotificationService();
BookingService booking = new BookingService(inv, pay, notif);
booking.bookTicket("user-42", "A12", 250.0);
}
}