Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 4

Mapping Business Terminology to Go Types

Recommended resources

Welcome back. In our last lesson, we did the foundational work of extracting a Ubiquitous Language from our B2B SaaS subscription business requirements. We now have a shared glossary of key terms like Subscription, Customer Order, and Invoice. This glossary is our source of truth, aligning business and development.

Today, we will make that language tangible. The learning objective is to bridge the gap between our conceptual glossary and the codebase by creating placeholder Go types that directly map to our business terminology. This is the first and most crucial act of implementation in DDD: ensuring the code speaks the Ubiquitous Language. We won't be implementing the full business logic yet, but we will lay the structural foundation upon which all future logic will be built.

The Core Principle: Aligning Code with the Domain

Before writing a single line of code, let's establish the guiding principle. Domain-Driven Design, when stripped of all ceremony, is built on one core idea: the structure and language of your code should directly match the business domain.

DDD in Go Without the Bureaucracy: Aggregates, Not Abstractions - DEV Community

This article gets straight to the point, cutting through the jargon often associated with DDD. It emphasizes a pragmatic, Go-idiomatic approach.

Please read the introductory section and the part titled What DDD Actually Is. The key takeaway is the direct mapping: if the business talks about orders and line items, your code should contain types named Order and LineItem.

This principle is deceptively simple but profoundly powerful. It means that when a domain expert discusses a "subscription amendment," a developer can immediately point to a Subscription type and an Amend method in the code. This one-to-one mapping eliminates the "translation debt" we discussed previously and makes the codebase a living model of the business itself.


From Nouns to Structs: The First Translation

Our Ubiquitous Language glossary is filled with nouns: Subscription, CustomerOrder, Invoice, RevenueContract. In Go, the most direct translation of a noun representing a complex concept is a struct.

Let's start by creating the most basic placeholders for our core concepts. In a new Go project, we would create a domain directory. This directory will become the heart of our application, containing the pure business logic, untainted by infrastructure concerns like databases or web servers.

Inside this domain directory, we can create files like subscription.go, customer_order.go, and so on.

domain/customer_order.go

package domain

// CustomerOrder represents a request from a customer for a new subscription
// or a change to an existing one. For now, it's just a placeholder.
type CustomerOrder struct {
    // Fields will be added as we discover more about the domain.
}

domain/subscription.go

package domain

// Subscription represents the ongoing service provided to a customer.
// It is the central concept in our billing system.
type Subscription struct {
    // Fields will be added shortly.
}

These are simple, empty structs, but they are incredibly significant. We have officially begun to build the model. The file names and type names directly reflect our Ubiquitous Language. This is a practice you'll see in many Go DDD projects.

How To Implement Domain-Driven Design (DDD) in Go

This video demonstrates the creation of basic domain types in Go. While it uses the term "entity," which we will define formally later, the process of creating the initial struct is what's important here.

Watch the segment from creating entities. Notice how the concepts of Person and Item are directly translated into Person and Item structs in their own files. This is the same simple, direct mapping we are applying to our own domain.


Beyond Primitives: Richer Domain Types

As an experienced engineer, you know that modeling everything with primitive types like string and int can lead to ambiguity and bugs. Does a string field represent a name, an email, or a unique ID? DDD encourages us to create more expressive types, even for simple values.

Instead of ID string, we can define type SubscriptionID string. This has several advantages:

  • Clarity: It's immediately clear what this value represents.
  • Type Safety: The Go compiler will prevent you from accidentally assigning a CustomerID to a SubscriptionID field, even if they are both strings underneath.
  • Encapsulation: It provides a natural home for validation logic. A function NewSubscriptionID can ensure the ID format is always correct.

Let's watch a short clip that explains this idea of moving from primitives to domain-specific types.

Domain Driven Design (DDD) in Golang!?

The presenter in this video does an excellent job of explaining the "why" behind creating custom types for IDs, emails, and other domain values.

Please watch from value types to see the concept introduced, and continue through the section on factory methods, where he demonstrates creating a constructor that validates the primitive value.

Applying this to our Subscription struct, we can start to flesh it out with more meaningful types. A subscription has an ID, it belongs to a customer, and it has a status.

package domain

import "time"

type SubscriptionID string
type CustomerID string // Represents the unique ID of a customer.

// SubscriptionStatus represents the lifecycle state of a subscription.
type SubscriptionStatus string

// Defines the possible statuses for a subscription.
const (
	StatusTrialing SubscriptionStatus = "trialing"
	StatusActive   SubscriptionStatus = "active"
	StatusCanceled SubscriptionStatus = "canceled"
	StatusPastDue  SubscriptionStatus = "past_due"
)

// Subscription represents the ongoing service provided to a customer.
type Subscription struct {
	ID         SubscriptionID
	CustomerID CustomerID
	Status     SubscriptionStatus
	CreatedAt  time.Time
}

Look at how much more expressive this is than using raw strings. The SubscriptionStatus with its const block serves as explicit documentation of the possible states, directly reflecting the business rules. This pattern of using a custom string type for enums is a common and effective idiom in Go.


Organizing Your Domain Code

As our domain model grows, we need a way to organize it. Where do all these files and types live? A well-structured project directory is critical for maintaining clarity and enforcing boundaries.

The image below shows a common project layout for a Go application following DDD principles.

This diagram illustrates a typical project layout for a DDD-based Go service. Notice the clear separation of `domain`, `infrastructure`, and other concerns.

For now, the key takeaway is the top-level domain package. This is where our subscription.go, customer_order.go, and other core business logic files will reside. The subdirectories like model/entity and model/valueobject hint at a more refined organization system, which we will explore in detail in Modules 4 and 5. For now, placing our types directly within the domain package is a perfectly valid starting point.

To see how these concepts come together in a more complete example, let's look at how an Order and its related types are defined in a pragmatic Go style.

DDD in Go Without the Bureaucracy: Aggregates, Not Abstractions - DEV Community

The same article from before provides a complete, end-to-end example of an Order aggregate. It's a fantastic reference for how to structure these domain types.

Review the entire section Worked Example. You don't need to absorb every detail of the logic yet. Instead, focus on how the business concepts are represented: Status becomes a custom type with a const block. LineItem becomes its own struct, representing a component of an Order. The Order struct itself is composed of these more granular, domain-specific types. This structure is exactly what we are aiming for with our own Subscription model.


Conclusion

In this lesson, we took the critical step of transforming our abstract Ubiquitous Language into concrete Go code. We established the fundamental principle of mirroring the domain's language and structure in our software, and we created our first set of placeholder types.

Here are the key takeaways:

  • Direct Mapping: The nouns from your Ubiquitous Language glossary should map directly to types (often structs) in your domain package.
  • Embrace Custom Types: Avoid overusing primitive types. Create specific types like SubscriptionID or SubscriptionStatus to increase clarity, provide type safety, and create a home for future validation logic.
  • Domain-Centric Structure: All core business logic and its corresponding types belong in a dedicated domain package, isolated from application and infrastructure concerns.

We now have the skeleton of our domain model. Our Subscription struct is no longer an empty placeholder but a type with a defined structure that speaks the language of our business.

In our next lesson, we will shift our focus from the static structure of the domain to its dynamic behavior. We will simulate a powerful collaborative modeling technique called EventStorming to discover the domain events that drive our subscription lifecycle. These events will ultimately become the catalyst for the methods we'll add to the types we created today.