Tuesday, July 22, 2025

SOLID Principles, DRY, YAGNI, KISS


S – Single Responsibility Principle (SRP)

Definition:
A class should have only one reason to change — it should do one thing, and do it well.

Why This Principle?

I applied SRP because we needed to separate concerns and reduce the impact of changes.
It fits well in the architecture as it isolates responsibilities, so that changes in logging don’t affect business logic, for example.
It keeps the design modular, testable, and easy to understand.

Example:

  • Separate services for UserRegistrationService, EmailNotificationService, and AuditLogger.


O – Open/Closed Principle (OCP)

Definition:
Software should be open for extension but closed for modification.

Why This Principle?

I followed OCP because we needed to add new features without breaking or editing existing code.
It fits well in plugin-based architectures where we add new handlers or processors without changing the core logic.
It keeps the design safe for scale and change.

Example:

  • Using Strategy Pattern for different payment methods — new strategy = no change in core flow.


L – Liskov Substitution Principle (LSP)

Definition:
Subtypes must be substitutable for their base types — i.e., derived classes must honor the contract of the parent class.

Why This Principle?

I used LSP to ensure that subclasses behave consistently when used as their parent.
It fits well in polymorphic designs, helping us write safe and predictable interfaces.
It keeps the design reliable and avoids bugs from incorrect overrides.

Example:

  • A Bird superclass with fly() should not have Penguin subclass that throws UnsupportedOperationException.


I – Interface Segregation Principle (ISP)

Definition:
Clients should not be forced to depend on methods they do not use — prefer many small interfaces over one large interface.

Why This Principle?

I followed ISP because we needed to avoid bloated interfaces and keep contracts minimal.
It fits well in microservices or plugin systems where each consumer may only need part of a capability.
It keeps the design focused and reduces implementation burden.

Example:

  • Readable, Writable, Closable interfaces instead of one huge FileHandler.


D – Dependency Inversion Principle (DIP)

Definition:
High-level modules should not depend on low-level modules. Both should depend on abstractions (interfaces). Also, details should depend on abstractions, not the other way around.

Why This Principle?

I used DIP to decouple high-level logic from implementation details like persistence or network access.
It fits well in layered architectures and frameworks like Spring, where we inject interfaces rather than concrete classes.
It keeps the design loosely coupled, testable, and open for switching implementations.

Example:

  • A UserService depending on UserRepository interface, not the actual JDBC class.


💡 Real-World Mapping in Spring Boot

SOLID PrincipleSpring Boot Example
SRP@Service, @Repository, @Controller layers
OCP@Component-based plugin extension
LSPInterfaces + Spring Proxies
ISPREST interfaces focused per domain
DIPConstructor Injection + @Qualifier

🧠 Bonus Interview Tip:

When asked "How do you apply SOLID in microservices?" respond with:

“Each service should follow SRP by owning a bounded context, depend on abstractions (DIP), and be extensible via events or strategies (OCP). Interfaces should be small and focused (ISP), and contracts across services must be consistent (LSP).”



✅DRY – Don’t Repeat Yourself

🔹 Definition:

Avoid duplication of logic or code. Every piece of knowledge should have a single, unambiguous representation in your system.

💡 Why This Principle?

I applied DRY because we needed to eliminate redundant logic across modules and prevent bugs from inconsistent changes.
It fits well in the architecture when we extract shared utilities, common services, or base classes, especially in domain model or validation layers.
It keeps the design clean, maintainable, and reduces technical debt.

📌 Example Use:

  • Centralize date parsing logic or error handling.

  • Shared validation or mapping logic for DTOs.


YAGNI – You Aren’t Gonna Need It

🔹 Definition:

Only build what you need now, not what you think you'll need later.

💡 Why This Principle?

I followed YAGNI because we wanted to avoid over-engineering and unused features that might never be needed.
It fits well in agile architecture where incremental delivery and quick feedback matter, especially in early-stage systems or MVPs.
It keeps the design focused, lean, and easier to change.

📌 Example Use:

  • Don’t implement multitenancy or feature flags until they are actually needed.

  • Avoid abstracting service layers prematurely when there’s only one implementation.


KISS – Keep It Simple, Stupid

🔹 Definition:

Favor simple, understandable solutions over complex ones — avoid cleverness unless necessary.

💡 Why This Principle?

I used KISS to keep the system easy to read, debug, and evolve, especially under deadlines.
It fits well when we want to reduce onboarding time, minimize bugs, and ensure code is clear to other engineers.
It keeps the design elegant and avoids accidental complexity.

📌 Example Use:

  • Prefer REST over complex GraphQL unless really needed.

  • Use a simple if/else instead of overusing polymorphism when there are only two cases.

 

No comments:

Post a Comment