1. java
  2. /enterprise
  3. /design-patterns

Essential Enterprise Design Patterns for Java Development

Enterprise Design Patterns

Design patterns are reusable solutions to commonly occurring problems in software design. In enterprise Java development, certain patterns are particularly important for creating maintainable, scalable, and robust applications. These patterns provide proven approaches to solving recurring design challenges.

Creational Patterns

Singleton Pattern

The Singleton pattern ensures a class has only one instance and provides global access to it.

// Thread-safe Singleton with lazy initialization
public class DatabaseConnection {
    
    private static volatile DatabaseConnection instance;
    private final Connection connection;
    
    private DatabaseConnection() {
        // Private constructor prevents instantiation
        try {
            this.connection = DriverManager.getConnection(
                "jdbc:postgresql://localhost:5432/app", "user", "password"
            );
        } catch (SQLException e) {
            throw new RuntimeException("Failed to create database connection", e);
        }
    }
    
    public static DatabaseConnection getInstance() {
        if (instance == null) {
            synchronized (DatabaseConnection.class) {
                if (instance == null) {
                    instance = new DatabaseConnection();
                }
            }
        }
        return instance;
    }
    
    public Connection getConnection() {
        return connection;
    }
}

// Enum-based Singleton (preferred approach)
public enum ConfigurationManager {
    INSTANCE;
    
    private final Properties properties;
    
    ConfigurationManager() {
        properties = new Properties();
        loadConfiguration();
    }
    
    private void loadConfiguration() {
        try (InputStream input = getClass().getResourceAsStream("/application.properties")) {
            properties.load(input);
        } catch (IOException e) {
            throw new RuntimeException("Failed to load configuration", e);
        }
    }
    
    public String getProperty(String key) {
        return properties.getProperty(key);
    }
    
    public String getProperty(String key, String defaultValue) {
        return properties.getProperty(key, defaultValue);
    }
}

Factory Pattern

The Factory pattern creates objects without specifying the exact class to create.

// Product interface
public interface PaymentProcessor {
    PaymentResult processPayment(BigDecimal amount, String currency);
    boolean supportsPaymentMethod(PaymentMethod method);
}

// Concrete implementations
public class CreditCardProcessor implements PaymentProcessor {
    
    @Override
    public PaymentResult processPayment(BigDecimal amount, String currency) {
        // Credit card processing logic
        System.out.println("Processing credit card payment: " + amount + " " + currency);
        return new PaymentResult(true, "CC-" + UUID.randomUUID().toString());
    }
    
    @Override
    public boolean supportsPaymentMethod(PaymentMethod method) {
        return method == PaymentMethod.CREDIT_CARD;
    }
}

public class PayPalProcessor implements PaymentProcessor {
    
    @Override
    public PaymentResult processPayment(BigDecimal amount, String currency) {
        // PayPal processing logic
        System.out.println("Processing PayPal payment: " + amount + " " + currency);
        return new PaymentResult(true, "PP-" + UUID.randomUUID().toString());
    }
    
    @Override
    public boolean supportsPaymentMethod(PaymentMethod method) {
        return method == PaymentMethod.PAYPAL;
    }
}

// Factory
public class PaymentProcessorFactory {
    
    private static final Map<PaymentMethod, Supplier<PaymentProcessor>> processors = Map.of(
        PaymentMethod.CREDIT_CARD, CreditCardProcessor::new,
        PaymentMethod.PAYPAL, PayPalProcessor::new,
        PaymentMethod.BANK_TRANSFER, BankTransferProcessor::new
    );
    
    public static PaymentProcessor createProcessor(PaymentMethod method) {
        Supplier<PaymentProcessor> supplier = processors.get(method);
        if (supplier == null) {
            throw new IllegalArgumentException("Unsupported payment method: " + method);
        }
        return supplier.get();
    }
    
    public static List<PaymentProcessor> getAllProcessors() {
        return processors.values().stream()
            .map(Supplier::get)
            .collect(Collectors.toList());
    }
}

// Usage
public class PaymentService {
    
    public PaymentResult processPayment(PaymentRequest request) {
        PaymentProcessor processor = PaymentProcessorFactory.createProcessor(request.getMethod());
        
        if (!processor.supportsPaymentMethod(request.getMethod())) {
            throw new UnsupportedPaymentMethodException("Payment method not supported");
        }
        
        return processor.processPayment(request.getAmount(), request.getCurrency());
    }
}

Builder Pattern

The Builder pattern constructs complex objects step by step.

public class User {
    
    private final String firstName;
    private final String lastName;
    private final String email;
    private final String phone;
    private final Address address;
    private final Set<Role> roles;
    private final LocalDateTime createdAt;
    private final boolean active;
    
    private User(Builder builder) {
        this.firstName = builder.firstName;
        this.lastName = builder.lastName;
        this.email = builder.email;
        this.phone = builder.phone;
        this.address = builder.address;
        this.roles = Collections.unmodifiableSet(new HashSet<>(builder.roles));
        this.createdAt = builder.createdAt != null ? builder.createdAt : LocalDateTime.now();
        this.active = builder.active;
    }
    
    // Getters only - immutable object
    public String getFirstName() { return firstName; }
    public String getLastName() { return lastName; }
    public String getEmail() { return email; }
    public String getPhone() { return phone; }
    public Address getAddress() { return address; }
    public Set<Role> getRoles() { return roles; }
    public LocalDateTime getCreatedAt() { return createdAt; }
    public boolean isActive() { return active; }
    
    public static class Builder {
        private String firstName;
        private String lastName;
        private String email;
        private String phone;
        private Address address;
        private Set<Role> roles = new HashSet<>();
        private LocalDateTime createdAt;
        private boolean active = true;
        
        public Builder firstName(String firstName) {
            this.firstName = firstName;
            return this;
        }
        
        public Builder lastName(String lastName) {
            this.lastName = lastName;
            return this;
        }
        
        public Builder email(String email) {
            this.email = email;
            return this;
        }
        
        public Builder phone(String phone) {
            this.phone = phone;
            return this;
        }
        
        public Builder address(Address address) {
            this.address = address;
            return this;
        }
        
        public Builder addRole(Role role) {
            this.roles.add(role);
            return this;
        }
        
        public Builder roles(Set<Role> roles) {
            this.roles = new HashSet<>(roles);
            return this;
        }
        
        public Builder createdAt(LocalDateTime createdAt) {
            this.createdAt = createdAt;
            return this;
        }
        
        public Builder inactive() {
            this.active = false;
            return this;
        }
        
        public User build() {
            validate();
            return new User(this);
        }
        
        private void validate() {
            if (firstName == null || firstName.trim().isEmpty()) {
                throw new IllegalArgumentException("First name is required");
            }
            if (lastName == null || lastName.trim().isEmpty()) {
                throw new IllegalArgumentException("Last name is required");
            }
            if (email == null || !email.contains("@")) {
                throw new IllegalArgumentException("Valid email is required");
            }
        }
    }
}

// Usage
User user = new User.Builder()
    .firstName("John")
    .lastName("Doe")
    .email("[email protected]")
    .phone("+1-555-123-4567")
    .address(new Address("123 Main St", "New York", "NY", "10001"))
    .addRole(Role.USER)
    .addRole(Role.PREMIUM)
    .build();

Behavioral Patterns

Observer Pattern

The Observer pattern defines a one-to-many dependency between objects.

// Subject interface
public interface Subject<T> {
    void addObserver(Observer<T> observer);
    void removeObserver(Observer<T> observer);
    void notifyObservers(T event);
}

// Observer interface
@FunctionalInterface
public interface Observer<T> {
    void update(T event);
}

// Event types
public abstract class OrderEvent {
    private final String orderId;
    private final LocalDateTime timestamp;
    
    protected OrderEvent(String orderId) {
        this.orderId = orderId;
        this.timestamp = LocalDateTime.now();
    }
    
    public String getOrderId() { return orderId; }
    public LocalDateTime getTimestamp() { return timestamp; }
}

public class OrderCreatedEvent extends OrderEvent {
    private final BigDecimal amount;
    private final String customerId;
    
    public OrderCreatedEvent(String orderId, BigDecimal amount, String customerId) {
        super(orderId);
        this.amount = amount;
        this.customerId = customerId;
    }
    
    public BigDecimal getAmount() { return amount; }
    public String getCustomerId() { return customerId; }
}

public class OrderShippedEvent extends OrderEvent {
    private final String trackingNumber;
    
    public OrderShippedEvent(String orderId, String trackingNumber) {
        super(orderId);
        this.trackingNumber = trackingNumber;
    }
    
    public String getTrackingNumber() { return trackingNumber; }
}

// Concrete subject
public class OrderService implements Subject<OrderEvent> {
    
    private final List<Observer<OrderEvent>> observers = new CopyOnWriteArrayList<>();
    private final OrderRepository orderRepository;
    
    public OrderService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }
    
    @Override
    public void addObserver(Observer<OrderEvent> observer) {
        observers.add(observer);
    }
    
    @Override
    public void removeObserver(Observer<OrderEvent> observer) {
        observers.remove(observer);
    }
    
    @Override
    public void notifyObservers(OrderEvent event) {
        observers.forEach(observer -> {
            try {
                observer.update(event);
            } catch (Exception e) {
                // Log error but don't let one observer failure affect others
                System.err.println("Observer failed to process event: " + e.getMessage());
            }
        });
    }
    
    public Order createOrder(CreateOrderRequest request) {
        Order order = new Order(request);
        Order saved = orderRepository.save(order);
        
        notifyObservers(new OrderCreatedEvent(
            saved.getId(), 
            saved.getTotal(), 
            saved.getCustomerId()
        ));
        
        return saved;
    }
    
    public void shipOrder(String orderId, String trackingNumber) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new OrderNotFoundException("Order not found: " + orderId));
        
        order.setStatus(OrderStatus.SHIPPED);
        order.setTrackingNumber(trackingNumber);
        orderRepository.save(order);
        
        notifyObservers(new OrderShippedEvent(orderId, trackingNumber));
    }
}

// Concrete observers
public class EmailNotificationService implements Observer<OrderEvent> {
    
    private final EmailService emailService;
    private final UserService userService;
    
    public EmailNotificationService(EmailService emailService, UserService userService) {
        this.emailService = emailService;
        this.userService = userService;
    }
    
    @Override
    public void update(OrderEvent event) {
        if (event instanceof OrderCreatedEvent) {
            handleOrderCreated((OrderCreatedEvent) event);
        } else if (event instanceof OrderShippedEvent) {
            handleOrderShipped((OrderShippedEvent) event);
        }
    }
    
    private void handleOrderCreated(OrderCreatedEvent event) {
        User customer = userService.findById(event.getCustomerId());
        emailService.sendOrderConfirmation(customer.getEmail(), event.getOrderId());
    }
    
    private void handleOrderShipped(OrderShippedEvent event) {
        // Send shipping notification
        emailService.sendShippingNotification(event.getOrderId(), event.getTrackingNumber());
    }
}

public class InventoryService implements Observer<OrderEvent> {
    
    private final InventoryRepository inventoryRepository;
    
    public InventoryService(InventoryRepository inventoryRepository) {
        this.inventoryRepository = inventoryRepository;
    }
    
    @Override
    public void update(OrderEvent event) {
        if (event instanceof OrderCreatedEvent) {
            handleOrderCreated((OrderCreatedEvent) event);
        }
    }
    
    private void handleOrderCreated(OrderCreatedEvent event) {
        // Update inventory levels
        // This would typically involve loading the order and updating stock
        System.out.println("Updating inventory for order: " + event.getOrderId());
    }
}

Strategy Pattern

The Strategy pattern defines a family of algorithms and makes them interchangeable.

// Strategy interface
public interface PricingStrategy {
    BigDecimal calculatePrice(Order order);
    String getStrategyName();
}

// Concrete strategies
public class RegularPricingStrategy implements PricingStrategy {
    
    @Override
    public BigDecimal calculatePrice(Order order) {
        return order.getItems().stream()
            .map(item -> item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())))
            .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
    
    @Override
    public String getStrategyName() {
        return "REGULAR";
    }
}

public class VIPPricingStrategy implements PricingStrategy {
    
    private static final BigDecimal VIP_DISCOUNT = new BigDecimal("0.15"); // 15% discount
    
    @Override
    public BigDecimal calculatePrice(Order order) {
        BigDecimal basePrice = order.getItems().stream()
            .map(item -> item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())))
            .reduce(BigDecimal.ZERO, BigDecimal::add);
        
        BigDecimal discount = basePrice.multiply(VIP_DISCOUNT);
        return basePrice.subtract(discount);
    }
    
    @Override
    public String getStrategyName() {
        return "VIP";
    }
}

public class BulkPricingStrategy implements PricingStrategy {
    
    private static final int BULK_THRESHOLD = 10;
    private static final BigDecimal BULK_DISCOUNT = new BigDecimal("0.10"); // 10% discount
    
    @Override
    public BigDecimal calculatePrice(Order order) {
        BigDecimal basePrice = order.getItems().stream()
            .map(item -> item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())))
            .reduce(BigDecimal.ZERO, BigDecimal::add);
        
        int totalItems = order.getItems().stream()
            .mapToInt(OrderItem::getQuantity)
            .sum();
        
        if (totalItems >= BULK_THRESHOLD) {
            BigDecimal discount = basePrice.multiply(BULK_DISCOUNT);
            return basePrice.subtract(discount);
        }
        
        return basePrice;
    }
    
    @Override
    public String getStrategyName() {
        return "BULK";
    }
}

// Context class
public class PricingService {
    
    private final Map<CustomerType, PricingStrategy> strategies;
    
    public PricingService() {
        strategies = Map.of(
            CustomerType.REGULAR, new RegularPricingStrategy(),
            CustomerType.VIP, new VIPPricingStrategy(),
            CustomerType.BULK, new BulkPricingStrategy()
        );
    }
    
    public BigDecimal calculateOrderPrice(Order order, CustomerType customerType) {
        PricingStrategy strategy = strategies.get(customerType);
        if (strategy == null) {
            throw new IllegalArgumentException("No pricing strategy for customer type: " + customerType);
        }
        
        BigDecimal price = strategy.calculatePrice(order);
        
        // Log the pricing strategy used
        System.out.println("Applied " + strategy.getStrategyName() + " pricing strategy");
        
        return price;
    }
    
    public void addStrategy(CustomerType customerType, PricingStrategy strategy) {
        strategies.put(customerType, strategy);
    }
}

Command Pattern

The Command pattern encapsulates a request as an object.

// Command interface
public interface Command {
    void execute();
    void undo();
    String getDescription();
}

// Receiver
public class Account {
    
    private String accountNumber;
    private BigDecimal balance;
    private List<Transaction> transactions = new ArrayList<>();
    
    public Account(String accountNumber, BigDecimal initialBalance) {
        this.accountNumber = accountNumber;
        this.balance = initialBalance;
    }
    
    public void credit(BigDecimal amount, String description) {
        balance = balance.add(amount);
        transactions.add(new Transaction(TransactionType.CREDIT, amount, description));
        System.out.println("Credited " + amount + " to account " + accountNumber);
    }
    
    public void debit(BigDecimal amount, String description) {
        if (balance.compareTo(amount) < 0) {
            throw new InsufficientFundsException("Insufficient funds");
        }
        balance = balance.subtract(amount);
        transactions.add(new Transaction(TransactionType.DEBIT, amount, description));
        System.out.println("Debited " + amount + " from account " + accountNumber);
    }
    
    public BigDecimal getBalance() { return balance; }
    public String getAccountNumber() { return accountNumber; }
    public List<Transaction> getTransactions() { return new ArrayList<>(transactions); }
}

// Concrete commands
public class CreditCommand implements Command {
    
    private final Account account;
    private final BigDecimal amount;
    private final String description;
    private boolean executed = false;
    
    public CreditCommand(Account account, BigDecimal amount, String description) {
        this.account = account;
        this.amount = amount;
        this.description = description;
    }
    
    @Override
    public void execute() {
        if (executed) {
            throw new IllegalStateException("Command already executed");
        }
        account.credit(amount, description);
        executed = true;
    }
    
    @Override
    public void undo() {
        if (!executed) {
            throw new IllegalStateException("Command not executed yet");
        }
        account.debit(amount, "Reversal: " + description);
        executed = false;
    }
    
    @Override
    public String getDescription() {
        return "Credit " + amount + " to account " + account.getAccountNumber();
    }
}

public class DebitCommand implements Command {
    
    private final Account account;
    private final BigDecimal amount;
    private final String description;
    private boolean executed = false;
    
    public DebitCommand(Account account, BigDecimal amount, String description) {
        this.account = account;
        this.amount = amount;
        this.description = description;
    }
    
    @Override
    public void execute() {
        if (executed) {
            throw new IllegalStateException("Command already executed");
        }
        account.debit(amount, description);
        executed = true;
    }
    
    @Override
    public void undo() {
        if (!executed) {
            throw new IllegalStateException("Command not executed yet");
        }
        account.credit(amount, "Reversal: " + description);
        executed = false;
    }
    
    @Override
    public String getDescription() {
        return "Debit " + amount + " from account " + account.getAccountNumber();
    }
}

// Invoker
public class BankingService {
    
    private final Stack<Command> commandHistory = new Stack<>();
    private final Stack<Command> undoHistory = new Stack<>();
    
    public void executeCommand(Command command) {
        try {
            command.execute();
            commandHistory.push(command);
            undoHistory.clear(); // Clear redo history when new command is executed
            System.out.println("Executed: " + command.getDescription());
        } catch (Exception e) {
            System.err.println("Failed to execute command: " + e.getMessage());
            throw e;
        }
    }
    
    public void undoLastCommand() {
        if (commandHistory.isEmpty()) {
            throw new IllegalStateException("No commands to undo");
        }
        
        Command lastCommand = commandHistory.pop();
        try {
            lastCommand.undo();
            undoHistory.push(lastCommand);
            System.out.println("Undone: " + lastCommand.getDescription());
        } catch (Exception e) {
            commandHistory.push(lastCommand); // Put it back if undo fails
            System.err.println("Failed to undo command: " + e.getMessage());
            throw e;
        }
    }
    
    public void redoLastCommand() {
        if (undoHistory.isEmpty()) {
            throw new IllegalStateException("No commands to redo");
        }
        
        Command commandToRedo = undoHistory.pop();
        executeCommand(commandToRedo);
    }
    
    public List<String> getCommandHistory() {
        return commandHistory.stream()
            .map(Command::getDescription)
            .collect(Collectors.toList());
    }
}

Structural Patterns

Adapter Pattern

The Adapter pattern allows incompatible interfaces to work together.

// Target interface (what client expects)
public interface NotificationService {
    void sendNotification(String recipient, String subject, String message);
    boolean isSupported(String recipient);
}

// Adaptee (existing class with incompatible interface)
public class LegacyEmailService {
    
    public void sendEmail(String to, String from, String subject, String body) {
        System.out.println("Legacy email sent to: " + to);
        System.out.println("Subject: " + subject);
        System.out.println("Body: " + body);
    }
    
    public boolean isValidEmail(String email) {
        return email != null && email.contains("@");
    }
}

public class SlackApiClient {
    
    public void postMessage(String channel, String text) {
        System.out.println("Slack message posted to " + channel + ": " + text);
    }
    
    public boolean isValidChannel(String channel) {
        return channel != null && channel.startsWith("#");
    }
}

// Adapters
public class EmailNotificationAdapter implements NotificationService {
    
    private final LegacyEmailService emailService;
    private final String fromAddress;
    
    public EmailNotificationAdapter(LegacyEmailService emailService, String fromAddress) {
        this.emailService = emailService;
        this.fromAddress = fromAddress;
    }
    
    @Override
    public void sendNotification(String recipient, String subject, String message) {
        emailService.sendEmail(recipient, fromAddress, subject, message);
    }
    
    @Override
    public boolean isSupported(String recipient) {
        return emailService.isValidEmail(recipient);
    }
}

public class SlackNotificationAdapter implements NotificationService {
    
    private final SlackApiClient slackClient;
    
    public SlackNotificationAdapter(SlackApiClient slackClient) {
        this.slackClient = slackClient;
    }
    
    @Override
    public void sendNotification(String recipient, String subject, String message) {
        String formattedMessage = subject + "\n" + message;
        slackClient.postMessage(recipient, formattedMessage);
    }
    
    @Override
    public boolean isSupported(String recipient) {
        return slackClient.isValidChannel(recipient);
    }
}

// Client code
public class NotificationManager {
    
    private final List<NotificationService> notificationServices;
    
    public NotificationManager(List<NotificationService> notificationServices) {
        this.notificationServices = notificationServices;
    }
    
    public void sendNotification(String recipient, String subject, String message) {
        NotificationService service = notificationServices.stream()
            .filter(s -> s.isSupported(recipient))
            .findFirst()
            .orElseThrow(() -> new UnsupportedOperationException(
                "No notification service supports recipient: " + recipient));
        
        service.sendNotification(recipient, subject, message);
    }
    
    public void broadcastNotification(List<String> recipients, String subject, String message) {
        recipients.forEach(recipient -> {
            try {
                sendNotification(recipient, subject, message);
            } catch (Exception e) {
                System.err.println("Failed to send notification to " + recipient + ": " + e.getMessage());
            }
        });
    }
}

Decorator Pattern

The Decorator pattern adds behavior to objects dynamically.

// Component interface
public interface DataProcessor {
    String process(String data);
}

// Concrete component
public class BasicDataProcessor implements DataProcessor {
    
    @Override
    public String process(String data) {
        return data;
    }
}

// Base decorator
public abstract class DataProcessorDecorator implements DataProcessor {
    
    protected final DataProcessor processor;
    
    public DataProcessorDecorator(DataProcessor processor) {
        this.processor = processor;
    }
    
    @Override
    public String process(String data) {
        return processor.process(data);
    }
}

// Concrete decorators
public class EncryptionDecorator extends DataProcessorDecorator {
    
    private final String algorithm;
    
    public EncryptionDecorator(DataProcessor processor, String algorithm) {
        super(processor);
        this.algorithm = algorithm;
    }
    
    @Override
    public String process(String data) {
        String processedData = super.process(data);
        return encrypt(processedData);
    }
    
    private String encrypt(String data) {
        // Simplified encryption (in reality, use proper encryption)
        String encrypted = Base64.getEncoder().encodeToString(data.getBytes());
        System.out.println("Data encrypted using " + algorithm);
        return encrypted;
    }
}

public class CompressionDecorator extends DataProcessorDecorator {
    
    public CompressionDecorator(DataProcessor processor) {
        super(processor);
    }
    
    @Override
    public String process(String data) {
        String processedData = super.process(data);
        return compress(processedData);
    }
    
    private String compress(String data) {
        // Simplified compression
        String compressed = data.replaceAll("\\s+", " ").trim();
        System.out.println("Data compressed");
        return compressed;
    }
}

public class LoggingDecorator extends DataProcessorDecorator {
    
    private final String logLevel;
    
    public LoggingDecorator(DataProcessor processor, String logLevel) {
        super(processor);
        this.logLevel = logLevel;
    }
    
    @Override
    public String process(String data) {
        System.out.println("[" + logLevel + "] Processing data: " + data.substring(0, Math.min(50, data.length())) + "...");
        
        long startTime = System.currentTimeMillis();
        String result = super.process(data);
        long endTime = System.currentTimeMillis();
        
        System.out.println("[" + logLevel + "] Processing completed in " + (endTime - startTime) + "ms");
        return result;
    }
}

// Usage
public class DataProcessingService {
    
    public void processUserData(String userData) {
        // Create a decorated processor pipeline
        DataProcessor processor = new LoggingDecorator(
            new EncryptionDecorator(
                new CompressionDecorator(
                    new BasicDataProcessor()
                ), "AES"
            ), "INFO"
        );
        
        String processedData = processor.process(userData);
        System.out.println("Final processed data: " + processedData);
    }
}

Enterprise Patterns

Repository Pattern

The Repository pattern encapsulates data access logic.

// Generic repository interface
public interface Repository<T, ID> {
    Optional<T> findById(ID id);
    List<T> findAll();
    T save(T entity);
    void deleteById(ID id);
    boolean existsById(ID id);
}

// Specific repository interface
public interface UserRepository extends Repository<User, Long> {
    Optional<User> findByEmail(String email);
    List<User> findByRole(Role role);
    List<User> findActiveUsers();
    Page<User> findAll(Pageable pageable);
}

// Implementation
public class JpaUserRepository implements UserRepository {
    
    private final EntityManager entityManager;
    
    public JpaUserRepository(EntityManager entityManager) {
        this.entityManager = entityManager;
    }
    
    @Override
    public Optional<User> findById(Long id) {
        User user = entityManager.find(User.class, id);
        return Optional.ofNullable(user);
    }
    
    @Override
    public List<User> findAll() {
        TypedQuery<User> query = entityManager.createQuery("SELECT u FROM User u", User.class);
        return query.getResultList();
    }
    
    @Override
    public User save(User user) {
        if (user.getId() == null) {
            entityManager.persist(user);
            return user;
        } else {
            return entityManager.merge(user);
        }
    }
    
    @Override
    public void deleteById(Long id) {
        User user = entityManager.find(User.class, id);
        if (user != null) {
            entityManager.remove(user);
        }
    }
    
    @Override
    public boolean existsById(Long id) {
        return findById(id).isPresent();
    }
    
    @Override
    public Optional<User> findByEmail(String email) {
        TypedQuery<User> query = entityManager.createQuery(
            "SELECT u FROM User u WHERE u.email = :email", User.class);
        query.setParameter("email", email);
        
        try {
            return Optional.of(query.getSingleResult());
        } catch (NoResultException e) {
            return Optional.empty();
        }
    }
    
    @Override
    public List<User> findByRole(Role role) {
        TypedQuery<User> query = entityManager.createQuery(
            "SELECT u FROM User u WHERE :role MEMBER OF u.roles", User.class);
        query.setParameter("role", role);
        return query.getResultList();
    }
    
    @Override
    public List<User> findActiveUsers() {
        TypedQuery<User> query = entityManager.createQuery(
            "SELECT u FROM User u WHERE u.active = true", User.class);
        return query.getResultList();
    }
    
    @Override
    public Page<User> findAll(Pageable pageable) {
        // Implementation for pagination
        TypedQuery<User> query = entityManager.createQuery("SELECT u FROM User u", User.class);
        query.setFirstResult(pageable.getPageNumber() * pageable.getPageSize());
        query.setMaxResults(pageable.getPageSize());
        
        List<User> users = query.getResultList();
        
        TypedQuery<Long> countQuery = entityManager.createQuery("SELECT COUNT(u) FROM User u", Long.class);
        Long total = countQuery.getSingleResult();
        
        return new PageImpl<>(users, pageable, total);
    }
}

Service Layer Pattern

The Service Layer pattern defines application boundaries and encapsulates business logic.

@Service
@Transactional
public class UserService {
    
    private final UserRepository userRepository;
    private final EmailService emailService;
    private final PasswordEncoder passwordEncoder;
    private final AuditService auditService;
    
    public UserService(UserRepository userRepository, 
                      EmailService emailService,
                      PasswordEncoder passwordEncoder,
                      AuditService auditService) {
        this.userRepository = userRepository;
        this.emailService = emailService;
        this.passwordEncoder = passwordEncoder;
        this.auditService = auditService;
    }
    
    public User createUser(CreateUserRequest request) {
        validateCreateUserRequest(request);
        
        if (userRepository.findByEmail(request.getEmail()).isPresent()) {
            throw new UserAlreadyExistsException("User with email already exists: " + request.getEmail());
        }
        
        User user = new User.Builder()
            .firstName(request.getFirstName())
            .lastName(request.getLastName())
            .email(request.getEmail())
            .addRole(Role.USER)
            .build();
        
        String encodedPassword = passwordEncoder.encode(request.getPassword());
        user.setPassword(encodedPassword);
        
        User savedUser = userRepository.save(user);
        
        // Send welcome email asynchronously
        emailService.sendWelcomeEmailAsync(savedUser.getEmail(), savedUser.getFirstName());
        
        // Audit the user creation
        auditService.logUserCreation(savedUser.getId(), savedUser.getEmail());
        
        return savedUser;
    }
    
    @Transactional(readOnly = true)
    public User getUserById(Long id) {
        return userRepository.findById(id)
            .orElseThrow(() -> new UserNotFoundException("User not found with id: " + id));
    }
    
    @Transactional(readOnly = true)
    public Page<User> getUsers(Pageable pageable) {
        return userRepository.findAll(pageable);
    }
    
    public User updateUser(Long id, UpdateUserRequest request) {
        User user = getUserById(id);
        
        validateUpdateUserRequest(request);
        
        // Check if email is being changed and if new email already exists
        if (!user.getEmail().equals(request.getEmail())) {
            if (userRepository.findByEmail(request.getEmail()).isPresent()) {
                throw new EmailAlreadyExistsException("Email already in use: " + request.getEmail());
            }
        }
        
        user.setFirstName(request.getFirstName());
        user.setLastName(request.getLastName());
        user.setEmail(request.getEmail());
        user.setPhone(request.getPhone());
        
        User updatedUser = userRepository.save(user);
        
        auditService.logUserUpdate(user.getId(), "User profile updated");
        
        return updatedUser;
    }
    
    public void deleteUser(Long id) {
        User user = getUserById(id);
        
        // Soft delete - just mark as inactive
        user.setActive(false);
        userRepository.save(user);
        
        auditService.logUserDeletion(user.getId(), "User deactivated");
    }
    
    public void activateUser(Long id) {
        User user = getUserById(id);
        user.setActive(true);
        userRepository.save(user);
        
        auditService.logUserUpdate(user.getId(), "User activated");
    }
    
    private void validateCreateUserRequest(CreateUserRequest request) {
        if (request.getFirstName() == null || request.getFirstName().trim().isEmpty()) {
            throw new ValidationException("First name is required");
        }
        if (request.getLastName() == null || request.getLastName().trim().isEmpty()) {
            throw new ValidationException("Last name is required");
        }
        if (request.getEmail() == null || !EmailValidator.isValid(request.getEmail())) {
            throw new ValidationException("Valid email is required");
        }
        if (request.getPassword() == null || request.getPassword().length() < 8) {
            throw new ValidationException("Password must be at least 8 characters");
        }
    }
    
    private void validateUpdateUserRequest(UpdateUserRequest request) {
        if (request.getFirstName() == null || request.getFirstName().trim().isEmpty()) {
            throw new ValidationException("First name is required");
        }
        if (request.getLastName() == null || request.getLastName().trim().isEmpty()) {
            throw new ValidationException("Last name is required");
        }
        if (request.getEmail() == null || !EmailValidator.isValid(request.getEmail())) {
            throw new ValidationException("Valid email is required");
        }
    }
}

Summary

Enterprise design patterns provide proven solutions for common software design challenges:

Creational Patterns:

  • Singleton: Single instance access
  • Factory: Object creation without specifying exact classes
  • Builder: Step-by-step complex object construction

Behavioral Patterns:

  • Observer: One-to-many dependency notifications
  • Strategy: Interchangeable algorithms
  • Command: Encapsulating requests as objects

Structural Patterns:

  • Adapter: Making incompatible interfaces work together
  • Decorator: Adding behavior dynamically

Enterprise Patterns:

  • Repository: Data access abstraction
  • Service Layer: Business logic encapsulation

Benefits:

  • Reusability: Proven solutions to common problems
  • Maintainability: Well-structured, understandable code
  • Flexibility: Easy to extend and modify
  • Communication: Common vocabulary for developers

These patterns form the foundation of enterprise Java applications, providing structure, maintainability, and scalability.