Essential Software Architecture Principles with Example Implementations
This article outlines core software architecture principles such as Dependency Inversion, Separation of Concerns, Inversion of Control, Dependency Injection, Single Responsibility, DRY, Open‑Closed, Persistence Ignorance, and many others, and illustrates them with Java code examples for actions and converters.
Software architecture is built on a set of fundamental principles that experienced architects apply to create maintainable, flexible systems. Below is a quick overview of the most common principles followed in daily practice.
1. Dependency Inversion – Dependencies should point toward abstractions rather than concrete implementations, allowing the direction of control to be reversed.
2. Separation of Concerns (SoC) – Divide a system by the type of work (business logic, infrastructure, UI) to simplify development, testing, and deployment.
3. Inversion of Control (IoC) – Frameworks like Spring manage control flow, configuring beans instead of the developer manually initializing them.
4. Dependency Injection – Dependencies are supplied at runtime, e.g., injecting an Action implementation into a Human class.
5. Single Responsibility – Each component should have only one reason to change, whether it is a plugin, class, function, or variable.
6. DRY (Don’t Repeat Yourself) – Reuse existing functionality instead of duplicating code.
7. Open‑Closed Principle – Software entities should be open for extension but closed for modification.
8. Persistence Ignorance – Business logic should not depend on specific database or persistence technologies.
9. YAGNI – Do not implement features until they are truly needed.
10. Boy Scout Rule – Leave the codebase cleaner than you found it, refactoring when encountering anti‑patterns.
11. Liskov Substitution – Subtypes must be replaceable for their base types without altering program behavior.
12. Encapsulation – Hide internal details of components, exposing only necessary interfaces.
13. Loose Coupling – Minimize the impact of changes in one part of the system on others.
14. Cohesion – Keep related functionality within the same module to support high cohesion.
15. Interface Segregation – Clients should not be forced to depend on methods they do not use.
16. Bounded Context – In Domain‑Driven Design, split large applications into independent conceptual modules with their own models and storage.
17. Stable Dependencies – Depend only on reliable, stable artifacts, especially when using external Docker images.
18. Polymorphism – Use interfaces that can be implemented in multiple forms.
19. Modularization – Divide a system into independent modules, a static‑architecture counterpart of Single Responsibility.
20. Abstraction – Hide unnecessary details to focus on essential aspects of a system.
21. KISS (Keep It Simple, Stupid) – Write simple, easy‑to‑understand code.
22. Incremental/Iterative Approach – Develop software in small, repeatable increments, a core agile principle.
23. Least Knowledge (Law of Demeter) – Limit each part of the system to only the knowledge it needs.
Below are Java code snippets that demonstrate Dependency Injection and a simple service‑converter pattern.
package az.alizeynalli.di; public interface Action { void do(); } public class HumanAction implements Action { @Override public void do() { System.out.print("run"); } } public class Human { Action action; public Human(Action action) { this.action = action; } @Override public void do() { action.do(); } } public static void main(String[] args) { Human human = new Human(new HumanAction()); human.do(); }Another example shows a converter service interface with implementations for income and expense conversion.
package az.alizeynalli.cashflow.core.service; public interface ConverterService { Income convertIncome(Income income); Expense convertExpense(Expense expense); } @Component
public class ExpenseConverterServiceImpl implements ConverterService { @Override
public Income convertIncome(Income income) {
throw new UnsupportedOperationException();
}
@Override
public Expense convertExpense(Expense expense) {
// convert expense here
return expense;
}
} @Component
public class IncomeConverterServiceImpl implements ConverterService { @Override
public Income convertIncome(Income income) {
// convert income here
return income;
}
@Override
public Expense convertExpense(Expense expense) {
throw new UnsupportedOperationException();
}
}Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
IT Architects Alliance
Discussion and exchange on system, internet, large‑scale distributed, high‑availability, and high‑performance architectures, as well as big data, machine learning, AI, and architecture adjustments with internet technologies. Includes real‑world large‑scale architecture case studies. Open to architects who have ideas and enjoy sharing.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
