- Keep modules small, focused, and decoupled. Each module should have a single clear responsibility.
- Define explicit public interfaces — hide implementation details behind well-defined boundaries.
- Define clear module boundaries with explicit public APIs — modules communicate through interfaces, not direct imports of internal classes.
- Use dependency injection to manage dependencies between modules — never hardcode concrete implementations.
- Enforce unidirectional dependencies. If A depends on B, B must never depend on A.
- Group related functionality into cohesive packages. A module should change for one reason only.
- Expose the minimum public API surface — internal implementation details should stay internal.