From a DDD perspective, how to handle DB specific types?

Viewed 289

I'm trying to implement DDD in my project and I'm using Firestore as my persistence infrastructure. Firestore in Go has some specific types like *firestore.DocumentRef.

I think that I should put it in my entity struct as it should represent my model, but my entity should also be db agnostic (I think).

So how should I handle from a DDD perspective this entity/struct:

    type Holder struct {
        SocialReason       string                 `firestore:"social_reason" json:"social_reason"`
        Contact            value.Contact          `firestore:"contact" json:"contact"`
        Location           value.Location         `firestore:"location" json:"location"`
        SubmissionDatetime time.Time              `firestore:"submission_datetime" json:"-"`
        UpdateDatetime     time.Time              `firestore:"update_datetime" json:"-"`
        Status             string                 `firestore:"status" json:"status"`
        LastModifier       *firestore.DocumentRef `firestore:"last_modifier" json:"-"`
        CEP                string                 `firestore:"cep,omitempty" json:"cep,omitempty"`
        Country            string                 `firestore:"country" json:"country"`
        Region             string                 `firestore:"region,omitempty" json:"region,omitempty"`
        City               string                 `firestore:"city,omitempty" json:"city,omitempty"`
    }
1 Answers

You should avoid having any kind of implementation-specific technology choices represented in your Domain, unless - possibly, but not likely - they are truly part of the Ubiquitous Language of said domain (which is not the case in your example, it seems).

There's a great blog series about implementing DDD using Golang, with an accompanying codebase that is evolved as the series introduces ever more DDD-related concepts. Check the Wild Workouts example.

You can directly learn from this code, as they are also using Firebase as one of the storage implementations in their Ports and Adapters architecture. They also have SQL and InMemory (useful for testing) storage implementations.

So, how do they represent firestore.DocumentRef outside of the repository impl? Well, as a simple string. See for example GetUser() ..

/internal/users/firestore.go

type db struct {
    firestoreClient *firestore.Client
}

func (d db) GetUser(ctx context.Context, userID string) (UserModel, error) {
    doc, err := d.UserDocumentRef(userID).Get(ctx)

    // More stuff to get the user model.
}

Important to note that for user management they do not use DDD, but basic CRUD (with their simple use cases for user management they have chosen to forego the extra complexity that DDD would introduce).

Examples of full DDD design are in the Trainer and Training domains. There's a bit more complexity here (e.g. in handling transactions), so I refer to The Repository pattern: a painless way to simplify your Go service logic that explains these concepts.

But you can clearly see how things are split up between domain concept (repository interface) and implementation (repository adapter). For instance in the Trainings domain, implementing Hour:

  • /internal/trainer/domain/hour/repository.go --> GetHour()
  • /internal/trainer/adapters/hour_firestore_repository.go --> GetHour()
Related