Domain Driven Design in Golang
Transforming Complexity into Coherence

In the ever-evolving world of software development, managing complexity is one of the greatest challenges. A well-structured approach to organizing code can significantly simplify this task, and Domain-Driven Design (DDD), along with popular architectural patterns such as Hexagonal Architecture and Clean Architecture, offers a roadmap to tackle it.
In this article, we will explore how these principles can be applied to a Golang project, helping us maintain clean, maintainable, and scalable systems.
Key Architectural Patterns
Let’s start by diving into the key architectural patterns that help us structure applications and handle complexity in a manageable way.
1. Domain Layer
The Domain Layer encapsulates the core business logic of the application. This layer is responsible for implementing the fundamental rules of the domain without being tied to any specific application. The core domain concepts are expressed through Entities, Aggregates, and Value Objects.
- Entities: Objects that have an identity which persists over time (e.g., User, Product).
- Aggregates: A group of related entities treated as a single unit (e.g., an Order with multiple OrderItems).
- Value Objects: Immutable objects that hold attributes (e.g., Address, Money).
This layer should not depend on any infrastructure concerns (like databases or external APIs). The goal is to define the core business logic in an isolated, pure form, so it can evolve independently of other layers.
2. Application Layer
The Application Layer is responsible for implementing the use cases of the application. It is use-case agnostic and coordinates tasks such as fetching and storing data. This layer serves as a glue between the incoming requests and the domain code, ensuring that the domain logic remains separate from infrastructure concerns like databases or user interfaces.
This layer should be free from business logic. It simply orchestrates operations using the domain entities and repositories.
3. Interfaces Layer
The Interfaces Layer consists of the external components through which the system interacts with the outside world. These can be REST APIs, UI components, message queues, or other kinds of external interfaces.
The role of this layer is to translate external requests (such as HTTP requests, user actions, or messages) into calls to the Application Layer, which then interacts with the Domain Layer. The Interfaces Layer should not contain any business logic, as it merely serves as a means of communication.
4. Infrastructure Layer
The Infrastructure Layer contains the implementations of the external systems that support the application. This layer provides adapters for interacting with databases, external APIs, file systems, or other infrastructure services. It ensures that the domain logic (in the Domain Layer) can operate independently from external dependencies.
In short, this layer deals with integrating third-party systems and fulfilling infrastructure concerns without polluting the core business logic.
Ubiquitous Language
A central tenet of Domain-Driven Design is the use of a Ubiquitous Language. This refers to a shared vocabulary between both developers and non-developers (such as business stakeholders). The goal is to ensure that everyone working on the system is speaking the same language, preventing misunderstandings and aligning development with the business domain.
The Ubiquitous Language should be reflected in the codebase: the names of classes, methods, and variables should all use terms from the business domain, making the code more understandable to both technical and non-technical stakeholders.
Bounded Contexts
A Bounded Context refers to a specific boundary within the domain where a particular Ubiquitous Language applies. Within this boundary, terms and concepts are defined precisely, and their meaning is shared across all teams and components that fall within it. Outside the boundary, these terms may have a different meaning.
In Golang, a bounded context can be implemented by organizing the codebase into packages that reflect different sub-domains or microservices. Each package can implement a different aspect of the domain, and their internal models should not bleed into other parts of the system.
Aggregates and Entities
In the context of DDD, an Aggregate is a cluster of domain objects that are treated as a single unit. It contains one or more Entities, each of which has its own identity and lifecycle.
An Entity in DDD is fundamentally defined by its identity, not its attributes. For example, a User entity may have attributes such as name
and email
, but its identity is defined by its unique ID.
Aggregates enforce consistency rules within the domain and help ensure that the invariants of the domain model are preserved. The Aggregate Root is the entry point to an aggregate and is responsible for ensuring the integrity of the entire aggregate.
Value Objects for Simplicity
Value Objects are immutable objects that do not have a distinct identity. They represent attributes or concepts that are defined solely by their attributes. A Money object, for example, might consist of a value
and a currency
but is considered equal to another Money object if it has the same value and currency, regardless of when it was created.
Using Value Objects helps to simplify the domain model by removing unnecessary complexity around mutable objects and identity.
Hexagonal Architecture (Ports and Adapters)
Hexagonal Architecture is a pattern that promotes isolating the core logic from external dependencies. The system is structured around Ports (which define how the core logic should interact with the outside world) and Adapters (which implement those ports).
In Golang, this pattern can be applied by designing interfaces for the core domain logic (the ports) and then implementing them using various adapters (such as a web handler for HTTP or a repository for a database).
The architecture is often visualized as a hexagon with the core business logic in the middle and the external systems (e.g., UI, databases, third-party services) on the outside.
Clean Architecture
Clean Architecture divides the application into layers, with dependencies always flowing inward toward the core business logic. This helps achieve a high level of decoupling and maintainability.
The layers typically consist of:
- Entities (Core domain): Represent the core business objects and their logic.
- Use Cases (Application Layer): Implement the application’s specific use cases.
- Interface Adapters (Infrastructure Layer): Handle interactions with external systems (e.g., databases, web APIs).
- Frameworks and Drivers (External Systems): Provide concrete implementations of interfaces (e.g., web servers, third-party libraries).
In Clean Architecture, the core domain is at the center of the application, and external components (such as frameworks or databases) interact with it through well-defined interfaces.
Directory Structure for Hexagonal Architecture
Here’s an example of how a Golang project could be organized using Hexagonal Architecture:
/myapp
├── cmd
│ └── main.go # Main application entry point
│
├── internal
│ ├── domain
│ │ ├── model # Entities, Aggregates, Value Objects
│ │ │ ├── user.go # Example entity
│ │ │ └── product.go # Example aggregate
│ │ │
│ │ ├── service # Business logic, domain services
│ │ │ └── order_service.go
│ │ │
│ │ └── repository # Interfaces for repositories (ports)
│ │ └── order_repo.go
│ │
│ ├── application
│ │ ├── usecase # Application-specific use cases
│ │ │ └── create_order.go
│ │ │
│ │ └── dto # Data Transfer Objects (DTOs)
│ │ └── order_dto.go
│ │
│ ├── infrastructure
│ │ ├── db # Database implementation (adapters)
│ │ │ └── postgres_repo.go
│ │ │
│ │ ├── web # Web server, HTTP adapters
│ │ │ └── handler.go # HTTP handler that calls use cases
│ │ │
│ │ └── third_party # Third-party integrations
│ │ └── payment_gateway.go
│ │
│ └── interfaces
│ ├── api # API routes and controllers
│ │ └── order_handler.go
│ ├── cli # CLI commands (optional)
│ └── events # Event-driven interfaces (optional)
│
└── go.mod # Go module file
Directory Structure for Clean Architecture
Here’s an example of a Clean Architecture project structure:
/myapp
├── cmd
│ └── main.go # Application entry point
│
├── internal
│ ├── core
│ │ ├── model # Entities, Aggregates, Value Objects
│ │ │ ├── user.go # Example entity
│ │ │ └── product.go # Example aggregate
│ │ │
│ │ ├── service # Business logic, domain services
│ │ │ └── order_service.go
│ │ │
│ │ └── repository # Interfaces for repositories (ports)
│ │ └── order_repo.go
│ │
│ ├── application
│ │ ├── usecase # Application-specific use cases
│ │ │ └── create_order.go
│ │ │
│ │ └── dto # Data Transfer Objects (DTOs)
│ │ └── order_dto.go
│ │
│ ├── infrastructure
│ │ ├── db # Database implementation (adapters)
│ │ │ └── postgres_repo.go
│ │ │
│ │ ├── web # Web server, HTTP adapters
│ │ │ └── handler.go # HTTP handler that calls use cases
│ │ │
│ │ └── third_party # Third-party integrations
│ │ └── payment_gateway.go
│ │
│ └── interfaces
│ ├── api # API routes and controllers
│ │ └── order_handler.go
│ ├── cli # CLI commands (optional)
│ └── events # Event-driven interfaces (optional)
│
└── go.mod # Go module file
Conclusion
In this article, we explored Domain-Driven Design (DDD) and how it integrates with Hexagonal Architecture and Clean Architecture to help structure applications in Golang. By focusing on the domain, applying strategic architectural patterns, and organizing code into layers, we can manage complexity, increase maintainability, and ensure scalability.
Remember, these principles can be applied in any language, but Golang’s simplicity and performance make it particularly well-suited for clean, scalable architectures. Happy coding!