Domain-Driven Design (DDD) in Go: The Hexagonal Architecture

Go Domain-Driven Design: The Bounded Context Mirror
When you start a Go project, a single main.go and a types.go are enough. But as your team grows and your business logic expands to 50,000 lines of code, the "Flat Structure" becomes a nightmare. To survive at scale, you need a strategy that isolates your Business Logic from your Infrastructure.
In Go, we achieve this through Domain-Driven Design (DDD) and Hexagonal Architecture (also known as Ports & Adapters). This pattern ensures that your "Domain" (the core logic) doesn't know about your database, your web framework, or your external APIs. They are mere "Adapters" that can be swapped without touching a single line of business code.
1. The Core Layers: Protecting the Domain
Imagine an onion. The center is the Domain.
- Domain Layer (The Heart): Contains only business rules, entities, and value objects. No external dependencies. No SQL imports. No HTTP references.
- Application Layer (The Brain): Orchestrates the domain. It defines "Use Cases" (e.g.,
PlaceOrder,RegisterUser). It coordinates between the Domain and the outside world. - Infrastructure Layer (The Muscles): Implementations of the external world. These are the Adapters:
GORM repository(Adapter for SQL).Gin handler(Adapter for HTTP).
Twilio client(Adapter for SMS).
1. The Bounded Context Mirror: Domain Geometry
In DDD, a Bounded Context is a boundary around a specific linguistic mirror.
The Structural Physics
- The Ubiquitous Language Mirror: Inside a package (e.g.,
shipping), the word "Order" means a physical package with weight. In thebillingpackage, "Order" means an invoice with a price. Go's package system allows these mirrors to coexist without collision. - The Context Boundary: Moving data between contexts requires a Translator Mirror. Never pass a
shipping.Orderstruct directly to abillingservice; use a specific DTO to bridge the gap. - The Result: You prevent the "Circular Dependency Mirror" (Module 10) by strictly defining which context owns which part of the silicon state.
2. Ports and Adapters: The Interface Strategy
The secret to Hexagonal Architecture is Dependency Inversion.
- The Port: A Go
interfacedefined in the internal layers. - The Adapter: A Go
structin the infrastructure layer that implements that interface.
Example Port:
Example Adapter:
3. The Hardware-Mirror: The Tax of Abstraction
Senior architects understand that "Clean Architecture" is not "Free Architecture." It has a physical footprint.
The Overhead of "Cleanliness"
- Interface Indirect Dispatch: When you call a method through a Go
interface, the CPU must perform a VTable lookup (theitab). This is slightly slower than a direct function call. In high-frequency hot loops,1,000interface calls can add nanoseconds of delay. - Allocation and Casting: Mapping from an Infrastructure-specific Struct (e.g.,
GormOrder) to a Domain Entity (Order) requires data copying. For large datasets, this increases Memory Allocation and triggers more frequent Garbage Collection. - Binary Bloat: Every layer of abstraction adds more symbols to your compiled binary. While a
15MBvs20MBbinary doesn't matter for a desktop, it matters for L1/L2 Instruction Cache efficiency in high-performance computing.
Hardware-Mirror Rule: Use DDD for complex business logic, but Avoid it for high-speed data planes. If you are building a proxy or an image processor that handles 1TB of data per hour, use a flat, direct, and un-abstracted structure to minimize CPU branch mispredictions.
3. The Anti-Corruption Mirror: Mapper Physics
The most critical part of DDD in Go is the Anti-Corruption Layer (ACL).
The Protection Physics
- The Leakage Mirror: If your domain uses a struct from an external library (like
gorm.Model), the external vendor now owns your business mirror. If GORM updates and breaks their struct, your domain breaks. - The Mapper Mirror: You create separate structs for the DB and the Domain. You write a "Mapper" function that mirrors data between them.
- The Independence Cost: This adds boilerplate (copying fields), but it ensures your "Domain Mirror" is sovereign. You can swap your database from Postgres to MongoDB without changing a single line of your order processing logic.
4. Why Go is the Perfect Language for DDD
Unlike Java or C#, Go's Implicit Interfaces are a superpower for Hexagonal Architecture.
- In Java, you must explicitly say
implements Repository. - In Go, if your struct has the right methods, it implements the interface. This allows you to keep your Domain purely focused on logic, without even needing to know which interfaces exist in other packages.
This decoupling is what makes Go projects so resilient to change.
5. Directory Structure: Managing the Project
A standard Go DDD project should look like this:
6. Implementation Checklist: The Architecture Audit
- The "No Dependency" Rule: Check your
internal/domainpackage. If you see an import fordatabase/sql,gin, orprotoc, your architecture is "leaking." - Constructor Injection: Always use
NewService(repo Repository)to inject dependencies. NEVER use global variables. - Map Every Layer: Don't be afraid to create "Duplicate" structs for your DB and your Domain. The independence you buy is worth the
10lines of boilerplate mapping code. - Test the Core: You should be able to write and run all your business logic tests without a database or a network. If you need a DB for a business logic test, your layers are tangled.
Summary: Designing for the Decade
- Isolate the Domain: Keep your business rules pure and protected.
- Invert with Interfaces: Use Ports & Adapters to make your infrastructure swappable.
- Respect the Silicons: Understand that every interface adds a slight CPU tax—use abstraction where complexity is high, not where speed is the only goal.
- Clean over Fast: For enterprise microservices, maintainability is more valuable than saving a few nanoseconds on a direct call.
You have now reached the highest level of Go engineering. You can build systems that will last for a decade. It is time for your Final Knowledge Test.
Part of the Go Mastery Course — engineering the decade.
Phase 26: DDD Architecture Mastery Checklist
- Verify Package Sovereignty: Check your
internal/domain. Ensure it has zero imports frominfrastructureor third-party web/DB frameworks. - Audit Ubiquitous Language: Confirm that struct names and field names mirror the business terminology, not the database column names.
- Implement Anti-Corruption Mappers: Never return a DB model directly from a repository. Always mirror it into a Domain Entity before it leaves the
infrastructurelayer. - Test In-Memory Mirrors: You should be able to run all business logic tests using a simple
mapas a repository instead of a real database. - Use Value Object Immuntability: For types like
MoneyorAddress, use unexported fields with "Getter" mirrors to ensure the domain state remains stable.
Read next: Go Observability & OTEL: The Telemetry Mirror →
