Master Java's modular architecture through JPMS design patterns, dependency management strategies, and enterprise-scale application organization
By the end of this lesson, you will:
<aside> π¦
JPMS Philosophy
JPMS treats applications like LEGO construction sets - each module is a well-defined building block with clear interfaces (what it provides) and dependencies (what it needs). This creates reliable, maintainable, and secure software architectures.
</aside>
Java Module Evolution - From Classpath Chaos to Modular Order
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π° PRE-JAVA 9 (CLASSPATH) ποΈ JAVA 9+ (MODULE SYSTEM)
βββββββββββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββββββ
β π All JARs on classpath β β π¦ Explicit module dependencies β
β β No encapsulation β β β
Strong encapsulation β
β β JAR hell & conflicts β β β
Reliable configuration β
β β Runtime discovery of missing β β β
Compile-time dependency check β
β dependencies β β β
β β All public classes accessible β β β
Only exported packages visible β
β β Monolithic runtime β β β
Custom runtime images β
β β β β
β java -cp app.jar:lib1.jar:lib2.jar β β java --module-path mods β
β MyApplication β β -m myapp/com.example.Main β
βββββββββββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββββββ
π§± MODULE ARCHITECTURE LAYERS
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β APPLICATION LAYER β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β UI Module β β Service β β Data Access β β Utility β β
β β β β Module β β Module β β Module β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β β β β β
β βΌ βΌ βΌ βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β API LAYER β β
β β (Exported Interfaces & Public APIs) β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β PLATFORM LAYER β β
β β java.base β java.logging β java.sql β [java.net](<http://java.net>).http β β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π ENCAPSULATION LEVELS
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β VISIBILITY SCOPE β ACCESSIBLE FROM β USE CASE β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β public + exported β Other modules β Public API β
β public + not exported β Same module only β Internal API β
β package-private β Same package only β Implementation β
β private β Same class only β Internal logic β
β opened (reflection) β Frameworks via reflectionβ Framework integβ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
<aside> π―
Module Declaration Strategy
Think of module-info.java as a contract document - it declares what your module provides (exports), what it needs (requires), and how it collaborates with other modules (services).
</aside>
// ========================================================================================
// πͺ E-COMMERCE PLATFORM MODULE STRUCTURE
// ========================================================================================
// π¦ Module 1: Core API Module
// File: ecommerce.api/src/[module-info.java](<http://module-info.java>)
module ecommerce.api {
// Export public APIs to all consumers
exports com.ecommerce.api.product;
exports com.ecommerce.api.customer;
exports com.ecommerce.api.order;
exports com.ecommerce.api.payment;
// Qualified exports - only to specific modules
exports com.ecommerce.api.internal to
ecommerce.service,
[ecommerce.data](<http://ecommerce.data>);
// Service interfaces that can be implemented by providers
uses com.ecommerce.api.payment.PaymentProcessor;
uses com.ecommerce.api.notification.NotificationService;
}
// π― Core API Interfaces
// File: ecommerce.api/src/com/ecommerce/api/product/[Product.java](<http://Product.java>)
package com.ecommerce.api.product;
import java.math.BigDecimal;
import java.util.List;
public interface Product {
String getId();
String getName();
String getDescription();
BigDecimal getPrice();
String getCategory();
List<String> getImages();
boolean isAvailable();
}
// File: ecommerce.api/src/com/ecommerce/api/product/[ProductService.java](<http://ProductService.java>)
package com.ecommerce.api.product;
import java.util.List;
import java.util.Optional;
public interface ProductService {
List<Product> findAll();
List<Product> findByCategory(String category);
Optional<Product> findById(String id);
List<Product> search(String query);
Product save(Product product);
void deleteById(String id);
}
// File: ecommerce.api/src/com/ecommerce/api/customer/[Customer.java](<http://Customer.java>)
package com.ecommerce.api.customer;
import java.time.LocalDateTime;
import java.util.List;
public interface Customer {
String getId();
String getEmail();
String getFirstName();
String getLastName();
List<Address> getAddresses();
LocalDateTime getRegistrationDate();
CustomerStatus getStatus();
}
public enum CustomerStatus {
ACTIVE, INACTIVE, SUSPENDED, VIP
}
// File: ecommerce.api/src/com/ecommerce/api/customer/[Address.java](<http://Address.java>)
package com.ecommerce.api.customer;
public interface Address {
String getStreet();
String getCity();
String getState();
String getZipCode();
String getCountry();
AddressType getType();
}
public enum AddressType {
BILLING, SHIPPING, BOTH
}
// File: ecommerce.api/src/com/ecommerce/api/order/[Order.java](<http://Order.java>)
package com.ecommerce.api.order;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
public interface Order {
String getId();
String getCustomerId();
List<OrderItem> getItems();
BigDecimal getTotalAmount();
OrderStatus getStatus();
LocalDateTime getCreatedDate();
LocalDateTime getUpdatedDate();
String getShippingAddress();
String getBillingAddress();
}
public enum OrderStatus {
PENDING, CONFIRMED, PROCESSING, SHIPPED, DELIVERED, CANCELLED, REFUNDED
}
// File: ecommerce.api/src/com/ecommerce/api/order/[OrderItem.java](<http://OrderItem.java>)
package com.ecommerce.api.order;
import java.math.BigDecimal;
public interface OrderItem {
String getProductId();
int getQuantity();
BigDecimal getUnitPrice();
BigDecimal getTotalPrice();
}
// File: ecommerce.api/src/com/ecommerce/api/payment/[PaymentProcessor.java](<http://PaymentProcessor.java>)
package com.ecommerce.api.payment;
import java.math.BigDecimal;
public interface PaymentProcessor {
String getProviderName();
PaymentResult processPayment(PaymentRequest request);
PaymentResult refundPayment(String transactionId, BigDecimal amount);
boolean supports(PaymentMethod method);
}
public class PaymentRequest {
private final String customerId;
private final BigDecimal amount;
private final PaymentMethod method;
private final String currency;
// Constructor, getters, builder pattern
public PaymentRequest(String customerId, BigDecimal amount,
PaymentMethod method, String currency) {
this.customerId = customerId;
this.amount = amount;
this.method = method;
this.currency = currency;
}
// Getters
public String getCustomerId() { return customerId; }
public BigDecimal getAmount() { return amount; }
public PaymentMethod getMethod() { return method; }
public String getCurrency() { return currency; }
}
public class PaymentResult {
private final boolean success;
private final String transactionId;
private final String message;
private final PaymentStatus status;
public PaymentResult(boolean success, String transactionId,
String message, PaymentStatus status) {
this.success = success;
this.transactionId = transactionId;
this.message = message;
this.status = status;
}
// Getters
public boolean isSuccess() { return success; }
public String getTransactionId() { return transactionId; }
public String getMessage() { return message; }
public PaymentStatus getStatus() { return status; }
}
public enum PaymentMethod {
CREDIT_CARD, DEBIT_CARD, PAYPAL, BANK_TRANSFER, CRYPTO
}
public enum PaymentStatus {
PENDING, APPROVED, DECLINED, ERROR, REFUNDED
}
// ========================================================================================
// π¦ Module 2: Service Implementation Module
// ========================================================================================
// File: ecommerce.service/src/[module-info.java](<http://module-info.java>)
module ecommerce.service {
// Require API module
requires ecommerce.api;
// Require platform modules
requires java.logging;
requires transitive java.sql; // Transitive - consumers get java.sql too
// Export service implementations
exports com.ecommerce.service.impl to ecommerce.web;
// Open packages for dependency injection frameworks
opens com.ecommerce.service.impl to spring.core, spring.context;
// Provide service implementations
provides com.ecommerce.api.product.ProductService
with com.ecommerce.service.impl.ProductServiceImpl;
}
// ποΈ Service Implementation
// File: ecommerce.service/src/com/ecommerce/service/impl/[ProductServiceImpl.java](<http://ProductServiceImpl.java>)
package com.ecommerce.service.impl;
import com.ecommerce.api.product.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import [java.util.stream](<http://java.util.stream>).Collectors;
public class ProductServiceImpl implements ProductService {
private static final Logger logger = Logger.getLogger(ProductServiceImpl.class.getName());
// In-memory storage for demo (in real app, this would be injected)
private final Map<String, Product> products = new ConcurrentHashMap<>();
public ProductServiceImpl() {
initializeProducts();
[logger.info](<http://logger.info>)("ProductServiceImpl initialized with " + products.size() + " products");
}
@Override
public List<Product> findAll() {
[logger.info](<http://logger.info>)("Finding all products");
return new ArrayList<>(products.values());
}
@Override
public List<Product> findByCategory(String category) {
[logger.info](<http://logger.info>)("Finding products by category: " + category);
return products.values().stream()
.filter(product -> product.getCategory().equalsIgnoreCase(category))
.collect(Collectors.toList());
}
@Override
public Optional<Product> findById(String id) {
[logger.info](<http://logger.info>)("Finding product by ID: " + id);
return Optional.ofNullable(products.get(id));
}
@Override
public List<Product> search(String query) {
[logger.info](<http://logger.info>)("Searching products with query: " + query);
String lowerQuery = query.toLowerCase();
return products.values().stream()
.filter(product ->
product.getName().toLowerCase().contains(lowerQuery) ||
product.getDescription().toLowerCase().contains(lowerQuery))
.collect(Collectors.toList());
}
@Override
public Product save(Product product) {
[logger.info](<http://logger.info>)("Saving product: " + product.getId());
products.put(product.getId(), product);
return product;
}
@Override
public void deleteById(String id) {
[logger.info](<http://logger.info>)("Deleting product: " + id);
products.remove(id);
}
private void initializeProducts() {
// Sample products for demonstration
List<ProductImpl> sampleProducts = Arrays.asList(
new ProductImpl("1", "Laptop Pro", "High-performance laptop",
new java.math.BigDecimal("1299.99"), "Electronics",
Arrays.asList("laptop1.jpg", "laptop2.jpg"), true),
new ProductImpl("2", "Wireless Headphones", "Noise-cancelling headphones",
new java.math.BigDecimal("249.99"), "Electronics",
Arrays.asList("headphones1.jpg"), true),
new ProductImpl("3", "Running Shoes", "Comfortable running shoes",
new java.math.BigDecimal("89.99"), "Sports",
Arrays.asList("shoes1.jpg", "shoes2.jpg", "shoes3.jpg"), true),
new ProductImpl("4", "Coffee Maker", "Automatic coffee maker",
new java.math.BigDecimal("79.99"), "Home",
Arrays.asList("coffee1.jpg"), true),
new ProductImpl("5", "Programming Book", "Advanced Java Programming",
new java.math.BigDecimal("49.99"), "Books",
Arrays.asList("book1.jpg"), true)
);
sampleProducts.forEach(product -> products.put(product.getId(), product));
}
// Internal implementation class
private static class ProductImpl implements Product {
private final String id;
private final String name;
private final String description;
private final java.math.BigDecimal price;
private final String category;
private final List<String> images;
private final boolean available;
public ProductImpl(String id, String name, String description,
java.math.BigDecimal price, String category,
List<String> images, boolean available) {
[this.id](<http://this.id>) = id;
[this.name](<http://this.name>) = name;
this.description = description;
this.price = price;
this.category = category;
this.images = new ArrayList<>(images);
this.available = available;
}
// Implement all Product interface methods
@Override public String getId() { return id; }
@Override public String getName() { return name; }
@Override public String getDescription() { return description; }
@Override public java.math.BigDecimal getPrice() { return price; }
@Override public String getCategory() { return category; }
@Override public List<String> getImages() { return new ArrayList<>(images); }
@Override public boolean isAvailable() { return available; }
@Override
public String toString() {
return String.format("Product[id=%s, name=%s, category=%s, price=$%.2f]",
id, name, category, price);
}
}
}
// ========================================================================================
// π¦ Module 3: Payment Provider Module
// ========================================================================================
// File: ecommerce.payment.stripe/src/[module-info.java](<http://module-info.java>)
module ecommerce.payment.stripe {
requires ecommerce.api;
requires [java.net](<http://java.net>).http;
requires java.logging;
// Provide payment processor implementation
provides com.ecommerce.api.payment.PaymentProcessor
with com.ecommerce.payment.stripe.StripePaymentProcessor;
}
// π³ Stripe Payment Implementation
// File: ecommerce.payment.stripe/src/com/ecommerce/payment/stripe/[StripePaymentProcessor.java](<http://StripePaymentProcessor.java>)
package com.ecommerce.payment.stripe;
import com.ecommerce.api.payment.*;
import java.math.BigDecimal;
import java.util.logging.Logger;
import java.util.UUID;
public class StripePaymentProcessor implements PaymentProcessor {
private static final Logger logger = Logger.getLogger(StripePaymentProcessor.class.getName());
@Override
public String getProviderName() {
return "Stripe Payment Processor";
}
@Override
public PaymentResult processPayment(PaymentRequest request) {
[logger.info](<http://logger.info>)("Processing Stripe payment for customer: " + request.getCustomerId() +
" amount: " + request.getAmount());
try {
// Simulate Stripe API call
Thread.sleep(500); // Simulate network delay
// Simulate different outcomes based on amount
if (request.getAmount().compareTo(new BigDecimal("10000")) > 0) {
// Large amounts might be declined
return new PaymentResult(false, null, "Amount too large", PaymentStatus.DECLINED);
}
if (request.getAmount().compareTo([BigDecimal.ZERO](<http://BigDecimal.ZERO>)) <= 0) {
return new PaymentResult(false, null, "Invalid amount", PaymentStatus.ERROR);
}
// Successful payment
String transactionId = "stripe_" + UUID.randomUUID().toString().substring(0, 8);
return new PaymentResult(true, transactionId, "Payment successful", PaymentStatus.APPROVED);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return new PaymentResult(false, null, "Payment interrupted", PaymentStatus.ERROR);
} catch (Exception e) {
logger.severe("Stripe payment processing error: " + e.getMessage());
return new PaymentResult(false, null, "Processing error: " + e.getMessage(), PaymentStatus.ERROR);
}
}
@Override
public PaymentResult refundPayment(String transactionId, BigDecimal amount) {
[logger.info](<http://logger.info>)("Processing Stripe refund for transaction: " + transactionId +
" amount: " + amount);
try {
Thread.sleep(300); // Simulate API call
if (transactionId == null || !transactionId.startsWith("stripe_")) {
return new PaymentResult(false, null, "Invalid transaction ID", PaymentStatus.ERROR);
}
String refundId = "refund_" + UUID.randomUUID().toString().substring(0, 8);
return new PaymentResult(true, refundId, "Refund successful", PaymentStatus.REFUNDED);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return new PaymentResult(false, null, "Refund interrupted", PaymentStatus.ERROR);
} catch (Exception e) {
logger.severe("Stripe refund processing error: " + e.getMessage());
return new PaymentResult(false, null, "Refund error: " + e.getMessage(), PaymentStatus.ERROR);
}
}
@Override
public boolean supports(PaymentMethod method) {
// Stripe supports most payment methods except crypto
return method != PaymentMethod.CRYPTO;
}
}
// ========================================================================================
// π¦ Module 4: Alternative Payment Provider
// ========================================================================================
// File: ecommerce.payment.paypal/src/[module-info.java](<http://module-info.java>)
module ecommerce.payment.paypal {
requires ecommerce.api;
requires java.logging;
provides com.ecommerce.api.payment.PaymentProcessor
with com.ecommerce.payment.paypal.PayPalPaymentProcessor;
}
// π° PayPal Payment Implementation
// File: ecommerce.payment.paypal/src/com/ecommerce/payment/paypal/[PayPalPaymentProcessor.java](<http://PayPalPaymentProcessor.java>)
package com.ecommerce.payment.paypal;
import com.ecommerce.api.payment.*;
import java.math.BigDecimal;
import java.util.logging.Logger;
import java.util.UUID;
public class PayPalPaymentProcessor implements PaymentProcessor {
private static final Logger logger = Logger.getLogger(PayPalPaymentProcessor.class.getName());
@Override
public String getProviderName() {
return "PayPal Payment Processor";
}
@Override
public PaymentResult processPayment(PaymentRequest request) {
[logger.info](<http://logger.info>)("Processing PayPal payment for customer: " + request.getCustomerId() +
" amount: " + request.getAmount());
try {
Thread.sleep(800); // PayPal might be slower
// PayPal has different business rules
if (request.getMethod() != PaymentMethod.PAYPAL &&
request.getMethod() != [PaymentMethod.BANK](<http://PaymentMethod.BANK>)_TRANSFER) {
return new PaymentResult(false, null, "PayPal doesn't support this method",
PaymentStatus.DECLINED);
}
if (request.getAmount().compareTo(new BigDecimal("5000")) > 0) {
return new PaymentResult(false, null, "Amount exceeds PayPal limit",
PaymentStatus.DECLINED);
}
String transactionId = "paypal_" + UUID.randomUUID().toString().substring(0, 10);
return new PaymentResult(true, transactionId, "PayPal payment successful",
PaymentStatus.APPROVED);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return new PaymentResult(false, null, "Payment interrupted", PaymentStatus.ERROR);
} catch (Exception e) {
logger.severe("PayPal payment processing error: " + e.getMessage());
return new PaymentResult(false, null, "Processing error: " + e.getMessage(),
PaymentStatus.ERROR);
}
}
@Override
public PaymentResult refundPayment(String transactionId, BigDecimal amount) {
[logger.info](<http://logger.info>)("Processing PayPal refund for transaction: " + transactionId);
try {
Thread.sleep(600);
if (transactionId == null || !transactionId.startsWith("paypal_")) {
return new PaymentResult(false, null, "Invalid PayPal transaction ID",
PaymentStatus.ERROR);
}
String refundId = "paypal_refund_" + UUID.randomUUID().toString().substring(0, 8);
return new PaymentResult(true, refundId, "PayPal refund successful",
PaymentStatus.REFUNDED);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return new PaymentResult(false, null, "Refund interrupted", PaymentStatus.ERROR);
} catch (Exception e) {
logger.severe("PayPal refund processing error: " + e.getMessage());
return new PaymentResult(false, null, "Refund error: " + e.getMessage(),
PaymentStatus.ERROR);
}
}
@Override
public boolean supports(PaymentMethod method) {
return method == PaymentMethod.PAYPAL || method == [PaymentMethod.BANK](<http://PaymentMethod.BANK>)_TRANSFER;
}
}
// ========================================================================================
// π¦ Module 5: Main Application Module
// ========================================================================================
// File: [ecommerce.app/src/module-info.java](<http://ecommerce.app/src/module-info.java>)
module [ecommerce.app](<http://ecommerce.app>) {
// Require all necessary modules
requires ecommerce.api;
requires ecommerce.service;
// Platform modules
requires java.logging;
requires java.base; // implicit, but shown for clarity
// Payment processors are loaded dynamically via ServiceLoader
uses com.ecommerce.api.payment.PaymentProcessor;
// Main class is in this module
exports [com.ecommerce.app](<http://com.ecommerce.app>) to java.base; // Allow JVM to access main method
}
// π Main Application
// File: [ecommerce.app/src/com/ecommerce/app/ECommerceApplication.java](<http://ecommerce.app/src/com/ecommerce/app/ECommerceApplication.java>)
package [com.ecommerce.app](<http://com.ecommerce.app>);
import com.ecommerce.api.product.*;
import com.ecommerce.api.payment.*;
import java.math.BigDecimal;
import java.util.List;
import java.util.ServiceLoader;
import java.util.logging.Logger;
import [java.util.stream](<http://java.util.stream>).Collectors;
public class ECommerceApplication {
private static final Logger logger = Logger.getLogger(ECommerceApplication.class.getName());
public static void main(String[] args) {
[logger.info](<http://logger.info>)("π Starting E-Commerce Platform");
ECommerceApplication app = new ECommerceApplication();
// Demonstrate modular architecture
app.demonstrateProductService();
app.demonstratePaymentProcessors();
app.simulateOrderProcessing();
[logger.info](<http://logger.info>)("β
E-Commerce Platform demonstration completed");
}
private void demonstrateProductService() {
System.out.println("\\nποΈ Product Service Demonstration");
System.out.println("=================================");
// Load ProductService implementation via ServiceLoader
ServiceLoader<ProductService> loader = ServiceLoader.load(ProductService.class);
ProductService productService = loader.findFirst()
.orElseThrow(() -> new RuntimeException("No ProductService implementation found"));
System.out.println("π¦ Product service loaded: " + productService.getClass().getName());
// Find all products
List<Product> allProducts = productService.findAll();
System.out.println("\\nπ All Products (" + allProducts.size() + "):");
allProducts.forEach(product ->
System.out.println(" β’ " + product.getName() + " - $" + product.getPrice()));
// Search by category
List<Product> electronics = productService.findByCategory("Electronics");
System.out.println("\\nπ Electronics (" + electronics.size() + "):");
electronics.forEach(product ->
System.out.println(" β’ " + product.getName() + " - $" + product.getPrice()));
// Search products
List<Product> searchResults = [productService.search](<http://productService.search>)("java");
System.out.println("\\nπ Search results for 'java' (" + searchResults.size() + "):");
searchResults.forEach(product ->
System.out.println(" β’ " + product.getName() + " - $" + product.getPrice()));
}
private void demonstratePaymentProcessors() {
System.out.println("\\nπ³ Payment Processor Demonstration");
System.out.println("===================================");
// Load all available payment processors
ServiceLoader<PaymentProcessor> loader = ServiceLoader.load(PaymentProcessor.class);
List<PaymentProcessor> processors = [loader.stream](<http://loader.stream>)()
.map(ServiceLoader.Provider::get)
.collect(Collectors.toList());
System.out.println("π° Available Payment Processors (" + processors.size() + "):");
processors.forEach(processor ->
System.out.println(" β’ " + processor.getProviderName()));
// Test each payment processor
PaymentRequest testRequest = new PaymentRequest(
"customer123",
new BigDecimal("99.99"),
[PaymentMethod.CREDIT](<http://PaymentMethod.CREDIT>)_CARD,
"USD"
);
System.out.println("\\nπ§ͺ Testing payment processing:");
for (PaymentProcessor processor : processors) {
System.out.println("\\n Testing " + processor.getProviderName() + ":");
// Check if processor supports the payment method
boolean supports = processor.supports(testRequest.getMethod());
System.out.println(" Supports Credit Card: " + supports);
if (supports) {
PaymentResult result = processor.processPayment(testRequest);
System.out.println(" Result: " + (result.isSuccess() ? "SUCCESS" : "FAILED"));
System.out.println(" Message: " + result.getMessage());
if (result.isSuccess()) {
System.out.println(" Transaction ID: " + result.getTransactionId());
}
}
}
}
private void simulateOrderProcessing() {
System.out.println("\\nπ Order Processing Simulation");
System.out.println("===============================");
// Load services
ProductService productService = ServiceLoader.load(ProductService.class)
.findFirst()
.orElseThrow(() -> new RuntimeException("ProductService not found"));
List<PaymentProcessor> paymentProcessors = ServiceLoader.load(PaymentProcessor.class)
.stream()
.map(ServiceLoader.Provider::get)
.collect(Collectors.toList());
// Simulate an order
System.out.println("π Creating sample order...");
// Find some products
List<Product> cart = List.of(
productService.findById("1").orElse(null),
productService.findById("3").orElse(null)
).stream()
.filter(p -> p != null)
.collect(Collectors.toList());
if (cart.isEmpty()) {
System.out.println("β No products found for order");
return;
}
// Calculate total
BigDecimal total = [cart.stream](<http://cart.stream>)()
.map(Product::getPrice)
.reduce([BigDecimal.ZERO](<http://BigDecimal.ZERO>), BigDecimal::add);
System.out.println("ποΈ Order contents:");
cart.forEach(product ->
System.out.println(" β’ " + product.getName() + " - $" + product.getPrice()));
System.out.println("π° Total: $" + total);
// Try different payment methods
PaymentMethod[] methods = {[PaymentMethod.CREDIT](<http://PaymentMethod.CREDIT>)_CARD, PaymentMethod.PAYPAL};
for (PaymentMethod method : methods) {
System.out.println("\\nπ³ Attempting payment with " + method + ":");
// Find a suitable payment processor
PaymentProcessor processor = [paymentProcessors.stream](<http://paymentProcessors.stream>)()
.filter(p -> p.supports(method))
.findFirst()
.orElse(null);
if (processor != null) {
PaymentRequest request = new PaymentRequest("customer123", total, method, "USD");
PaymentResult result = processor.processPayment(request);
System.out.println(" Processor: " + processor.getProviderName());
System.out.println(" Result: " + (result.isSuccess() ? "β
SUCCESS" : "β FAILED"));
System.out.println(" Message: " + result.getMessage());
if (result.isSuccess()) {
System.out.println(" Transaction ID: " + result.getTransactionId());
System.out.println(" π Order completed successfully!");
break; // Order successful, stop trying other methods
}
} else {
System.out.println(" β No processor available for " + method);
}
}
}
}