为了账号安全,请及时绑定邮箱和手机立即绑定

Golang 和 DDD 领域建模

Golang 和 DDD 领域建模

Go
largeQ 2023-06-12 14:21:08
我最近一直在研究领域驱动设计,必须说这种类型的架构设计触发了我的一些东西。当我尝试将它的概念应用到我的 Go 项目时,我遇到了一些障碍。以下是一些示例方法,但我不确定要使用哪种方法。项目结构摘录:├── api/├── cmd/├── internal/|   ├── base/|   |   ├── eid.go|   |   ├── entity.go|   |   └── value_object.go|   ├── modules/|   |   ├── realm/|   |   |   ├── api/|   |   |   ├── domain/|   |   |   |   ├── realm/|   |   |   |   |   ├── service/|   |   |   |   |   ├── friendly_name.go|   |   |   |   |   ├── realm.go|   |   |   |   |   └── realm_test.go|   |   |   |   └── other_subdomain/|   |   |   └── repository/|   |   |       ├── inmem/|   |   |       └── postgres/所有方法通用:package realm // import "git.int.xxxx.no/go/xxxx/internal/modules/realm/domain/realm"// base contains common elements used by all modulesimport "git.int.xxxx.no/go/xxxx/internal/base"方法#1:type Realm struct {   base.Entity   FriendlyName FriendlyName}type CreateRealmParams struct {    FriendlyName string}func CreateRealm(id base.EID, params *CreateRealmParams) (*Realm, error) {   var err error   var r = new(Realm)   r.Entity = base.NewEntity(id)   r.FriendlyName, err = NewFriendlyName(params.FriendlyName)   return r, err}type FriendlyName struct {    value string}var ErrInvalidFriendlyName = errors.New("invalid friendly name")func (n FriendlyName) String() string { return n.value }func NewFriendlyName(input string) (FriendlyName, error) {    if input == "" {        return ErrInvalidFriendlyName    }    // perhaps some regexp rule here...    return FriendlyName{value: input}, nil}使用这种方法,我认为从长远来看会有很多重复的代码,但至少 FriendlyName 值对象根据 DDD 要求是不可变的,并且可以附加更多方法。这里的友好名称类型只是一个字符串,但不可变。这个结构让我想起了 Java 代码……在查找领域时,存储库层是否应该使用域模型中的设置方法来构造领域聚合?我尝试将 DTO 实现放置在同一个包 (dto_sql.go) 中,该包对领域聚合进行编码/解码,但是将这个问题放在域包中感觉有点不对劲。如果您面临与我相同的问题,知道任何其他方法或有任何需要指出的,我将非常有兴趣收到您的来信!
查看完整描述

1 回答

?
aluckdog

TA贡献1847条经验 获得超7个赞

你的选项 #1 对我来说看起来最好,但有一些额外的观察(注意:我会坚持你的命名,这会使其中一些看起来有点矫枉过正......再次重要的是想法

  • 而不是Entity我会说Realm代表一个AggregateRoot.

  • 这可以是隐式的,也可以内联一个base.AggregateRoot.

  • 聚合根是域的访问点,并确保其状态始终一致。

  • 因此内部状态Realm应该是不可变的。状态变化通过函数发生。

  • 除非真的微不足道并且不太可能改变,否则我会FriendlyName在单独的文件中实现值对象。

  • 域的一部分也是 a ,RealmRepository但这仅提供一个接口。

现在我正在使用 CQRS,它是对您的代码片段中显示的内容的扩展。在这个:

  • ChangeFriendlyName应用层中可能有一个命令处理程序。

  • 处理程序可以访问存储库实现,例如InMemRealmRepository

  • 可能会将 a 传递CreateRealmParams给命令,然后命令进行验证。

  • Realm处理程序逻辑可能从从数据库中获取聚合开始。

  • 然后构造一个新的FriendlyName(也可以封装在一个Realm函数调用中)。

  • Realm更新状态和排队事件的函数调用FriendlyNameChanged

  • 命令处理程序将更改保存到 InMemory 数据库。

  • 只有在没有错误的情况下,命令处理程序才会Commit()在聚合上调用。

  • 一个或多个排队的事件现在发布,例如通过EventBus,在需要的地方处理。

至于选项 #1 的代码有些变化(希望我做对了)..

realm.go - 聚合根

type Realm struct {

   base.AggregateRoot


   friendlyName FriendlyName

}


// Change state via function calls. Not shown: event impl, error handling.

// Even with CQRS having Events is entirely optional. You might implement

// it solely to e.g. maintain an audit log.

func (r *Realm) ChangeFriendlyName(name FriendlyName) {

   r.friendlyName = name

   

   var ev = NewFriendlyNameChanged(r.id, name)


   // Queue the event.

   r.Apply(ev)

}


// You might use Params types and encapsulate value object creation,

// but I'll pass value objects directly created in a command handler.

func CreateRealm(id base.AID, name FriendlyName) (*Realm, error) {

   ar := base.NewAggregateRoot(id)


   // Might do some param validation here.


   return &Realm{

       AggregateRoot: ar,

       friendlyName: name,

   }, nil

}

friendlyname.go - 值对象


type FriendlyName struct {

    value string

}


// Domain error. Part of ubiquitous language.

var FriendlyNameInvalid = errors.New("invalid friendly name")


func (n FriendlyName) String() string { return n.value }


func NewFriendlyName(input string) (FriendlyName, error) {

    if input == "" {

        return FriendlyNameInvalid

    }

    // perhaps some regexp rule here...


    return FriendlyName{value: input}, nil

}


查看完整回答
反对 回复 2023-06-12
  • 1 回答
  • 0 关注
  • 148 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信