Solid Principles Production

Production-grade guide to solid principles production covering architecture patterns, implementation strategies, testing approaches, and operational best practices for enterprise engineering teams.

This reference describes how to apply the SOLID principles in production software systems, focusing on practical implementation, deployment, and failure modes. These are the foundational design rules for maintainable, scalable, and robust codebases.

Understanding the Principle of Single Responsibility

The Single Responsibility Principle (SRP) requires that each class or module should have only one reason to change. When implementing this principle, verify that a module's responsibilities align with its usage patterns.

A common failure occurs when modules grow beyond their original purpose. For example, consider a service class that both manages database connections and performs business logic:

class OrderService:
    def __init__(self):
        self.db = DatabaseConnection()
    
    def process_order(self, order_data):
        # Business logic here
        return self.db.save(order_data)
    
    def get_user_orders(self, user_id):
        # Another responsibility
        return self.db.query(f"SELECT * FROM orders WHERE user_id = {user_id}")

This class violates SRP because it handles both persistence and business operations. The fix involves separating concerns into distinct classes:

class OrderRepository:
    def __init__(self):
        self.db = DatabaseConnection()
    
    def save(self, order_data):
        return self.db.save(order_data)
    
    def find_by_user(self, user_id):
        return self.db.query(f"SELECT * FROM orders WHERE user_id = {user_id}")

class OrderService:
    def __init__(self, repository):
        self.repository = repository
    
    def process_order(self, order_data):
        # Business logic only
        return self.repository.save(order_data)

When deploying services that follow SRP, ensure configuration tools do not allow cross-cutting concerns to creep into single modules. Monitor for code changes that add new responsibilities to existing classes.

Applying the Open/Closed Principle

The Open/Closed Principle states that software entities should be open for extension but closed for modification. This is typically implemented using inheritance, composition, or strategy patterns.

In practice, this means designing interfaces that allow new behavior without altering existing code:

class PaymentProcessor:
    def process(self, amount):
        raise NotImplementedError

class CreditCardProcessor(PaymentProcessor):
    def process(self, amount):
        # Implementation for credit card
        return f"Charged ${amount} via credit card"

class PayPalProcessor(PaymentProcessor):
    def process(self, amount):
        # Implementation for PayPal
        return f"Charged ${amount} via PayPal"

# Usage
def charge_customer(processor, amount):
    return processor.process(amount)

When deploying this pattern, ensure that the system can dynamically load new implementations without recompilation. Many systems use dependency injection containers or service registries to manage such extensions.

The most common failure mode is treating the principle as a static design decision. Production systems must account for how extension points are managed at runtime. If a new payment method requires database schema changes, the system may not be truly open for extension.

Implementing the Liskov Substitution Principle

The Liskov Substitution Principle (LSP) requires that objects of a superclass should be replaceable with instances of its subclasses without altering the correctness of the program. This is particularly important in production when polymorphic behavior is expected.

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    def area(self):
        return self.width * self.height

class Square(Rectangle):
    def __init__(self, side):
        super().__init__(side, side)
    
    def set_width(self, width):
        self.width = width
        self.height = width  # This violates LSP

The Square class breaks LSP because setting width affects height, which changes the object's behavior. Production systems often fail to detect this when testing with concrete classes rather than interfaces.

A robust implementation maintains behavioral consistency:

class Rectangle:
    def __init__(self, width, height):
        self._width = width
        self._height = height
    
    @property
    def width(self):
        return self._width
    
    @width.setter
    def width(self, value):
        self._width = value
    
    @property
    def height(self):
        return self._height
    
    @height.setter
    def height(self, value):
        self._height = value
    
    def area(self):
        return self._width * self._height

class Square(Rectangle):
    def __init__(self, side):
        super().__init__(side, side)
    
    @property
    def width(self):
        return self._width
    
    @width.setter
    def width(self, value):
        self._width = value
        self._height = value
    
    @property
    def height(self):
        return self._height
    
    @height.setter
    def height(self, value):
        self._height = value
        self._width = value

In production, test that all subclasses behave identically under the same interface. This is critical for systems using polymorphic operations.

Managing the Interface Segregation Principle

Interface Segregation Principle (ISP) states that clients should not be forced to depend on interfaces they don't use. In production, this prevents cascading changes when interfaces evolve.

# Bad example - fat interface
class Worker:
    def work(self):
        pass
    
    def eat(self):
        pass
    
    def sleep(self):
        pass

class HumanWorker(Worker):
    def work(self):
        return "Working"
    
    def eat(self):
        return "Eating"
    
    def sleep(self):
        return "Sleeping"

class RobotWorker(Worker):
    def work(self):
        return "Working"
    
    def eat(self):
        raise NotImplementedError("Robots don't eat")
    
    def sleep(self):
        raise NotImplementedError("Robots don't sleep")

The correct approach splits interfaces:

class Workable:
    def work(self):
        pass

class Eatable:
    def eat(self):
        pass

class Sleepable:
    def sleep(self):
        pass

class HumanWorker(Workable, Eatable, Sleepable):
    def work(self):
        return "Working"
    
    def eat(self):
        return "Eating"
    
    def sleep(self):
        return "Sleeping"

class RobotWorker(Workable):
    def work(self):
        return "Working"

In production, this separation prevents changes to eating behavior from affecting robot workers. This is especially important when services communicate through interfaces and need to scale independently.

Maintaining the Dependency Inversion Principle

The Dependency Inversion Principle (DIP) requires that high-level modules should not depend on low-level modules. Both should depend on abstractions. This is essential for decoupling components in production systems.

# Bad example - direct dependencies
class EmailService:
    def send_email(self, message):
        # Implementation details
        pass

class NotificationService:
    def __init__(self):
        self.email_service = EmailService()  # Direct dependency
    
    def notify_user(self, user_id, message):
        return self.email_service.send_email(message)

The correct implementation uses abstractions:

class NotificationSender:
    def send(self, message):
        raise NotImplementedError

class EmailService(NotificationSender):
    def send(self, message):
        # Implementation details
        return f"Email sent: {message}"

class NotificationService:
    def __init__(self, sender: NotificationSender):
        self.sender = sender
    
    def notify_user(self, user_id, message):
        return self.sender.send(message)

Production systems using DIP must ensure that dependency injection works correctly. Configuration management tools should allow swapping implementations without redeployment.

Handling Common Production Failures

Silent Interface Changes

The most common production failure is interface evolution that silently breaks clients. When a method signature changes, existing implementations may continue to work but behave incorrectly:

# Before
class DataProcessor:
    def process(self, data):
        return data.upper()

# After - breaking change
class DataProcessor:
    def process(self, data, options=None):
        if options and options.get('uppercase', True):
            return data.upper()
        return data.lower()  # Silent behavior change

Runtime vs Compile-time Checking

Many production systems assume compile-time checking for interface compliance. In languages without strong typing, runtime failures can occur:

# Python example - runtime failure
def process_items(items):
    for item in items:
        if hasattr(item, 'process'):
            item.process()  # No guarantee that process method exists
        else:
            raise AttributeError("Item must have process method")

Configuration Management Issues

Dependency inversion requires robust configuration. When dependencies are configured incorrectly, production systems fail silently:

# Configuration example - missing dependency
services:
  notification_service:
    type: email
    # Missing required configuration for email service

Deployment Considerations

When deploying systems following SOLID principles, ensure that configuration tools support runtime dependency swapping. Monitor for violations of these principles in deployed code.

Use continuous integration to catch interface changes that break existing implementations. Test both compile-time and runtime behavior to ensure all dependencies are properly injected.

Testing Solid Principles

Testing production code that follows SOLID principles requires careful attention to polymorphic behavior:

import unittest

class TestNotificationService(unittest.TestCase):
    def test_email_notification(self):
        sender = EmailService()
        service = NotificationService(sender)
        result = service.notify_user(123, "Hello")
        self.assertEqual(result, "Email sent: Hello")

    def test_different_senders(self):
        # Test that any NotificationSender works
        senders = [EmailService(), SMSNotificationService()]
        for sender in senders:
            service = NotificationService(sender)
            result = service.notify_user(123, "Hello")
            self.assertTrue(result.startswith("Sent"))

Production testing must verify that all implementations of interfaces behave correctly under the same contract. This prevents subtle failures when production code switches between different implementations.

Monitoring and Observability

Production systems using SOLID principles benefit from monitoring that detects interface violations:

# Logging example
class MonitoredService:
    def __init__(self, sender):
        self.sender = sender
    
    def notify_user(self, user_id, message):
        try:
            result = self.sender.send(message)
            # Log successful notification
            return result
        except Exception as e:
            # Log interface violation
            logger.error(f"Notification failed: {e}")
            raise

Production monitoring should detect when implementations fail to meet expected contracts. This includes checking that all required methods exist and behave correctly.

Conclusion

Applying SOLID principles in production requires careful attention to how interfaces are defined, implemented, and managed. The key is ensuring that systems remain flexible while maintaining predictable behavior. Production failures typically occur from ignoring these principles during development, leading to systems that appear correct but fail under real-world conditions.

This page was rewritten on 15 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.