Thursday, March 19, 2026

APPLYING ARCHITECTURE AND DESIGN PATTERNS: A SYSTEMATIC APPROACH TO BETTER SOFTWARE

 



INTRODUCTION: THE FOUNDATION OF ROBUST SOFTWARE SYSTEMS


Software patterns represent proven solutions to recurring problems in software design and architecture. They serve as a vocabulary for developers, enabling clear communication about design decisions and providing tested approaches to common challenges. However, the mere knowledge of patterns does not guarantee their effective application. The art lies not in knowing patterns, but in understanding when, where, and how to apply them systematically.


The journey from a novice pattern user to an expert practitioner requires understanding fundamental principles that govern pattern application. These principles ensure that patterns enhance rather than complicate software systems, leading to maintainable, scalable, and robust applications.


THE CARDINAL RULES OF PATTERN APPLICATION


The first and most crucial rule is to begin with architectural patterns before considering design patterns. Architectural patterns establish the overall structure and organization of your system, defining the high-level components and their relationships. Design patterns, in contrast, address specific design problems within individual components or small groups of classes. Attempting to apply design patterns without a solid architectural foundation is akin to decorating a house before building its frame.


Consider this fundamental truth: architecture provides the skeleton upon which design patterns flesh out specific functionality. Without this skeletal structure, design patterns become scattered solutions that lack cohesion and may even work against each other.


The second cardinal rule demands applying only one pattern at a time. This disciplined approach prevents the common mistake of pattern overload, where multiple patterns are simultaneously introduced, making it impossible to evaluate their individual impacts. When patterns are applied incrementally, you can assess each pattern’s contribution to the system’s overall quality and make informed decisions about whether to retain, modify, or remove it.


The third rule emphasizes that patterns are not applied mechanistically like stamps on paper. Instead, pattern application involves thoughtful assignment of pattern roles to existing or newly created components. This means understanding not just what a pattern does, but how its constituent parts map to your specific domain and requirements.


The fourth and final rule requires thoroughly reading and understanding the pattern description before application. This seems obvious, yet many developers rush to implement patterns based on superficial understanding or code examples alone. Each pattern comes with specific intent, applicability conditions, consequences, and implementation considerations that must be fully grasped.


UNDERSTANDING THE DISTINCTION: ARCHITECTURAL VS DESIGN PATTERNS


Architectural patterns operate at the system level, defining the overall structure and organization of software applications. They establish the fundamental blueprint that guides how major components interact and collaborate. Examples include Layered Architecture, Model-View-Controller, and Microservices Architecture.


Design patterns, conversely, focus on specific design problems within the context established by architectural patterns. They address how individual classes and objects interact to solve particular problems while maintaining flexibility and reusability. Examples include Strategy, Observer, and Factory patterns.


This distinction is crucial because architectural patterns provide the context within which design patterns operate. A Strategy pattern implementation will differ significantly depending on whether it exists within a layered architecture or a microservices architecture.


STARTING WITH ARCHITECTURAL PATTERNS: THE FOUNDATION LAYER


Let us examine how to properly establish an architectural foundation using a practical example. We will build an e-commerce order processing system, beginning with a layered architecture pattern.


The layered architecture organizes code into horizontal layers, each with specific responsibilities. The typical structure includes a presentation layer for user interfaces, a business layer for application logic, and a data layer for persistence operations.


Here is how we establish the foundational structure:



// Presentation Layer - Handles user interactions and data presentation

public class OrderController {

    private OrderService orderService;

    

    public OrderController(OrderService orderService) {

        this.orderService = orderService;

    }

    

    /**

     * Processes order creation request from the presentation layer

     * Delegates business logic to the service layer

     */

    public OrderResponse createOrder(OrderRequest request) {

        try {

            Order order = orderService.processOrder(request);

            return new OrderResponse(order.getId(), "Order created successfully");

        } catch (InvalidOrderException e) {

            return new OrderResponse(null, "Order creation failed: " + e.getMessage());

        }

    }

}



The presentation layer component shown above demonstrates proper layer separation. It handles the incoming request, delegates business logic to the appropriate service, and formats the response for the presentation layer. Notice how it does not contain any business logic or direct data access code.


The business layer contains the core application logic and business rules:



// Business Layer - Contains business logic and rules

public class OrderService {

    private OrderRepository orderRepository;

    private PaymentService paymentService;

    

    public OrderService(OrderRepository orderRepository, PaymentService paymentService) {

        this.orderRepository = orderRepository;

        this.paymentService = paymentService;

    }

    

    /**

     * Processes order according to business rules

     * Coordinates between different business components

     */

    public Order processOrder(OrderRequest request) throws InvalidOrderException {

        // Business rule validation

        validateOrderRequest(request);

        

        // Create order entity

        Order order = new Order(request.getCustomerId(), request.getItems());

        

        // Process payment

        PaymentResult paymentResult = paymentService.processPayment(order.getTotalAmount());

        

        if (paymentResult.isSuccessful()) {

            order.markAsPaid();

            return orderRepository.save(order);

        } else {

            throw new InvalidOrderException("Payment processing failed");

        }

    }

    

    private void validateOrderRequest(OrderRequest request) throws InvalidOrderException {

        if (request.getItems() == null || request.getItems().isEmpty()) {

            throw new InvalidOrderException("Order must contain at least one item");

        }

        if (request.getCustomerId() == null) {

            throw new InvalidOrderException("Customer ID is required");

        }

    }

}



The business layer component demonstrates proper encapsulation of business logic. It coordinates between different business services, enforces business rules, and manages the overall order processing workflow without concerning itself with presentation details or data storage mechanisms.


The data layer handles all persistence-related operations:



// Data Layer - Handles data persistence and retrieval

public class OrderRepositoryImpl implements OrderRepository {

    private DatabaseConnection connection;

    

    public OrderRepositoryImpl(DatabaseConnection connection) {

        this.connection = connection;

    }

    

    /**

     * Persists order to database

     * Handles database-specific operations and error handling

     */

    public Order save(Order order) {

        String sql = "INSERT INTO orders (customer_id, total_amount, status) VALUES (?, ?, ?)";

        

        try (PreparedStatement stmt = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {

            stmt.setLong(1, order.getCustomerId());

            stmt.setBigDecimal(2, order.getTotalAmount());

            stmt.setString(3, order.getStatus().toString());

            

            int affectedRows = stmt.executeUpdate();

            if (affectedRows == 0) {

                throw new DataAccessException("Order creation failed, no rows affected");

            }

            

            try (ResultSet generatedKeys = stmt.getGeneratedKeys()) {

                if (generatedKeys.next()) {

                    order.setId(generatedKeys.getLong(1));

                    return order;

                } else {

                    throw new DataAccessException("Order creation failed, no ID obtained");

                }

            }

        } catch (SQLException e) {

            throw new DataAccessException("Database error during order creation", e);

        }

    }

}



The data layer component focuses exclusively on data persistence concerns. It translates domain objects into database operations and handles database-specific error conditions, completely isolating these concerns from the business layer.


This layered architecture provides several important benefits. Each layer has a single, well-defined responsibility, making the system easier to understand and maintain. Changes in one layer have minimal impact on other layers, provided the interfaces between layers remain stable. The architecture also supports testing, as each layer can be tested independently with appropriate mocking of dependencies.


INTEGRATING DESIGN PATTERNS WITHIN THE ARCHITECTURAL STRUCTURE


Once the architectural foundation is established, we can thoughtfully integrate design patterns to address specific design challenges within each layer. The key is to identify actual problems that patterns can solve, rather than applying patterns for their own sake.


Consider the payment processing requirement in our order system. Currently, the system supports only one payment method, but business requirements demand support for multiple payment types such as credit cards, digital wallets, and bank transfers. This presents a perfect opportunity for the Strategy pattern.


The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. In our payment processing context, each payment method represents a different algorithm for processing payments.


Here is how we integrate the Strategy pattern within our established architecture:



// Strategy interface defines the contract for all payment strategies

public interface PaymentStrategy {

    /**

     * Processes payment using specific payment method

     * Returns result indicating success or failure with details

     */

    PaymentResult processPayment(BigDecimal amount, PaymentDetails details);

    

    /**

     * Validates that the payment details are appropriate for this strategy

     */

    boolean isValidForPayment(PaymentDetails details);

}



The strategy interface establishes the contract that all payment strategies must fulfill. This interface resides in the business layer, as it represents a business concept rather than a technical implementation detail.


Now we implement concrete strategies for different payment methods:



// Concrete strategy for credit card payments

public class CreditCardPaymentStrategy implements PaymentStrategy {

    private CreditCardProcessor processor;

    

    public CreditCardPaymentStrategy(CreditCardProcessor processor) {

        this.processor = processor;

    }

    

    @Override

    public PaymentResult processPayment(BigDecimal amount, PaymentDetails details) {

        if (!isValidForPayment(details)) {

            return PaymentResult.failure("Invalid credit card details");

        }

        

        CreditCardDetails cardDetails = (CreditCardDetails) details;

        

        // Validate credit card specific business rules

        if (amount.compareTo(cardDetails.getCreditLimit()) > 0) {

            return PaymentResult.failure("Amount exceeds credit limit");

        }

        

        // Process through credit card processor

        return processor.charge(cardDetails.getCardNumber(), amount);

    }

    

    @Override

    public boolean isValidForPayment(PaymentDetails details) {

        return details instanceof CreditCardDetails && 

               ((CreditCardDetails) details).getCardNumber() != null &&

               ((CreditCardDetails) details).getExpiryDate().isAfter(LocalDate.now());

    }

}



Each concrete strategy encapsulates the specific algorithm and business rules for its payment method. The credit card strategy includes validation specific to credit card payments and delegates the actual processing to a specialized processor component.


The context class coordinates strategy selection and execution:



// Context class that uses strategies

public class PaymentService {

    private Map<PaymentType, PaymentStrategy> strategies;

    

    public PaymentService(Map<PaymentType, PaymentStrategy> strategies) {

        this.strategies = new HashMap<>(strategies);

    }

    

    /**

     * Processes payment using appropriate strategy based on payment details

     * Demonstrates strategy selection and execution

     */

    public PaymentResult processPayment(BigDecimal amount, PaymentDetails details) {

        PaymentType type = details.getPaymentType();

        PaymentStrategy strategy = strategies.get(type);

        

        if (strategy == null) {

            return PaymentResult.failure("Unsupported payment type: " + type);

        }

        

        if (!strategy.isValidForPayment(details)) {

            return PaymentResult.failure("Invalid payment details for type: " + type);

        }

        

        return strategy.processPayment(amount, details);

    }

    

    /**

     * Allows dynamic addition of new payment strategies

     * Supports extensibility without modifying existing code

     */

    public void addPaymentStrategy(PaymentType type, PaymentStrategy strategy) {

        strategies.put(type, strategy);

    }

}



The PaymentService acts as the context in the Strategy pattern. It maintains references to available strategies and selects the appropriate one based on the payment details. This design makes it easy to add new payment methods without modifying existing code, demonstrating the Open-Closed Principle.


PATTERN ROLE ASSIGNMENT AND THOUGHTFUL APPLICATION


The previous example illustrates a crucial concept in pattern application: assigning pattern roles to components based on their natural responsibilities rather than forcing artificial divisions. The PaymentService naturally serves as the Strategy context because it already coordinates payment processing. The different payment methods naturally become strategies because they represent alternative algorithms for the same goal.


This approach contrasts sharply with mechanical pattern application, where developers create new classes solely to fit pattern structures. Such mechanical application often leads to over-engineered solutions that obscure rather than clarify the design intent.


Consider how we might extend our system with the Observer pattern to handle order status notifications. Rather than creating artificial observer structures, we identify components that naturally fit observer roles.


The Order class becomes the subject (observable) because it naturally maintains state that other components need to monitor:



// Subject in Observer pattern - naturally maintains observable state

public class Order {

    private Long id;

    private Long customerId;

    private List<OrderItem> items;

    private OrderStatus status;

    private List<OrderStatusObserver> observers;

    

    public Order(Long customerId, List<OrderItem> items) {

        this.customerId = customerId;

        this.items = new ArrayList<>(items);

        this.status = OrderStatus.PENDING;

        this.observers = new ArrayList<>();

    }

    

    /**

     * Adds observer to be notified of status changes

     * Part of Observer pattern implementation

     */

    public void addStatusObserver(OrderStatusObserver observer) {

        observers.add(observer);

    }

    

    /**

     * Changes order status and notifies all observers

     * Demonstrates proper observer notification

     */

    public void updateStatus(OrderStatus newStatus) {

        OrderStatus oldStatus = this.status;

        this.status = newStatus;

        notifyStatusObservers(oldStatus, newStatus);

    }

    

    private void notifyStatusObservers(OrderStatus oldStatus, OrderStatus newStatus) {

        for (OrderStatusObserver observer : observers) {

            try {

                observer.onStatusChanged(this, oldStatus, newStatus);

            } catch (Exception e) {

                // Log error but continue notifying other observers

                System.err.println("Error notifying observer: " + e.getMessage());

            }

        }

    }

}



The Order class naturally maintains state that other parts of the system need to monitor. By making it observable, we enable loose coupling between the order and the various systems that need to react to order status changes.


Concrete observers implement the observer interface to handle specific notification requirements:



// Concrete observer for email notifications

public class EmailNotificationObserver implements OrderStatusObserver {

    private EmailService emailService;

    private CustomerService customerService;

    

    public EmailNotificationObserver(EmailService emailService, CustomerService customerService) {

        this.emailService = emailService;

        this.customerService = customerService;

    }

    

    /**

     * Handles order status change by sending appropriate email

     * Demonstrates observer pattern in action

     */

    @Override

    public void onStatusChanged(Order order, OrderStatus oldStatus, OrderStatus newStatus) {

        if (shouldSendEmail(oldStatus, newStatus)) {

            Customer customer = customerService.findById(order.getCustomerId());

            String subject = generateEmailSubject(order, newStatus);

            String body = generateEmailBody(order, newStatus);

            

            emailService.sendEmail(customer.getEmail(), subject, body);

        }

    }

    

    private boolean shouldSendEmail(OrderStatus oldStatus, OrderStatus newStatus) {

        // Send emails for significant status changes

        return newStatus == OrderStatus.CONFIRMED ||

               newStatus == OrderStatus.SHIPPED ||

               newStatus == OrderStatus.DELIVERED ||

               newStatus == OrderStatus.CANCELLED;

    }

    

    private String generateEmailSubject(Order order, OrderStatus status) {

        return "Order #" + order.getId() + " - " + status.getDisplayName();

    }

    

    private String generateEmailBody(Order order, OrderStatus status) {

        StringBuilder body = new StringBuilder();

        body.append("Dear Customer,\n\n");

        body.append("Your order #").append(order.getId());

        body.append(" has been ").append(status.getDisplayName().toLowerCase()).append(".\n\n");

        

        if (status == OrderStatus.SHIPPED) {

            body.append("You can track your shipment using the tracking number provided.\n\n");

        }

        

        body.append("Thank you for your business!\n");

        body.append("The E-Commerce Team");

        

        return body.toString();

    }

}



This observer implementation demonstrates how components can naturally assume observer roles based on their existing responsibilities. The email notification system already needs to respond to order changes, making it a natural observer of order status.


AVOIDING COMMON PITFALLS AND EMBRACING BEST PRACTICES


One of the most common mistakes in pattern application is the tendency to apply patterns preemptively, before actual problems arise. This often leads to over-engineered solutions that are more complex than the problems they attempt to solve. The antidote is to apply patterns reactively, in response to identified problems or requirements.


Another frequent pitfall is misunderstanding the scope and intent of patterns. Developers sometimes force patterns into contexts where they do not fit naturally, creating awkward and counterproductive designs. The solution is to thoroughly understand each pattern’s intent, applicability, and consequences before considering its use.


Pattern composition presents another challenge. When multiple patterns interact within the same system, their combined complexity can become overwhelming. The key is to ensure that patterns complement rather than compete with each other, and that their interactions are well-understood and documented.


Finally, many developers neglect the evolutionary aspect of pattern application. Patterns should not be viewed as permanent fixtures but as design decisions that can be revisited and modified as requirements change. This requires maintaining clean boundaries and clear interfaces that support pattern refactoring when necessary.


CONCLUSION: MASTERING THE ART OF PATTERN APPLICATION


Effective pattern application is both art and science. It requires technical knowledge of pattern structures and implementations, but also design judgment about when and where to apply them. The rules presented in this article provide a framework for developing this judgment, but experience and practice remain essential for mastery.


The key insight is that patterns serve the software, not the reverse. They are tools for solving specific problems and achieving specific quality attributes. When applied thoughtfully and systematically, they enhance software quality and developer productivity. When applied mechanically or prematurely, they can complicate and obscure otherwise straightforward designs.


Remember that great software is measured not by the number of patterns it employs, but by how well it solves real problems for real users. Patterns are valuable servants in this endeavor, but they must be employed with wisdom and restraint.



FULL RUNNING EXAMPLE -  E-COMMERCE ORDER PROCESSING SYSTEM



The following complete example demonstrates the integration of architectural and design patterns in a cohesive system. This implementation includes all the concepts discussed in the article and provides a fully functional foundation for an e-commerce order processing system.



import java.math.BigDecimal;

import java.sql.*;

import java.time.LocalDate;

import java.time.LocalDateTime;

import java.util.*;


// ============================================================================

// DOMAIN ENTITIES AND VALUE OBJECTS

// ============================================================================


/**

 * Core domain entity representing a customer order

 * Serves as Subject in Observer pattern for status notifications

 */

public class Order {

    private Long id;

    private Long customerId;

    private List<OrderItem> items;

    private OrderStatus status;

    private BigDecimal totalAmount;

    private LocalDateTime createdAt;

    private List<OrderStatusObserver> observers;

    

    public Order(Long customerId, List<OrderItem> items) {

        this.customerId = customerId;

        this.items = new ArrayList<>(items);

        this.status = OrderStatus.PENDING;

        this.createdAt = LocalDateTime.now();

        this.observers = new ArrayList<>();

        calculateTotal();

    }

    

    private void calculateTotal() {

        this.totalAmount = items.stream()

            .map(item -> item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())))

            .reduce(BigDecimal.ZERO, BigDecimal::add);

    }

    

    public void addStatusObserver(OrderStatusObserver observer) {

        observers.add(observer);

    }

    

    public void removeStatusObserver(OrderStatusObserver observer) {

        observers.remove(observer);

    }

    

    public void updateStatus(OrderStatus newStatus) {

        OrderStatus oldStatus = this.status;

        this.status = newStatus;

        notifyStatusObservers(oldStatus, newStatus);

    }

    

    private void notifyStatusObservers(OrderStatus oldStatus, OrderStatus newStatus) {

        for (OrderStatusObserver observer : observers) {

            try {

                observer.onStatusChanged(this, oldStatus, newStatus);

            } catch (Exception e) {

                System.err.println("Error notifying observer: " + e.getMessage());

            }

        }

    }

    

    public void markAsPaid() {

        updateStatus(OrderStatus.CONFIRMED);

    }

    

    // Getters and setters

    public Long getId() { return id; }

    public void setId(Long id) { this.id = id; }

    public Long getCustomerId() { return customerId; }

    public List<OrderItem> getItems() { return new ArrayList<>(items); }

    public OrderStatus getStatus() { return status; }

    public BigDecimal getTotalAmount() { return totalAmount; }

    public LocalDateTime getCreatedAt() { return createdAt; }

}


/**

 * Represents an individual item within an order

 */

public class OrderItem {

    private Long productId;

    private String productName;

    private BigDecimal price;

    private int quantity;

    

    public OrderItem(Long productId, String productName, BigDecimal price, int quantity) {

        this.productId = productId;

        this.productName = productName;

        this.price = price;

        this.quantity = quantity;

    }

    

    // Getters

    public Long getProductId() { return productId; }

    public String getProductName() { return productName; }

    public BigDecimal getPrice() { return price; }

    public int getQuantity() { return quantity; }

}


/**

 * Enumeration of possible order statuses

 */

public enum OrderStatus {

    PENDING("Pending"),

    CONFIRMED("Confirmed"),

    SHIPPED("Shipped"),

    DELIVERED("Delivered"),

    CANCELLED("Cancelled");

    

    private final String displayName;

    

    OrderStatus(String displayName) {

        this.displayName = displayName;

    }

    

    public String getDisplayName() {

        return displayName;

    }

}


/**

 * Represents customer information

 */

public class Customer {

    private Long id;

    private String name;

    private String email;

    private String address;

    

    public Customer(Long id, String name, String email, String address) {

        this.id = id;

        this.name = name;

        this.email = email;

        this.address = address;

    }

    

    // Getters

    public Long getId() { return id; }

    public String getName() { return name; }

    public String getEmail() { return email; }

    public String getAddress() { return address; }

}


// ============================================================================

// REQUEST AND RESPONSE OBJECTS

// ============================================================================


/**

 * Request object for order creation

 */

public class OrderRequest {

    private Long customerId;

    private List<OrderItem> items;

    private PaymentDetails paymentDetails;

    

    public OrderRequest(Long customerId, List<OrderItem> items, PaymentDetails paymentDetails) {

        this.customerId = customerId;

        this.items = items;

        this.paymentDetails = paymentDetails;

    }

    

    // Getters

    public Long getCustomerId() { return customerId; }

    public List<OrderItem> getItems() { return items; }

    public PaymentDetails getPaymentDetails() { return paymentDetails; }

}


/**

 * Response object for order operations

 */

public class OrderResponse {

    private Long orderId;

    private String message;

    private boolean success;

    

    public OrderResponse(Long orderId, String message) {

        this.orderId = orderId;

        this.message = message;

        this.success = orderId != null;

    }

    

    // Getters

    public Long getOrderId() { return orderId; }

    public String getMessage() { return message; }

    public boolean isSuccess() { return success; }

}


// ============================================================================

// STRATEGY PATTERN IMPLEMENTATION FOR PAYMENTS

// ============================================================================


/**

 * Abstract base class for payment details

 */

public abstract class PaymentDetails {

    public abstract PaymentType getPaymentType();

}


/**

 * Credit card payment details implementation

 */

public class CreditCardDetails extends PaymentDetails {

    private String cardNumber;

    private String holderName;

    private LocalDate expiryDate;

    private String cvv;

    private BigDecimal creditLimit;

    

    public CreditCardDetails(String cardNumber, String holderName, 

                           LocalDate expiryDate, String cvv, BigDecimal creditLimit) {

        this.cardNumber = cardNumber;

        this.holderName = holderName;

        this.expiryDate = expiryDate;

        this.cvv = cvv;

        this.creditLimit = creditLimit;

    }

    

    @Override

    public PaymentType getPaymentType() {

        return PaymentType.CREDIT_CARD;

    }

    

    // Getters

    public String getCardNumber() { return cardNumber; }

    public String getHolderName() { return holderName; }

    public LocalDate getExpiryDate() { return expiryDate; }

    public String getCvv() { return cvv; }

    public BigDecimal getCreditLimit() { return creditLimit; }

}


/**

 * Digital wallet payment details implementation

 */

public class DigitalWalletDetails extends PaymentDetails {

    private String walletId;

    private String walletProvider;

    private BigDecimal balance;

    

    public DigitalWalletDetails(String walletId, String walletProvider, BigDecimal balance) {

        this.walletId = walletId;

        this.walletProvider = walletProvider;

        this.balance = balance;

    }

    

    @Override

    public PaymentType getPaymentType() {

        return PaymentType.DIGITAL_WALLET;

    }

    

    // Getters

    public String getWalletId() { return walletId; }

    public String getWalletProvider() { return walletProvider; }

    public BigDecimal getBalance() { return balance; }

}


/**

 * Enumeration of supported payment types

 */

public enum PaymentType {

    CREDIT_CARD,

    DIGITAL_WALLET,

    BANK_TRANSFER

}


/**

 * Result object for payment operations

 */

public class PaymentResult {

    private boolean successful;

    private String message;

    private String transactionId;

    

    private PaymentResult(boolean successful, String message, String transactionId) {

        this.successful = successful;

        this.message = message;

        this.transactionId = transactionId;

    }

    

    public static PaymentResult success(String transactionId) {

        return new PaymentResult(true, "Payment processed successfully", transactionId);

    }

    

    public static PaymentResult failure(String message) {

        return new PaymentResult(false, message, null);

    }

    

    // Getters

    public boolean isSuccessful() { return successful; }

    public String getMessage() { return message; }

    public String getTransactionId() { return transactionId; }

}


/**

 * Strategy interface for payment processing

 */

public interface PaymentStrategy {

    PaymentResult processPayment(BigDecimal amount, PaymentDetails details);

    boolean isValidForPayment(PaymentDetails details);

}


/**

 * Concrete strategy for credit card payments

 */

public class CreditCardPaymentStrategy implements PaymentStrategy {

    private CreditCardProcessor processor;

    

    public CreditCardPaymentStrategy(CreditCardProcessor processor) {

        this.processor = processor;

    }

    

    @Override

    public PaymentResult processPayment(BigDecimal amount, PaymentDetails details) {

        if (!isValidForPayment(details)) {

            return PaymentResult.failure("Invalid credit card details");

        }

        

        CreditCardDetails cardDetails = (CreditCardDetails) details;

        

        if (amount.compareTo(cardDetails.getCreditLimit()) > 0) {

            return PaymentResult.failure("Amount exceeds credit limit");

        }

        

        return processor.charge(cardDetails.getCardNumber(), amount);

    }

    

    @Override

    public boolean isValidForPayment(PaymentDetails details) {

        return details instanceof CreditCardDetails && 

               ((CreditCardDetails) details).getCardNumber() != null &&

               ((CreditCardDetails) details).getExpiryDate().isAfter(LocalDate.now());

    }

}


/**

 * Concrete strategy for digital wallet payments

 */

public class DigitalWalletPaymentStrategy implements PaymentStrategy {

    private DigitalWalletProcessor processor;

    

    public DigitalWalletPaymentStrategy(DigitalWalletProcessor processor) {

        this.processor = processor;

    }

    

    @Override

    public PaymentResult processPayment(BigDecimal amount, PaymentDetails details) {

        if (!isValidForPayment(details)) {

            return PaymentResult.failure("Invalid digital wallet details");

        }

        

        DigitalWalletDetails walletDetails = (DigitalWalletDetails) details;

        

        if (amount.compareTo(walletDetails.getBalance()) > 0) {

            return PaymentResult.failure("Insufficient wallet balance");

        }

        

        return processor.debit(walletDetails.getWalletId(), amount);

    }

    

    @Override

    public boolean isValidForPayment(PaymentDetails details) {

        return details instanceof DigitalWalletDetails &&

               ((DigitalWalletDetails) details).getWalletId() != null;

    }

}


// ============================================================================

// EXTERNAL PROCESSORS (SIMULATED)

// ============================================================================


/**

 * Simulated external credit card processor

 */

public class CreditCardProcessor {

    public PaymentResult charge(String cardNumber, BigDecimal amount) {

        // Simulate external processing

        if (cardNumber.endsWith("0000")) {

            return PaymentResult.failure("Card declined");

        }

        

        String transactionId = "CC_" + System.currentTimeMillis();

        return PaymentResult.success(transactionId);

    }

}


/**

 * Simulated external digital wallet processor

 */

public class DigitalWalletProcessor {

    public PaymentResult debit(String walletId, BigDecimal amount) {

        // Simulate external processing

        if (walletId.startsWith("INVALID")) {

            return PaymentResult.failure("Invalid wallet");

        }

        

        String transactionId = "DW_" + System.currentTimeMillis();

        return PaymentResult.success(transactionId);

    }

}


// ============================================================================

// OBSERVER PATTERN IMPLEMENTATION

// ============================================================================


/**

 * Observer interface for order status changes

 */

public interface OrderStatusObserver {

    void onStatusChanged(Order order, OrderStatus oldStatus, OrderStatus newStatus);

}


/**

 * Concrete observer for email notifications

 */

public class EmailNotificationObserver implements OrderStatusObserver {

    private EmailService emailService;

    private CustomerService customerService;

    

    public EmailNotificationObserver(EmailService emailService, CustomerService customerService) {

        this.emailService = emailService;

        this.customerService = customerService;

    }

    

    @Override

    public void onStatusChanged(Order order, OrderStatus oldStatus, OrderStatus newStatus) {

        if (shouldSendEmail(oldStatus, newStatus)) {

            Customer customer = customerService.findById(order.getCustomerId());

            String subject = generateEmailSubject(order, newStatus);

            String body = generateEmailBody(order, newStatus);

            

            emailService.sendEmail(customer.getEmail(), subject, body);

        }

    }

    

    private boolean shouldSendEmail(OrderStatus oldStatus, OrderStatus newStatus) {

        return newStatus == OrderStatus.CONFIRMED ||

               newStatus == OrderStatus.SHIPPED ||

               newStatus == OrderStatus.DELIVERED ||

               newStatus == OrderStatus.CANCELLED;

    }

    

    private String generateEmailSubject(Order order, OrderStatus status) {

        return "Order #" + order.getId() + " - " + status.getDisplayName();

    }

    

    private String generateEmailBody(Order order, OrderStatus status) {

        StringBuilder body = new StringBuilder();

        body.append("Dear Customer,\n\n");

        body.append("Your order #").append(order.getId());

        body.append(" has been ").append(status.getDisplayName().toLowerCase()).append(".\n\n");

        

        if (status == OrderStatus.SHIPPED) {

            body.append("You can track your shipment using the tracking number provided.\n\n");

        }

        

        body.append("Thank you for your business!\n");

        body.append("The E-Commerce Team");

        

        return body.toString();

    }

}


/**

 * Concrete observer for inventory management

 */

public class InventoryUpdateObserver implements OrderStatusObserver {

    private InventoryService inventoryService;

    

    public InventoryUpdateObserver(InventoryService inventoryService) {

        this.inventoryService = inventoryService;

    }

    

    @Override

    public void onStatusChanged(Order order, OrderStatus oldStatus, OrderStatus newStatus) {

        if (newStatus == OrderStatus.CONFIRMED) {

            // Reserve inventory when order is confirmed

            for (OrderItem item : order.getItems()) {

                inventoryService.reserveItem(item.getProductId(), item.getQuantity());

            }

        } else if (newStatus == OrderStatus.CANCELLED) {

            // Release reserved inventory when order is cancelled

            for (OrderItem item : order.getItems()) {

                inventoryService.releaseReservation(item.getProductId(), item.getQuantity());

            }

        }

    }

}


// ============================================================================

// BUSINESS LAYER SERVICES

// ============================================================================


/**

 * Main service for order processing - Business Layer

 */

public class OrderService {

    private OrderRepository orderRepository;

    private PaymentService paymentService;

    

    public OrderService(OrderRepository orderRepository, PaymentService paymentService) {

        this.orderRepository = orderRepository;

        this.paymentService = paymentService;

    }

    

    public Order processOrder(OrderRequest request) throws InvalidOrderException {

        validateOrderRequest(request);

        

        Order order = new Order(request.getCustomerId(), request.getItems());

        

        PaymentResult paymentResult = paymentService.processPayment(

            order.getTotalAmount(), 

            request.getPaymentDetails()

        );

        

        if (paymentResult.isSuccessful()) {

            order.markAsPaid();

            return orderRepository.save(order);

        } else {

            throw new InvalidOrderException("Payment processing failed: " + paymentResult.getMessage());

        }

    }

    

    private void validateOrderRequest(OrderRequest request) throws InvalidOrderException {

        if (request.getItems() == null || request.getItems().isEmpty()) {

            throw new InvalidOrderException("Order must contain at least one item");

        }

        if (request.getCustomerId() == null) {

            throw new InvalidOrderException("Customer ID is required");

        }

        if (request.getPaymentDetails() == null) {

            throw new InvalidOrderException("Payment details are required");

        }

    }

    

    public Order findById(Long orderId) throws OrderNotFoundException {

        return orderRepository.findById(orderId)

            .orElseThrow(() -> new OrderNotFoundException("Order not found: " + orderId));

    }

    

    public void updateOrderStatus(Long orderId, OrderStatus newStatus) throws OrderNotFoundException {

        Order order = findById(orderId);

        order.updateStatus(newStatus);

        orderRepository.save(order);

    }

}


/**

 * Payment service implementing Strategy pattern - Business Layer

 */

public class PaymentService {

    private Map<PaymentType, PaymentStrategy> strategies;

    

    public PaymentService(Map<PaymentType, PaymentStrategy> strategies) {

        this.strategies = new HashMap<>(strategies);

    }

    

    public PaymentResult processPayment(BigDecimal amount, PaymentDetails details) {

        PaymentType type = details.getPaymentType();

        PaymentStrategy strategy = strategies.get(type);

        

        if (strategy == null) {

            return PaymentResult.failure("Unsupported payment type: " + type);

        }

        

        if (!strategy.isValidForPayment(details)) {

            return PaymentResult.failure("Invalid payment details for type: " + type);

        }

        

        return strategy.processPayment(amount, details);

    }

    

    public void addPaymentStrategy(PaymentType type, PaymentStrategy strategy) {

        strategies.put(type, strategy);

    }

}


/**

 * Customer service for customer-related operations - Business Layer

 */

public class CustomerService {

    private CustomerRepository customerRepository;

    

    public CustomerService(CustomerRepository customerRepository) {

        this.customerRepository = customerRepository;

    }

    

    public Customer findById(Long customerId) {

        return customerRepository.findById(customerId)

            .orElseThrow(() -> new CustomerNotFoundException("Customer not found: " + customerId));

    }

}


/**

 * Email service for sending notifications - Business Layer

 */

public class EmailService {

    public void sendEmail(String to, String subject, String body) {

        // Simulate email sending

        System.out.println("Sending email to: " + to);

        System.out.println("Subject: " + subject);

        System.out.println("Body: " + body);

        System.out.println("Email sent successfully");

    }

}


/**

 * Inventory service for managing product inventory - Business Layer

 */

public class InventoryService {

    public void reserveItem(Long productId, int quantity) {

        // Simulate inventory reservation

        System.out.println("Reserved " + quantity + " units of product " + productId);

    }

    

    public void releaseReservation(Long productId, int quantity) {

        // Simulate releasing inventory reservation

        System.out.println("Released reservation for " + quantity + " units of product " + productId);

    }

}


// ============================================================================

// PRESENTATION LAYER

// ============================================================================


/**

 * Controller handling order-related requests - Presentation Layer

 */

public class OrderController {

    private OrderService orderService;

    

    public OrderController(OrderService orderService) {

        this.orderService = orderService;

    }

    

    public OrderResponse createOrder(OrderRequest request) {

        try {

            Order order = orderService.processOrder(request);

            return new OrderResponse(order.getId(), "Order created successfully");

        } catch (InvalidOrderException e) {

            return new OrderResponse(null, "Order creation failed: " + e.getMessage());

        }

    }

    

    public OrderResponse updateOrderStatus(Long orderId, OrderStatus newStatus) {

        try {

            orderService.updateOrderStatus(orderId, newStatus);

            return new OrderResponse(orderId, "Order status updated successfully");

        } catch (OrderNotFoundException e) {

            return new OrderResponse(null, "Order update failed: " + e.getMessage());

        }

    }

}


// ============================================================================

// DATA LAYER

// ============================================================================


/**

 * Repository interface for order persistence

 */

public interface OrderRepository {

    Order save(Order order);

    Optional<Order> findById(Long id);

    List<Order> findByCustomerId(Long customerId);

}


/**

 * Repository implementation for order persistence - Data Layer

 */

public class OrderRepositoryImpl implements OrderRepository {

    private DatabaseConnection connection;

    

    public OrderRepositoryImpl(DatabaseConnection connection) {

        this.connection = connection;

    }

    

    @Override

    public Order save(Order order) {

        if (order.getId() == null) {

            return insertOrder(order);

        } else {

            return updateOrder(order);

        }

    }

    

    private Order insertOrder(Order order) {

        String sql = "INSERT INTO orders (customer_id, total_amount, status, created_at) VALUES (?, ?, ?, ?)";

        

        try (PreparedStatement stmt = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {

            stmt.setLong(1, order.getCustomerId());

            stmt.setBigDecimal(2, order.getTotalAmount());

            stmt.setString(3, order.getStatus().toString());

            stmt.setTimestamp(4, Timestamp.valueOf(order.getCreatedAt()));

            

            int affectedRows = stmt.executeUpdate();

            if (affectedRows == 0) {

                throw new DataAccessException("Order creation failed, no rows affected");

            }

            

            try (ResultSet generatedKeys = stmt.getGeneratedKeys()) {

                if (generatedKeys.next()) {

                    order.setId(generatedKeys.getLong(1));

                    saveOrderItems(order);

                    return order;

                } else {

                    throw new DataAccessException("Order creation failed, no ID obtained");

                }

            }

        } catch (SQLException e) {

            throw new DataAccessException("Database error during order creation", e);

        }

    }

    

    private Order updateOrder(Order order) {

        String sql = "UPDATE orders SET status = ? WHERE id = ?";

        

        try (PreparedStatement stmt = connection.prepareStatement(sql)) {

            stmt.setString(1, order.getStatus().toString());

            stmt.setLong(2, order.getId());

            

            int affectedRows = stmt.executeUpdate();

            if (affectedRows == 0) {

                throw new DataAccessException("Order update failed, no rows affected");

            }

            

            return order;

        } catch (SQLException e) {

            throw new DataAccessException("Database error during order update", e);

        }

    }

    

    private void saveOrderItems(Order order) {

        String sql = "INSERT INTO order_items (order_id, product_id, product_name, price, quantity) VALUES (?, ?, ?, ?, ?)";

        

        try (PreparedStatement stmt = connection.prepareStatement(sql)) {

            for (OrderItem item : order.getItems()) {

                stmt.setLong(1, order.getId());

                stmt.setLong(2, item.getProductId());

                stmt.setString(3, item.getProductName());

                stmt.setBigDecimal(4, item.getPrice());

                stmt.setInt(5, item.getQuantity());

                stmt.addBatch();

            }

            

            stmt.executeBatch();

        } catch (SQLException e) {

            throw new DataAccessException("Database error saving order items", e);

        }

    }

    

    @Override

    public Optional<Order> findById(Long id) {

        String sql = "SELECT * FROM orders WHERE id = ?";

        

        try (PreparedStatement stmt = connection.prepareStatement(sql)) {

            stmt.setLong(1, id);

            

            try (ResultSet rs = stmt.executeQuery()) {

                if (rs.next()) {

                    Order order = mapResultSetToOrder(rs);

                    loadOrderItems(order);

                    return Optional.of(order);

                } else {

                    return Optional.empty();

                }

            }

        } catch (SQLException e) {

            throw new DataAccessException("Database error finding order", e);

        }

    }

    

    @Override

    public List<Order> findByCustomerId(Long customerId) {

        String sql = "SELECT * FROM orders WHERE customer_id = ?";

        List<Order> orders = new ArrayList<>();

        

        try (PreparedStatement stmt = connection.prepareStatement(sql)) {

            stmt.setLong(1, customerId);

            

            try (ResultSet rs = stmt.executeQuery()) {

                while (rs.next()) {

                    Order order = mapResultSetToOrder(rs);

                    loadOrderItems(order);

                    orders.add(order);

                }

            }

        } catch (SQLException e) {

            throw new DataAccessException("Database error finding orders", e);

        }

        

        return orders;

    }

    

    private Order mapResultSetToOrder(ResultSet rs) throws SQLException {

        Long id = rs.getLong("id");

        Long customerId = rs.getLong("customer_id");

        OrderStatus status = OrderStatus.valueOf(rs.getString("status"));

        

        // Create order with empty items list for now

        Order order = new Order(customerId, new ArrayList<>());

        order.setId(id);

        order.updateStatus(status);

        

        return order;

    }

    

    private void loadOrderItems(Order order) {

        String sql = "SELECT * FROM order_items WHERE order_id = ?";

        List<OrderItem> items = new ArrayList<>();

        

        try (PreparedStatement stmt = connection.prepareStatement(sql)) {

            stmt.setLong(1, order.getId());

            

            try (ResultSet rs = stmt.executeQuery()) {

                while (rs.next()) {

                    OrderItem item = new OrderItem(

                        rs.getLong("product_id"),

                        rs.getString("product_name"),

                        rs.getBigDecimal("price"),

                        rs.getInt("quantity")

                    );

                    items.add(item);

                }

            }

        } catch (SQLException e) {

            throw new DataAccessException("Database error loading order items", e);

        }

        

        // Use reflection or provide setter to update items in order

        // This is simplified for the example

    }

}


/**

 * Repository interface for customer persistence

 */

public interface CustomerRepository {

    Optional<Customer> findById(Long id);

    Customer save(Customer customer);

}


/**

 * Repository implementation for customer persistence - Data Layer

 */

public class CustomerRepositoryImpl implements CustomerRepository {

    private DatabaseConnection connection;

    

    public CustomerRepositoryImpl(DatabaseConnection connection) {

        this.connection = connection;

    }

    

    @Override

    public Optional<Customer> findById(Long id) {

        String sql = "SELECT * FROM customers WHERE id = ?";

        

        try (PreparedStatement stmt = connection.prepareStatement(sql)) {

            stmt.setLong(1, id);

            

            try (ResultSet rs = stmt.executeQuery()) {

                if (rs.next()) {

                    Customer customer = new Customer(

                        rs.getLong("id"),

                        rs.getString("name"),

                        rs.getString("email"),

                        rs.getString("address")

                    );

                    return Optional.of(customer);

                } else {

                    return Optional.empty();

                }

            }

        } catch (SQLException e) {

            throw new DataAccessException("Database error finding customer", e);

        }

    }

    

    @Override

    public Customer save(Customer customer) {

        // Implementation would handle both insert and update

        // Simplified for this example

        return customer;

    }

}


// ============================================================================

// INFRASTRUCTURE AND UTILITIES

// ============================================================================


/**

 * Database connection wrapper

 */

public class DatabaseConnection {

    private Connection connection;

    

    public DatabaseConnection(String url, String username, String password) {

        try {

            this.connection = DriverManager.getConnection(url, username, password);

        } catch (SQLException e) {

            throw new DataAccessException("Failed to establish database connection", e);

        }

    }

    

    public PreparedStatement prepareStatement(String sql) throws SQLException {

        return connection.prepareStatement(sql);

    }

    

    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {

        return connection.prepareStatement(sql, autoGeneratedKeys);

    }

    

    public void close() {

        try {

            if (connection != null && !connection.isClosed()) {

                connection.close();

            }

        } catch (SQLException e) {

            System.err.println("Error closing database connection: " + e.getMessage());

        }

    }

}


// ============================================================================

// EXCEPTION CLASSES

// ============================================================================


/**

 * Business exception for invalid orders

 */

public class InvalidOrderException extends Exception {

    public InvalidOrderException(String message) {

        super(message);

    }

    

    public InvalidOrderException(String message, Throwable cause) {

        super(message, cause);

    }

}


/**

 * Business exception for order not found

 */

public class OrderNotFoundException extends RuntimeException {

    public OrderNotFoundException(String message) {

        super(message);

    }

}


/**

 * Business exception for customer not found

 */

public class CustomerNotFoundException extends RuntimeException {

    public CustomerNotFoundException(String message) {

        super(message);

    }

}


/**

 * Technical exception for data access errors

 */

public class DataAccessException extends RuntimeException {

    public DataAccessException(String message) {

        super(message);

    }

    

    public DataAccessException(String message, Throwable cause) {

        super(message, cause);

    }

}


// ============================================================================

// APPLICATION CONFIGURATION AND MAIN CLASS

// ============================================================================


/**

 * Main application class demonstrating system integration

 */

public class ECommerceApplication {

    private OrderController orderController;

    private DatabaseConnection databaseConnection;

    

    public void initialize() {

        // Initialize database connection

        databaseConnection = new DatabaseConnection(

            "jdbc:h2:mem:ecommerce", 

            "sa", 

            ""

        );

        

        // Initialize repositories

        OrderRepository orderRepository = new OrderRepositoryImpl(databaseConnection);

        CustomerRepository customerRepository = new CustomerRepositoryImpl(databaseConnection);

        

        // Initialize external processors

        CreditCardProcessor creditCardProcessor = new CreditCardProcessor();

        DigitalWalletProcessor walletProcessor = new DigitalWalletProcessor();

        

        // Initialize payment strategies

        Map<PaymentType, PaymentStrategy> paymentStrategies = new HashMap<>();

        paymentStrategies.put(PaymentType.CREDIT_CARD, 

            new CreditCardPaymentStrategy(creditCardProcessor));

        paymentStrategies.put(PaymentType.DIGITAL_WALLET, 

            new DigitalWalletPaymentStrategy(walletProcessor));

        

        // Initialize services

        PaymentService paymentService = new PaymentService(paymentStrategies);

        OrderService orderService = new OrderService(orderRepository, paymentService);

        CustomerService customerService = new CustomerService(customerRepository);

        EmailService emailService = new EmailService();

        InventoryService inventoryService = new InventoryService();

        

        // Initialize observers

        EmailNotificationObserver emailObserver = 

            new EmailNotificationObserver(emailService, customerService);

        InventoryUpdateObserver inventoryObserver = 

            new InventoryUpdateObserver(inventoryService);

        

        // Initialize controller

        orderController = new OrderController(orderService);

        

        System.out.println("E-Commerce application initialized successfully");

    }

    

    public void demonstrateOrderProcessing() {

        try {

            // Create sample order items

            List<OrderItem> items = Arrays.asList(

                new OrderItem(1L, "Laptop", new BigDecimal("999.99"), 1),

                new OrderItem(2L, "Mouse", new BigDecimal("25.00"), 2)

            );

            

            // Create payment details

            CreditCardDetails paymentDetails = new CreditCardDetails(

                "4111111111111111",

                "John Doe",

                LocalDate.now().plusYears(2),

                "123",

                new BigDecimal("5000.00")

            );

            

            // Create order request

            OrderRequest orderRequest = new OrderRequest(1L, items, paymentDetails);

            

            // Process order

            OrderResponse response = orderController.createOrder(orderRequest);

            

            if (response.isSuccess()) {

                System.out.println("Order processed successfully. Order ID: " + response.getOrderId());

                

                // Demonstrate status update

                OrderResponse updateResponse = orderController.updateOrderStatus(

                    response.getOrderId(), 

                    OrderStatus.SHIPPED

                );

                

                if (updateResponse.isSuccess()) {

                    System.out.println("Order status updated successfully");

                }

            } else {

                System.out.println("Order processing failed: " + response.getMessage());

            }

            

        } catch (Exception e) {

            System.err.println("Error during order processing: " + e.getMessage());

            e.printStackTrace();

        }

    }

    

    public void shutdown() {

        if (databaseConnection != null) {

            databaseConnection.close();

        }

        System.out.println("E-Commerce application shut down");

    }

    

    public static void main(String[] args) {

        ECommerceApplication app = new ECommerceApplication();

        

        try {

            app.initialize();

            app.demonstrateOrderProcessing();

        } finally {

            app.shutdown();

        }

    }

}



This complete example demonstrates the systematic application of architectural and design patterns in a cohesive system. The layered architecture provides the overall structure, while the Strategy pattern handles payment processing variations and the Observer pattern manages order status notifications. Each component has clearly defined responsibilities and maintains proper separation of concerns, creating a maintainable and extensible system.​​​​​​​​​​​​​​​​