The Power Outlet Analogy
Power outlets are standardized ports:
| Component | Role |
|---|---|
| Outlet (Port) | Standardized interface |
| Device (Adapter) | Plugs into the interface |
| Power source | Hidden behind the outlet |
- Any device can plug in
- Outlet doesn't care what's connected
- Device doesn't care how electricity is generated
Hexagonal architecture works the same way. Your business logic (core) connects to the outside world through standardized ports.
What Is Hexagonal Architecture?
Also called "Ports and Adapters"
| Concept | Description |
|---|---|
| Core | Business logic at the center |
| Ports | Interfaces defined by core |
| Adapters | Implementations that plug into ports |
| Key rule | Core doesn't know about databases, web, etc. |
The Hexagon Shape
┌─────────────────────────────┐
│ Web UI │ Adapter
└──────────┬──────────────────┘
│ Port
┌──────────▼──────────────────┐
│ │
REST API ─┤ Business Logic ├─ Database
│ (Domain Core) │
CLI ──────┤ ├─ Email API
└──────────▲──────────────────┘
│ Port
┌──────────┴──────────────────┐
│ Message Queue │ Adapter
└─────────────────────────────┘
Multiple sides for different connections.
Ports and Adapters
Ports
Ports are interfaces defined by the core:
| Example Port | What It Declares |
|---|---|
UserRepository | "I need a way to save users" |
NotificationService | "I need a way to send notifications" |
PaymentGateway | "I need a way to charge cards" |
Ports don't know HOW - just WHAT is needed.
Adapters
Adapters are implementations that plug into ports:
| Port | Adapter A | Adapter B |
|---|---|---|
| UserRepository | PostgresUserRepo | InMemoryUserRepo |
| NotificationService | SendGridAdapter | MockEmailAdapter |
| PaymentGateway | StripeAdapter | TestPaymentAdapter |
Multiple adapters can implement the same port!
Primary vs Secondary Ports
Primary Ports (Driving)
Drive the application - they call into the core.
| Examples | They say... |
|---|---|
| Web controller | "User wants to place an order" |
| CLI commands | "Admin wants to reset password" |
| Message handlers | "New message arrived" |
| Scheduled jobs | "Time to run daily report" |
Secondary Ports (Driven)
Driven by the application - core calls them.
| Examples | Core says... |
|---|---|
| Database | "Save this order" |
| External APIs | "Verify this address" |
| Email service | "Send confirmation email" |
| File storage | "Store this document" |
Visualization
DRIVING (calls INTO core)
│
▼
┌──────────────────────────┐
│ │
Controller┤ ├─→ Database
│ DOMAIN CORE │ DRIVEN
CLI ─────→┤ ├─→ Email
│ │ DRIVEN
└──────────────────────────┘
▲
│
DRIVING (calls INTO core)
Why Hexagonal Architecture?
1. Testability
| Without Hexagonal | With Hexagonal |
|---|---|
| Test needs database | Plug in fake adapter |
| Test needs web server | Plug in fake adapter |
| Slow, flaky tests | Fast, reliable tests |
2. Flexibility
| Change This | Impact |
|---|---|
| Database (PostgreSQL → MongoDB) | Only change adapter |
| REST → GraphQL | Add GraphQL adapter |
| SendGrid → Mailgun | Only change adapter |
Core unchanged!
3. Clear Boundaries
| Rule | Effect |
|---|---|
| No database code in domain | Business logic isolated |
| No HTTP in business rules | Easy to understand |
| Clear separation | Easy to navigate codebase |
4. Technology Independence
Core knows nothing about frameworks, databases, or UI technology. Framework changes? Core stays the same.
Project Structure
src/
├── domain/ ← Core business logic
│ ├── models/
│ ├── services/
│ └── repositories/ ← Port interfaces
│
├── application/ ← Use cases
│ └── use_cases/
│
├── adapters/ ← Implementations
│ ├── web/ ← REST controller
│ ├── cli/ ← Command line
│ ├── persistence/ ← Database
│ └── external/ ← Third-party APIs
│
└── main.py ← Wire everything together
Dependency Rule
Core Must Not Know About Adapters
| Direction | Valid? |
|---|---|
| Adapters → Application → Domain | ✅ Yes |
| Domain → Adapters | ❌ No! |
| Application → Adapters | ❌ No! |
Dependencies point INWARD only. This protects your core from external changes.
How to Enforce
- Ports (interfaces) defined in domain/application
- Adapters implement those interfaces
- Dependency injection connects them at runtime
Hexagonal vs Clean Architecture
| Architecture | Emphasis |
|---|---|
| Hexagonal | Ports and adapters metaphor |
| Clean Architecture | Layers (entities, use cases, etc.) |
Similarities:
- Both isolate domain from infrastructure
- Both depend inward
- Mostly a difference in terminology
Common Mistakes
1. Domain Depends on Framework
| ❌ Bad | ✅ Good |
|---|---|
Entity has @Column ORM decorator | Entity is pure Python/TypeScript |
| Domain imports framework types | Adapter handles mapping |
2. Leaky Abstractions
| ❌ Bad | ✅ Good |
|---|---|
Port returns SQLAlchemyUser | Port returns User (domain type) |
| Database specifics leak | Port is database-agnostic |
3. Too Many Ports
| ❌ Bad | ✅ Good |
|---|---|
| GetUserPort, SaveUserPort, DeleteUserPort | UserRepository (cohesive) |
| One port per operation | Group related operations |
When to Use
Good Fit
| Scenario | Why It Helps |
|---|---|
| Long-lived applications | Worth the investment |
| Complex business logic | Core protected |
| Need high testability | Easy to mock |
| May change infrastructure | Swap adapters |
| Multiple entry points | Same core, different adapters |
Might Be Overkill
| Scenario | Why |
|---|---|
| Simple CRUD apps | Too much structure |
| Short-lived prototypes | Speed matters more |
| Very small codebases | Overhead not worth it |
FAQ
Q: Is hexagonal the same as clean architecture?
Very similar - both isolate domain from infrastructure. Terminology differs slightly.
Q: How do I start with an existing app?
Gradually extract domain logic. Introduce ports for one area at a time.
Q: Does this add complexity?
Initially, yes. Pays off as app grows and changes.
Q: What language works best?
Any language with interfaces/protocols. Common in Java, C#, TypeScript, Python.
Summary
Hexagonal architecture isolates business logic from external concerns through ports (interfaces) and adapters (implementations).
Key Takeaways:
- Core business logic at the center
- Ports define interfaces needed by core
- Adapters implement those interfaces
- Dependencies point inward
- Core doesn't know about databases, web, etc.
- Easy to test with fake adapters
- Primary ports drive, secondary ports are driven
Think of your app as having a pluggable architecture!
Related Concepts
Leave a Comment
Comments (0)
Be the first to comment on this concept.
Comments are approved automatically.