Domain Layer
The innermost layer of your application containing pure business logic, free of databases, frameworks, and infrastructure.
The domain layer is where your business rules live, isolated from everything else. There's no database code, HTTP handlers, or framework internals. You use dedicated types to model the problem domain well.
Business logic is the hardest part to get right and the most expensive to change. When it's tangled with infrastructure, updating code related to the database or API forces you to touch rules that had nothing to do with the change. The domain layer removes that coupling. Things like storage, transport, and external services sit in outer layers and depend on the domain, never the other way around.
In practice, a domain layer is a Go package containing aggregates, entities, and value objects. They use unexported fields, constructors with validations, and methods that protect invariants. The public API makes sure you can't create invalid state. Because encapsulation in Go is package-level, the domain needs its own package to enforce these rules.
Not every module needs a domain layer. If your logic is mostly CRUD with little validation, the application layer is enough. But when invariants get complex, the domain layer is worth it. The tests there are pure unit tests: fast, focused, and free of infrastructure setup. TDD works really well here.
The trade-off is more code you need to write: constructors, getters, and explicit mapping between layers. For complex domains, that investment pays off in fewer bugs and code that's easier to understand. For simple services, it's often overengineering.
The Domain-First Approach can help you kick off bigger projects. Instead of worrying about the database schema, you focus on the problem domain and get it right. Later, implementation details are trivial to add.
References
- Combining DDD, CQRS, and Clean Architecture in Go — Shows how to build a domain layer that hides business complexity. Covers moving if statements from the application layer to the domain layer where logic belongs.
- Introduction to DDD Lite: When microservices in Go are not enough — Introduces working on the domain layer for 2-4 weeks with just an in-memory database, deferring the database choice while exploring the domain model.
- How to implement Clean Architecture in Go (Golang) — Explains how to introduce a domain layer that holds just the business logic when combining Clean Architecture with DDD patterns.
- Microservices test architecture. Can you sleep well without end-to-end tests? — The domain layer is where the most complex logic lives, but tests here should be the simplest to write and run fast. Aim for high test coverage in the domain layer.
- The Repository pattern in Go: a painless way to simplify your service logic — Shows how the domain layer ensures you can't do anything stupid by enforcing business rules, removing the need for scattered validation.
- Repository secure by design: how to sleep better without fear of security vulnerabilities — Demonstrates putting access control logic in the domain layer to enforce who can see a training. The repository implements what the domain layer defines.
- How to use basic CQRS in Go — Covers moving validations to the domain layer and how the domain handles business logic that command handlers orchestrate.
- When using Microservices or Modular Monolith in Go can be just a detail? — Describes the application layer as glue for the domain layer. Shows how both microservices and monolith architectures share the same domain layer.
- Auto-generated C4 Architecture Diagrams in Go — Discusses identifying which components belong to the application or domain layer when generating C4 architecture diagrams.
- Is Clean Architecture Overengineering? — Extensive discussion of the domain layer as an optional pure business logic layer. Covers when to introduce it and when it adds unnecessary complexity.
- AMA #1: Clean Architecture, Learning, Event-Driven, Go — Discusses when to use a domain layer, noting it's fine to skip it for simple modules. Different modules may or may not need a domain layer based on complexity.
- How to Know If your Software Is Overcomplicated or Oversimplified? — Sometimes after implementation you realize the domain layer is mostly boilerplate. In that case, simplify because there's no benefit.
- When you shouldn't use frameworks in Go — Discusses introducing transport DTOs to have encapsulation in the domain layer, and how this relates to clean architecture and framework choices.