Software Architecture Principles: A Guide for Architects
This article presents a comprehensive overview of essential software architecture principles—including Dependency Inversion, Separation of Concerns, Inversion of Control, Dependency Injection, SOLID rules, and more—explaining their purpose, practical impact, and providing Java code examples to illustrate their application in real projects.
Software architecture is built on a set of fundamental principles that experienced architects apply to create maintainable, flexible, and robust systems; this guide quickly reviews the most commonly used principles.
1. Dependency Inversion – Dependencies should point toward abstractions rather than concrete implementations, allowing the direction of control to flow from high‑level modules to low‑level ones.
2. Separation of Concerns (SoC) – A system should be divided by the type of work (business logic, infrastructure, UI, etc.) so that each part can be developed, tested, and deployed independently.
3. Inversion of Control (IoC) – Similar to Dependency Inversion but applied more broadly; frameworks such as Spring manage the control flow and bean configuration instead of the application code.
4. Dependency Injection (DI) – Dependencies are supplied at runtime, typically via constructors, enabling flexible selection of implementations. Example:
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;
}
public void do() {
action.do();
}
public static void main(String[] args) {
Human human = new Human(new HumanAction());
human.do();
}
}5. Single Responsibility – Each building block (class, module, function) should have only one reason to change.
6. DRY (Don’t Repeat Yourself) – Eliminate redundant code by reusing existing functionality instead of copying 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; refactor when encountering anti‑patterns.
11. Liskov Substitution – Subtypes must be usable wherever their base types are expected without altering program behavior.
12. Encapsulation – Hide internal details of a component by restricting external access, typically using private members.
13. Loose Coupling – Reduce inter‑module dependencies so that changes in one part have minimal impact on others; can be achieved via DI, messaging, or event sourcing.
14. Cohesion – Measure how closely related the responsibilities of a module are; high cohesion supports single responsibility and loose coupling.
15. Interface Segregation – Clients should not be forced to depend on interfaces they do not use; especially relevant for statically typed languages.
16. Bounded Context – In Domain‑Driven Design, split a large system into distinct conceptual modules with clear boundaries and independent data stores.
17. Stable Dependencies – Depend only on reliable, stable artifacts; this concept is also applicable to container images and external libraries.
18. Polymorphism – One of the four pillars of OOP; allows entities to be treated uniformly through a common interface.
19. Modularization – Divide a system into independent modules, a static‑architecture counterpart of the single‑responsibility principle.
20. Abstraction – Hide unnecessary details to focus on essential characteristics, similar to generalization.
21. KISS (Keep It Simple, Stupid) – Encourage simple, easy‑to‑understand code to avoid misinterpretation.
22. Incremental/Iterative Approach – Core of agile development; deliver functionality in small, repeatable increments.
23. Least Knowledge (Law of Demeter) – Each part of a system should only know what is necessary, reducing coupling.
Additional Java examples illustrate how to implement a conversion service using Spring components:
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();
}
}By adhering to these principles, architects can design systems that are easier to evolve, test, and maintain.
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.
Architect's Guide
Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.
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.
