Introduction: Why Your Go Project Needs DDD?
\n\nHave you ever written a Go service that started with a couple of endpoints and three database tables, only to turn into a monster six months later with mile-long handler.go files and magic numbers scattered throughout the code? Sound familiar. I've been through it too.
The problem with classic Go development is that we think about databases and JSON too early, forgetting the most important thing: business logic. Domain-Driven Design (DDD) comes to the rescue precisely when you want to bring order to the chaos. But how do you apply DDD to Go when the language doesn't spoil us with classes and inheritance? Let's figure it out.
\n\nIn this article, I'll show you how to apply Domain-Driven Design in Go projects without excessive fanaticism, with working code examples and a clear structure. You'll learn how to separate the wheat from the chaff and write code that is understandable not only to the machine but also to the business analyst.
\n\nWhat is DDD and Why Does a Go Developer Need It?
\n\nDomain-Driven Design is not about technology. It's about language. It's about making the code speak the language of the business. If your domain is an online store, the code should have Order, Product, Cart, not DataTransferObject and EntityManager.
Go, with its simplicity and lack of magic, is ideal for DDD. There are no proxy classes or reflection here that hide the essence. In Go, you are forced to be honest: if you have an aggregate, it's an explicit struct; if you have a repository, it's an interface.
\n\nThe main building blocks of DDD that we'll look at:
\n- \n
- Entity — an object with a unique identifier (e.g.,
User). \n - Value Object — an immutable object describing characteristics (e.g.,
Address). \n - Aggregate — a cluster of entities that guarantees consistency (e.g.,
OrderwithOrderItem). \n - Repository — a collection of objects that hides storage details. \n
- Domain Service — an operation that doesn't fit into a single entity. \n
Now let's translate this into Go.
\n\nProject Structure: How Not to Get Lost in Folders
\n\nThe good news: Go doesn't impose any structure. The bad news: without structure, you'll quickly drown. For DDD in Go, I recommend the following code organization:
\n\nyour-project/\n├── cmd/\n│ └── server/\n│ └── main.go\n├── internal/\n│ ├── domain/ # Pure core: entities, rules\n│ │ ├── order.go\n│ │ ├── product.go\n│ │ └── errors.go\n│ ├── application/ # Use cases / application services\n│ │ ├── order_service.go\n│ │ └── product_service.go\n│ └── infrastructure/ # Databases, external APIs, HTTP handlers\n│ ├── postgres/\n│ │ └── order_repository.go\n│ └── http/\n│ └── handler.go\n└── go.mod\n\nNote: domain does not import infrastructure. This is the golden rule of DDD — the domain knows nothing about databases and HTTP. It lives in a vacuum of pure structs and interfaces.
Writing the Domain: Order and Value Objects
\n\nLet's start with the core. Suppose we are building an order management system. Here's what the Order entity might look like:
// internal/domain/order.go\npackage domain\n\nimport (\n "errors"\n "time"\n)\n\ntype OrderStatus string\n\nconst (\n OrderStatusPending OrderStatus = "pending"\n OrderStatusConfirmed OrderStatus = "confirmed"\n OrderStatusCancelled OrderStatus = "cancelled"\n)\n\ntype OrderID string\n\ntype Order struct {\n ID OrderID\n CustomerID string\n Items []OrderItem\n Status OrderStatus\n CreatedAt time.Time\n Upd