Bootstrap

[ Golang 中的 DDD 实践] 仓储

译者:baiyutang

原文:

现在很难想象编写应用程序在运行时不访问存储,可能甚至不需要编写部署脚本,他们需要访问配置文件,在某种方式上,仍然是存储类型。

每当你编写应用解决真实业务世界的问题时,你需要连接数据库、外部接口、一些缓存系统,等等,这是不可避免的。

在某种角度,使用 DDD 模式解决这些需求并不奇怪。当然,DDD 并没有发明“仓库”和很多其他文献中的器具,但是 DDD 变得更清晰。

防腐层

领域驱动设计一个你可以应用在软件开发很多方面和地方的法则。但是,主要的焦点还是应该放在领域层,我们业务逻辑所在的地方。

仓储总代表连接外部世界而保持的技术细节的结构,它已经不属于业务逻辑了。

但是,有时,我们需要从领域层访问仓储。因为领域层是不与其他层通信的底层。我们定义仓储为接口。

import (
	"context"

	"github.com/google/uuid"
)

type Customer struct {
	ID uuid.UUID
	//
	// some fields
	//
}

type Customers []Customer

type CustomerRepository interface {
	GetCustomer(ctx context.Context, ID uuid.UUID) (*Customer, error)
	SearchCustomers(ctx context.Context, specification CustomerSpecification) (Customers, int, error)
	SaveCustomer(ctx context.Context, customer Customer) (*Customer, error)
	UpdateCustomer(ctx context.Context, customer Customer) (*Customer, error)
	DeleteCustomer(ctx context.Context, ID uuid.UUID) (*Customer, error)
}

被我们成为契约的接口,他定义了我们能够在领域调用的方法签名。在上面的例子中,我们能发现定义了 CRUD 方法的简单接口。

当我们定义了仓储像这样的接口,我们就能在领域层内任何地方使用它。它总是预期并返回我们的实体,在这个案例中, 和 (“我”喜欢在 Go 中定义特定的集合,以便为他们附加不同的方法)。

实体不持有一下存储类型的任何信息。没有定义 JSON 结构的 Go 标签,Gorm 列或其他任何类似。为此,我们使用基础设施层。

// domain layer

type CustomerRepository interface {
	GetCustomer(ctx context.Context, ID uuid.UUID) (*Customer, error)
	SearchCustomers(ctx context.Context, specification CustomerSpecification) (Customers, int, error)
	SaveCustomer(ctx context.Context, customer Customer) (*Customer, error)
	UpdateCustomer(ctx context.Context, customer Customer) (*Customer, error)
	DeleteCustomer(ctx context.Context, ID uuid.UUID) (*Customer, error)
}

// infrastructure layer

import (
	"context"

	"github.com/google/uuid"
	"gorm.io/gorm"
)

type CustomerGorm struct {
	ID   uint   `gorm:"primaryKey;column:id"`
	UUID string `gorm:"uniqueIndex;column:uuid"`
	//
	// some fields
	//
}

func (c CustomerGorm) ToEntity() (model.Customer, error) {
	parsed, err := uuid.Parse(c.UUID)
	if err != nil {
		return Customer{}, err
	}
	
	return model.Customer{
		ID: parsed,
		//
		// some fields
		//
	}, nil
}

type CustomerRepository struct {
	connection *gorm.DB
}

func (r *CustomerRepository) GetCustomer(ctx context.Context, ID uuid.UUID) (*model.Customer, error) {
	var row CustomerGorm
	err := r.connection.WithContext(ctx).Where("uuid = ?", ID).First(&row).Error
	if err != nil {
		return nil, err
	}
	
	customer, err := row.ToEntity()
	if err != nil {
		return nil, err
	}
	
	return &customer, nil
}
//
// other methods
//

在上面的例子中,你能看到 的实现片段。