Welcome back! Yesterday we covered the fundamentals of clean code, SOLID principles, and domain-driven design. Today, we're diving deeper into advanced concepts that separate senior developers from the rest: architectural patterns, performance considerations, and handling the messy realities of production systems.
Architectural Patterns for Clean Code
Hexagonal Architecture (Ports and Adapters)
Hexagonal architecture isolates your business logic from external concerns, making your code more testable and adaptable:
// Domain layer - Pure business logic
public class LoanApplicationService {
private final CreditScoreProvider creditScoreProvider;
private final RiskAssessmentEngine riskEngine;
private final LoanRepository loanRepository;
public LoanApplicationService(CreditScoreProvider creditScoreProvider,
RiskAssessmentEngine riskEngine,
LoanRepository loanRepository) {
this.creditScoreProvider = creditScoreProvider;
this.riskEngine = riskEngine;
this.loanRepository = loanRepository;
}
public LoanDecision processApplication(LoanApplication application) {
// Business logic remains pure
CreditScore creditScore = creditScoreProvider.getCreditScore(
application.getApplicantId()
);
RiskProfile riskProfile = riskEngine.assessRisk(
application, creditScore
);
LoanDecision decision = evaluateApplication(application, riskProfile);
if (decision.isApproved()) {
Loan loan = createLoan(application, decision);
loanRepository.save(loan);
}
return decision;
}
private LoanDecision evaluateApplication(LoanApplication application,
RiskProfile riskProfile) {
// Complex business rules here
if (riskProfile.getScore() < MINIMUM_ACCEPTABLE_RISK) {
return LoanDecision.rejected("High risk profile");
}
if (application.getLoanAmount().isGreaterThan(
calculateMaxLoanAmount(application, riskProfile))) {
return LoanDecision.rejected("Loan amount exceeds maximum allowable");
}
return LoanDecision.approved(calculateInterestRate(riskProfile));
}
}
// Infrastructure layer - External adapters
@Component
public class ExternalCreditScoreProvider implements CreditScoreProvider {
private final CreditBureauClient creditBureauClient;
private final CacheManager cacheManager;
@Override
public CreditScore getCreditScore(ApplicantId applicantId) {
return cacheManager.get(applicantId.toString(), CreditScore.class)
.orElseGet(() -> fetchAndCacheCreditScore(applicantId));
}
private CreditScore fetchAndCacheCreditScore(ApplicantId applicantId) {
CreditReportResponse response = creditBureauClient.getCreditReport(
applicantId.toString()
);
CreditScore score = mapToCreditScore(response);
cacheManager.put(applicantId.toString(), score, Duration.ofHours(24));
return score;
}
}
Event-Driven Architecture
Handle complex business processes with events:
// Domain Events
public abstract class DomainEvent {
private final UUID eventId;
private final Instant occurredAt;
private final String aggregateId;
protected DomainEvent(String aggregateId) {
this.eventId = UUID.randomUUID();
this.occurredAt = Instant.now();
this.aggregateId = aggregateId;
}
}
public class OrderPlacedEvent extends DomainEvent {
private final CustomerId customerId;
private final Money totalAmount;
private final List<ProductId> productIds;
public OrderPlacedEvent(OrderId orderId,
CustomerId customerId,
Money totalAmount,
List<ProductId> productIds) {
super(orderId.toString());
this.customerId = customerId;
this.totalAmount = totalAmount;
this.productIds = productIds;
}
}
// Aggregate Root with Event Publishing
public class Order extends AggregateRoot {
private OrderId orderId;
private CustomerId customerId;
private OrderStatus status;
private List<OrderLine> orderLines;
private Money totalAmount;
public void placeOrder() {
if (this.status != OrderStatus.DRAFT) {
throw new IllegalOrderStateException(
"Order can only be placed from DRAFT status"
);
}
validateOrderCanBePlaced();
this.status = OrderStatus.PLACED;
// Raise domain event
addDomainEvent(new OrderPlacedEvent(
this.orderId,
this.customerId,
this.totalAmount,
extractProductIds()
));
}
private void validateOrderCanBePlaced() {
if (orderLines.isEmpty()) {
throw new EmptyOrderException("Cannot place empty order");
}
if (totalAmount.isZeroOrNegative()) {
throw new InvalidOrderAmountException("Order amount must be positive");
}
}
}
// Event Handlers
@Component
public class OrderEventHandler {
private final InventoryService inventoryService;
private final PaymentService paymentService;
private final NotificationService notificationService;
@EventHandler
public void handle(OrderPlacedEvent event) {
reserveInventory(event);
initiatePaymentProcessing(event);
sendOrderConfirmation(event);
}
private void reserveInventory(OrderPlacedEvent event) {
try {
inventoryService.reserveItems(event.getProductIds());
} catch (InsufficientInventoryException e) {
// Publish compensation event
publishEvent(new OrderInventoryReservationFailedEvent(
event.getAggregateId(), e.getMessage()
));
}
}
}
Performance Considerations Without Sacrificing Clean Code
Lazy Loading with Clean Interfaces
public class CustomerProfile {
private final CustomerId customerId;
private final CustomerRepository customerRepository;
private final Supplier<List<Order>> ordersSupplier;
private final Supplier<PaymentHistory> paymentHistorySupplier;
public CustomerProfile(CustomerId customerId,
CustomerRepository customerRepository) {
this.customerId = customerId;
this.customerRepository = customerRepository;
// Lazy initialization
this.ordersSupplier = Suppliers.memoize(
() -> customerRepository.findOrdersByCustomerId(customerId)
);
this.paymentHistorySupplier = Suppliers.memoize(
() -> customerRepository.findPaymentHistoryByCustomerId(customerId)
);
}
public List<Order> getRecentOrders() {
return ordersSupplier.get().stream()
.filter(order -> order.wasPlacedAfter(Instant.now().minus(30, ChronoUnit.DAYS)))
.collect(Collectors.toList());
}
public CustomerCreditRating calculateCreditRating() {
PaymentHistory history = paymentHistorySupplier.get();
List<Order> orders = ordersSupplier.get();
return CreditRatingCalculator.calculate(history, orders);
}
}
Caching Strategies
@Service
public class ProductCatalogService {
private final ProductRepository productRepository;
private final CacheManager cacheManager;
private final ProductPricingService pricingService;
@Cacheable(value = "products", key = "#productId")
public Product getProduct(ProductId productId) {
return productRepository.findById(productId)
.orElseThrow(() -> new ProductNotFoundException(productId));
}
@Cacheable(value = "product-prices", key = "#productId + '_' + #customerType")
public Money getProductPrice(ProductId productId, CustomerType customerType) {
Product product = getProduct(productId);
return pricingService.calculatePrice(product, customerType);
}
@CacheEvict(value = {"products", "product-prices"}, key = "#productId")
public void updateProduct(ProductId productId, ProductUpdateRequest request) {
Product product = getProduct(productId);
product.updateDetails(request);
productRepository.save(product);
// Publish event for other services
eventPublisher.publish(new ProductUpdatedEvent(productId, request));
}
// Cache warming strategy
@EventListener
public void handleProductCreated(ProductCreatedEvent event) {
// Pre-warm cache for common customer types
Arrays.stream(CustomerType.values())
.forEach(customerType ->
getProductPrice(event.getProductId(), customerType)
);
}
}
Batch Processing with Clean Separation
public class OrderBatchProcessor {
private final OrderRepository orderRepository;
private final PaymentProcessor paymentProcessor;
private final NotificationService notificationService;
private final ExecutorService executorService;
public BatchProcessingResult processOrderBatch(List<OrderId> orderIds) {
BatchProcessingResult result = new BatchProcessingResult();
// Process in chunks to manage memory
List<List<OrderId>> chunks = Lists.partition(orderIds, BATCH_SIZE);
List<CompletableFuture<ChunkResult>> futures = chunks.stream()
.map(chunk -> CompletableFuture.supplyAsync(
() -> processChunk(chunk), executorService
))
.collect(Collectors.toList());
// Wait for all chunks to complete
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.join();
// Aggregate results
futures.stream()
.map(CompletableFuture::join)
.forEach(result::merge);
return result;
}
private ChunkResult processChunk(List<OrderId> orderIds) {
ChunkResult chunkResult = new ChunkResult();
List<Order> orders = orderRepository.findByIds(orderIds);
for (Order order : orders) {
try {
processOrderSafely(order);
chunkResult.addSuccess(order.getId());
} catch (Exception e) {
chunkResult.addFailure(order.getId(), e);
logger.error("Failed to process order: {}", order.getId(), e);
}
}
return chunkResult;
}
private void processOrderSafely(Order order) {
// Process with proper error handling and retries
RetryTemplate.builder()
.maxAttempts(3)
.exponentialBackoff(Duration.ofSeconds(1), 2, Duration.ofSeconds(10))
.retryOn(TransientException.class)
.build()
.execute(context -> {
paymentProcessor.processPayment(order.getPaymentDetails());
order.markAsProcessed();
orderRepository.save(order);
return null;
});
}
}
Handling Legacy Code and Technical Debt
Strangler Fig Pattern
Gradually replace legacy systems:
// Legacy system interface
public interface LegacyOrderService {
void processOrder(Map<String, Object> orderData);
}
// New system interface
public interface ModernOrderService {
OrderResult processOrder(OrderRequest request);
}
// Facade that gradually shifts traffic
@Service
public class OrderProcessingFacade {
private final LegacyOrderService legacyService;
private final ModernOrderService modernService;
private final FeatureToggleService featureToggleService;
private final OrderDataMapper mapper;
public OrderResult processOrder(OrderRequest request) {
// Feature toggle determines which system to use
if (shouldUseLegacySystem(request)) {
return processWithLegacySystem(request);
} else {
return processWithModernSystem(request);
}
}
private boolean shouldUseLegacySystem(OrderRequest request) {
// Gradual rollout strategy
if (!featureToggleService.isEnabled("modern-order-processing")) {
return true;
}
// Use legacy for complex orders during transition
if (request.hasComplexPricingRules()) {
return true;
}
// Percentage-based rollout
return request.getCustomerId().hashCode() % 100
>= featureToggleService.getRolloutPercentage("modern-order-processing");
}
private OrderResult processWithLegacySystem(OrderRequest request) {
try {
Map<String, Object> legacyData = mapper.toLegacyFormat(request);
legacyService.processOrder(legacyData);
// Bridge the gap - convert legacy response to modern format
return OrderResult.success(extractOrderIdFromLegacyResponse(legacyData));
} catch (Exception e) {
logger.error("Legacy order processing failed, falling back to modern", e);
return processWithModernSystem(request);
}
}
}
Anti-Corruption Layer
Protect your clean domain from messy external systems:
// External system's messy data structure
public class ExternalCustomerData {
public String cust_id;
public String f_name;
public String l_name;
public String email_addr;
public String phone_num;
public int status_code; // 0=inactive, 1=active, 2=suspended
public String created_dt; // "MM/dd/yyyy" format
// ... 50+ more poorly named fields
}
// Your clean domain model
public class Customer {
private final CustomerId customerId;
private final PersonName name;
private final EmailAddress email;
private final PhoneNumber phone;
private final CustomerStatus status;
private final LocalDate registrationDate;
// Rich domain behavior
public boolean canPlaceOrder() {
return status == CustomerStatus.ACTIVE;
}
public boolean isEligibleForPremiumFeatures() {
return status == CustomerStatus.ACTIVE
&& registrationDate.isBefore(LocalDate.now().minusMonths(6));
}
}
// Anti-corruption layer
@Component
public class CustomerDataTranslator {
private final DateTimeFormatter legacyDateFormat =
DateTimeFormatter.ofPattern("MM/dd/yyyy");
public Customer translateToCustomer(ExternalCustomerData externalData) {
validateExternalData(externalData);
return Customer.builder()
.customerId(CustomerId.of(externalData.cust_id))
.name(PersonName.of(externalData.f_name, externalData.l_name))
.email(EmailAddress.of(normalizeEmail(externalData.email_addr)))
.phone(PhoneNumber.of(normalizePhone(externalData.phone_num)))
.status(translateStatus(externalData.status_code))
.registrationDate(parseDate(externalData.created_dt))
.build();
}
private CustomerStatus translateStatus(int statusCode) {
return switch (statusCode) {
case 0 -> CustomerStatus.INACTIVE;
case 1 -> CustomerStatus.ACTIVE;
case 2 -> CustomerStatus.SUSPENDED;
default -> throw new IllegalArgumentException(
"Unknown status code: " + statusCode
);
};
}
private String normalizeEmail(String email) {
return Optional.ofNullable(email)
.map(String::trim)
.map(String::toLowerCase)
.filter(e -> e.contains("@"))
.orElseThrow(() -> new InvalidEmailException("Invalid email: " + email));
}
private void validateExternalData(ExternalCustomerData data) {
List<String> errors = new ArrayList<>();
if (StringUtils.isBlank(data.cust_id)) {
errors.add("Customer ID is required");
}
if (StringUtils.isBlank(data.email_addr)) {
errors.add("Email address is required");
}
if (!errors.isEmpty()) {
throw new InvalidExternalDataException(
"External customer data validation failed: " +
String.join(", ", errors)
);
}
}
}
Advanced Error Handling Strategies
Result Pattern for Better Error Handling
public abstract class Result<T, E extends Exception> {
public abstract boolean isSuccess();
public abstract boolean isFailure();
public abstract T getValue();
public abstract E getError();
public static <T, E extends Exception> Result<T, E> success(T value) {
return new Success<>(value);
}
public static <T, E extends Exception> Result<T, E> failure(E error) {
return new Failure<>(error);
}
public <U> Result<U, E> map(Function<T, U> mapper) {
if (isSuccess()) {
return success(mapper.apply(getValue()));
} else {
return failure(getError());
}
}
public <U> Result<U, E> flatMap(Function<T, Result<U, E>> mapper) {
if (isSuccess()) {
return mapper.apply(getValue());
} else {
return failure(getError());
}
}
}
// Usage in service layer
@Service
public class PaymentProcessingService {
public Result<PaymentConfirmation, PaymentException> processPayment(
PaymentRequest request) {
return validatePaymentRequest(request)
.flatMap(this::checkAvailableFunds)
.flatMap(this::processWithPaymentGateway)
.map(this::createPaymentConfirmation);
}
private Result<PaymentRequest, PaymentException> validatePaymentRequest(
PaymentRequest request) {
if (request.getAmount().isZeroOrNegative()) {
return Result.failure(new InvalidPaymentAmountException(
"Payment amount must be positive"
));
}
if (!request.getPaymentMethod().isValid()) {
return Result.failure(new InvalidPaymentMethodException(
"Payment method is invalid or expired"
));
}
return Result.success(request);
}
private Result<PaymentRequest, PaymentException> checkAvailableFunds(
PaymentRequest request) {
try {
FundsAvailability availability = fundsChecker.checkFunds(
request.getPaymentMethod(),
request.getAmount()
);
if (!availability.isAvailable()) {
return Result.failure(new InsufficientFundsException(
"Insufficient funds available"
));
}
return Result.success(request);
} catch (FundsCheckException e) {
return Result.failure(new PaymentProcessingException(
"Unable to verify funds availability", e
));
}
}
}
Circuit Breaker with Fallback Strategies
@Component
public class ExternalServiceClient {
private final CircuitBreaker circuitBreaker;
private final CacheManager cacheManager;
private final MetricRegistry metricRegistry;
public Optional<CustomerData> getCustomerData(CustomerId customerId) {
Timer.Context timerContext = metricRegistry.timer("external-service.calls")
.time();
try {
return circuitBreaker.executeSupplier(() -> {
CustomerData data = externalServiceClient.getCustomer(
customerId.toString()
);
// Cache successful responses
cacheManager.put(customerId.toString(), data, Duration.ofMinutes(15));
return Optional.of(data);
});
} catch (CircuitBreakerOpenException e) {
logger.warn("Circuit breaker open for customer data service");
metricRegistry.counter("external-service.circuit-breaker.open").inc();
// Fallback to cached data
return getCachedCustomerData(customerId);
} catch (Exception e) {
logger.error("Failed to fetch customer data", e);
metricRegistry.counter("external-service.errors").inc();
// Fallback strategies
return getCachedCustomerData(customerId)
.or(() -> getDefaultCustomerData(customerId));
} finally {
timerContext.stop();
}
}
private Optional<CustomerData> getCachedCustomerData(CustomerId customerId) {
return Optional.ofNullable(
cacheManager.get(customerId.toString(), CustomerData.class)
);
}
private Optional<CustomerData> getDefaultCustomerData(CustomerId customerId) {
// Return minimal customer data to keep system functional
return Optional.of(CustomerData.minimal(customerId));
}
}
Database Design and Clean Code
Specification Pattern for Complex Queries
public interface Specification<T> {
boolean isSatisfiedBy(T candidate);
Specification<T> and(Specification<T> other);
Specification<T> or(Specification<T> other);
Specification<T> not();
}
public class CustomerSpecifications {
public static Specification<Customer> isActive() {
return customer -> customer.getStatus() == CustomerStatus.ACTIVE;
}
public static Specification<Customer> hasOrdersInLastMonths(int months) {
LocalDate cutoffDate = LocalDate.now().minusMonths(months);
return customer -> customer.getOrders().stream()
.anyMatch(order -> order.getOrderDate().isAfter(cutoffDate));
}
public static Specification<Customer> hasTotalOrderValueGreaterThan(Money amount) {
return customer -> customer.getTotalOrderValue().isGreaterThan(amount);
}
public static Specification<Customer> isEligibleForPremiumProgram() {
return isActive()
.and(hasOrdersInLastMonths(6))
.and(hasTotalOrderValueGreaterThan(Money.of(1000)));
}
}
// Usage in repository
@Repository
public class JpaCustomerRepository implements CustomerRepository {
public List<Customer> findCustomersMatching(Specification<Customer> specification) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Customer> query = cb.createQuery(Customer.class);
Root<Customer> root = query.from(Customer.class);
Predicate predicate = buildPredicate(specification, cb, root);
query.where(predicate);
return entityManager.createQuery(query).getResultList();
}
// Convert specification to JPA criteria
private Predicate buildPredicate(Specification<Customer> specification,
CriteriaBuilder cb,
Root<Customer> root) {
// Implementation depends on your specification framework
return SpecificationToCriteriaConverter.convert(specification, cb, root);
}
}
Monitoring and Observability in Clean Code
Custom Metrics and Health Checks
@Component
public class BusinessMetricsCollector {
private final MeterRegistry meterRegistry;
private final Counter ordersProcessedCounter;
private final Timer orderProcessingTimer;
private final Gauge activeCustomersGauge;
public BusinessMetricsCollector(MeterRegistry meterRegistry,
CustomerService customerService) {
this.meterRegistry = meterRegistry;
this.ordersProcessedCounter = Counter.builder("orders.processed")
.description("Number of orders processed")
.tag("status", "success")
.register(meterRegistry);
this.orderProcessingTimer = Timer.builder("orders.processing.time")
.description("Time taken to process orders")
.register(meterRegistry);
this.activeCustomersGauge = Gauge.builder("customers.active.count")
.description("Number of active customers")
.register(meterRegistry, this, metrics -> customerService.getActiveCustomerCount());
}
public void recordOrderProcessed(OrderProcessingResult result) {
ordersProcessedCounter.increment(
Tags.of(
"status", result.isSuccess() ? "success" : "failure",
"order_type", result.getOrderType().toString()
)
);
}
public void recordOrderProcessingTime(Duration processingTime, OrderType orderType) {
orderProcessingTimer.record(processingTime,
Tags.of("order_type", orderType.toString())
);
}
}
// Health checks
@Component
public class OrderServiceHealthIndicator implements HealthIndicator {
private final OrderRepository orderRepository;
private final PaymentService paymentService;
@Override
public Health health() {
Health.Builder healthBuilder = Health.up();
try {
// Check database connectivity
long pendingOrdersCount = orderRepository.countPendingOrders();
healthBuilder.withDetail("pending_orders", pendingOrdersCount);
if (pendingOrdersCount > 1000) {
healthBuilder.status("DEGRADED")
.withDetail("reason", "High number of pending orders");
}
// Check external service connectivity
if (!paymentService.isHealthy()) {
healthBuilder.down()
.withDetail("payment_service", "Payment service is unavailable");
}
} catch (Exception e) {
healthBuilder.down()
.withDetail("error", e.getMessage());
}
return healthBuilder.build();
}
}
Key Principles for Advanced Clean Code
1. Embrace Composition Over Inheritance
// Instead of deep inheritance hierarchies
public class PremiumCustomerService extends CustomerService {
// Inherits all baggage from CustomerService
}
// Use composition
public class PremiumCustomerService {
private final CustomerService customerService;
private final LoyaltyProgramService loyaltyService;
private final DiscountCalculator discountCalculator;
public CustomerAccount createPremiumAccount(CustomerRequest request) {
CustomerAccount account = customerService.createAccount(request);
loyaltyService.enrollInPremiumProgram(account.getId());
return account;
}
public Money calculateDiscountedPrice(ProductId productId, CustomerId customerId) {
Money basePrice = customerService.getProductPrice(productId);
DiscountRate discount = loyaltyService.getDiscountRate(customerId);
return discountCalculator.apply(basePrice, discount);
}
}
2. Make Impossible States Impossible
// Bad: Order can be in inconsistent state
public class Order {
private OrderStatus status;
private PaymentStatus paymentStatus;
private ShippingStatus shippingStatus;
// Can have COMPLETED order with PENDING payment!
}
// Good: Use state machines or sealed classes
public abstract class OrderState {
public abstract OrderState placeOrder();
public abstract OrderState confirmPayment();
public abstract OrderState shipOrder();
public abstract OrderState deliverOrder();
protected OrderState invalidTransition(String action) {
throw new IllegalStateTransitionException(
String.format("Cannot %s order in %s state",
action, this.getClass().getSimpleName())
);
}
}
public class DraftOrderState extends OrderState {
@Override
public OrderState placeOrder() {
return new PlacedOrderState();
}
@Override
public OrderState confirmPayment() {
return invalidTransition("confirm payment for");
}
// Other transitions throw exceptions
}
Conclusion: The Path Forward
Advanced clean code isn't just about following patterns—it's about making intentional architectural decisions that serve your business needs while maintaining code quality. Remember:
- Architecture should emerge from domain needs, not the other way around
- Performance optimizations should be measured and targeted, not premature
- Legacy code integration requires careful boundaries to prevent contamination
- Error handling should be explicit and recoverable where possible
- Monitoring should provide business insights, not just technical metrics
The journey from good code to great code is about understanding when to apply which patterns, how to balance competing concerns, and always keeping your future self (and teammates) in mind.
Tomorrow, we'll explore testing strategies that ensure your clean architecture remains robust as it evolves. Stay tuned for Day 3: "Testing Clean Code - From Unit Tests to Architectural Validation."
Top comments (0)