Core Design Principles — Deep Dive
Level: Beginner → Intermediate
Pre-reading: 01 · Architectural Foundations
Why Principles Matter
Patterns solve specific problems. Principles guide decision-making when no pattern fits exactly. They're the "first principles" you reason from when facing unfamiliar situations.
Loose Coupling
"Components should depend on each other as little as possible."
Coupling measures how much one component knows about another's internal workings.
| Tight Coupling | Loose Coupling |
|---|---|
| Direct method calls across services | Async events or well-defined APIs |
| Shared database schemas | Each service owns its data |
| Synchronous chains | Async messaging with retries |
| Shared libraries with business logic | Shared contracts only (schemas, APIs) |
Coupling Spectrum
graph LR
A[Shared DB] --> B[RPC/REST]
B --> C[Events]
C --> D[No Dependency]
style A fill:#f99
style B fill:#ff9
style C fill:#9f9
style D fill:#9ff
How to Achieve Loose Coupling
| Technique | Description |
|---|---|
| Async messaging | Producer doesn't wait for consumer response |
| API versioning | Consumers don't break on minor changes |
| Schema evolution | Add fields; never remove/rename |
| Contracts > implementations | Depend on interfaces, not concrete classes |
| Feature flags | Deploy independently; enable features at runtime |
Distributed monolith
Microservices that must deploy together, share databases, or have synchronous call chains aren't microservices — they're a distributed monolith with all the costs and none of the benefits.
High Cohesion
"Related code should live together; unrelated code should live apart."
Cohesion measures how focused a component is. High cohesion means everything in the component serves a single, well-defined purpose.
| Low Cohesion | High Cohesion |
|---|---|
UtilityService with unrelated methods |
OrderService doing only order things |
| "God class" doing everything | Small classes with single responsibility |
| Feature spanning 5 services | Feature contained in one service |
Cohesion and Service Boundaries
graph TD
subgraph High Cohesion
A[Order Service] --> B[Create Order]
A --> C[Cancel Order]
A --> D[Order Status]
end
subgraph Low Cohesion
E[Business Service] --> F[Create Order]
E --> G[Update Inventory]
E --> H[Send Email]
E --> I[Calculate Tax]
end
Testing Cohesion
| Question | If Yes |
|---|---|
| Can you name the component in 2–3 words? | Cohesive |
| Does every method relate to that name? | Cohesive |
| Would splitting it create artificial coupling? | Keep together |
| Are there methods that never change together? | Split them |
Design for Failure
"In distributed systems, failure is not an exception — it's the norm."
Every network call can fail. Every service can go down. Every database can timeout. Design assuming everything will fail.
Failure Modes
| Failure | Example |
|---|---|
| Crash | Service process dies |
| Timeout | Service is slow; caller gives up |
| Error response | Service returns 500 |
| Corruption | Service returns wrong data |
| Byzantine | Service behaves unpredictably |
Design Patterns for Failure
| Pattern | Purpose |
|---|---|
| Retry | Recover from transient failures |
| Circuit Breaker | Stop calling failing services |
| Bulkhead | Isolate failures to one component |
| Timeout | Don't wait forever |
| Fallback | Degrade gracefully |
| Idempotency | Safe retries without side effects |
→ Deep Dive: Resilience Patterns for implementation details
Single Responsibility Principle (SRP)
"A component should have one, and only one, reason to change."
"Reason to change" = a stakeholder or business capability. If a change to billing logic and a change to shipping logic both require modifying the same service, that service has too many responsibilities.
At the Service Level
| Good | Bad |
|---|---|
OrderService changes only when order logic changes |
OrderService changes when shipping rules change |
PaymentService owns all payment logic |
Payment logic scattered across three services |
At the Code Level
| Good | Bad |
|---|---|
OrderValidator validates orders |
OrderService validates, persists, and notifies |
InvoiceGenerator creates invoices |
OrderService also generates invoices |
Fail Fast
"Surface problems immediately; don't hide or swallow errors."
The longer an error goes undetected, the harder it is to diagnose and the wider the blast radius.
| Fail Fast | Fail Slow (Anti-pattern) |
|---|---|
| Validate input at API boundary | Accept anything; fail deep in the call stack |
| Throw exception on bad config | Start with defaults; fail mysteriously later |
| Health check fails if dependency down | Serve requests anyway; return errors |
| Circuit breaker trips fast | Keep retrying until timeout |
Implementation
// Fail fast: validate at entry point
public Order createOrder(OrderRequest request) {
Objects.requireNonNull(request, "Request cannot be null");
if (request.getItems().isEmpty()) {
throw new IllegalArgumentException("Order must have items");
}
// Proceed with valid request
}
# Kubernetes: fail fast startup probe
startupProbe:
httpGet:
path: /actuator/health
port: 8080
failureThreshold: 3
periodSeconds: 5
Immutable Infrastructure
"Don't patch production — replace it."
Mutable infrastructure: SSH into servers, apply patches, modify config files. State drifts; "works on my machine" multiplies.
Immutable infrastructure: Build new images with changes; deploy them; destroy old instances. Every deployment is a fresh start.
| Mutable | Immutable |
|---|---|
| SSH and modify | Build new container image |
| In-place upgrades | Blue-green deployment |
| Configuration drift | Every environment identical |
| "Just tweak it in prod" | Rollback = deploy previous image |
Benefits
| Benefit | Explanation |
|---|---|
| Reproducibility | Same image runs everywhere |
| Rollback | Deploy previous known-good image |
| No drift | Production matches what you tested |
| Security | No SSH access needed; smaller attack surface |
Everything as Code
"If it's not in Git, it doesn't exist."
Version control isn't just for application code. Infrastructure, configuration, pipelines, policies — all should be codified, reviewed, and versioned.
| Domain | As Code |
|---|---|
| Infrastructure | Terraform, Pulumi, AWS CDK |
| Configuration | Helm values, Kustomize overlays |
| Pipelines | GitHub Actions, GitLab CI YAML |
| Policies | OPA Rego, Kubernetes admission policies |
| Documentation | Markdown in repo; MkDocs |
Benefits
| Benefit | Explanation |
|---|---|
| Audit trail | Who changed what, when, and why |
| Collaboration | PRs, reviews, comments |
| Reproducibility | Recreate environments from scratch |
| Rollback | git revert to undo changes |
Separation of Concerns
"Each component should handle one aspect of functionality."
Related to SRP but at a higher level. Different types of concerns should be separated:
| Concern | Separation |
|---|---|
| Business logic vs infrastructure | Hexagonal architecture |
| Read vs write models | CQRS |
| API vs implementation | Interface-based design |
| Configuration vs code | Environment variables, ConfigMaps |
| Cross-cutting concerns | Service mesh, middleware, aspects |
Cross-Cutting Concerns
Some concerns span all components: logging, security, metrics. Handle them consistently without duplicating logic:
| Approach | Example |
|---|---|
| Middleware/interceptors | Express middleware, Spring filters |
| Aspect-Oriented Programming | Spring AOP for logging |
| Service Mesh | Istio sidecars handle mTLS, metrics |
| API Gateway | Centralized auth, rate limiting |
Principle Tensions
Principles sometimes conflict. Architecture is about navigating trade-offs:
| Tension | Trade-off |
|---|---|
| Loose coupling vs consistency | Looser coupling means eventual consistency |
| High cohesion vs reuse | Cohesive components may duplicate code |
| Fail fast vs availability | Failing fast may reduce availability |
| Immutability vs cost | Rebuilding images costs compute time |
| Everything as code vs agility | PR reviews add latency to changes |
Principle Checklist
Use this when reviewing designs:
- [ ] Can this component change independently of others?
- [ ] Does this component do one thing well?
- [ ] What happens when this dependency fails?
- [ ] Is there a single reason this code would change?
- [ ] Would a bug here be detected immediately?
- [ ] Is the infrastructure reproducible from scratch?
- [ ] Is everything needed to deploy this system in version control?
- [ ] Is business logic separate from infrastructure concerns?
What's the difference between coupling and cohesion?
Coupling is about the relationship between components — how much one depends on another's internals. Cohesion is about the relationship within a component — how focused and unified its purpose is. Good design has low coupling (components independent) and high cohesion (components focused).
How do you apply 'design for failure' in practice?
(1) Assume every network call will fail: add timeouts, retries, circuit breakers. (2) Have fallback responses: cached data, default values, degraded modes. (3) Make operations idempotent: safe to retry without side effects. (4) Test failures: chaos engineering, fault injection. (5) Monitor and alert: detect failures before users do.
Why is 'immutable infrastructure' better than patching servers?
(1) No drift: production is identical to what was tested. (2) Reproducible: can recreate environment from code. (3) Easy rollback: deploy the previous image. (4) Better security: no SSH access needed. (5) Simpler debugging: every instance is identical. With mutable infrastructure, each server can be slightly different, making debugging a nightmare.