当前位置:首页 > 文章列表 > Golang > Go问答 > go中的存储库模式和连接表

go中的存储库模式和连接表

来源:stackoverflow 2024-03-17 19:24:29 0浏览 收藏

在领域驱动设计中,处理实体之间的连接表时面临着挑战。直接连接表虽然简单,但效率低下。而将实体合并到一个接口或构建封装实体来解决此问题,可能会导致实体包含许多空参数。因此,需要寻找一种干净且有效的解决方案来实现连接表中的查询。

问题内容

我目前正在尝试围绕域驱动设计、实体、服务、存储库构建我的应用程序......

所有基本的增删改查操作都很简单,基本上 1 个实体 => 1 个表 => 1 个存储库 => 1 个服务

但我无法找出处理两个实体之间的联接表的最干净的方法。

可以在连接内按表进行 1 次查询,这将是“干净的”(可以这么说),但效率不高,因为简单的连接会导致一次查询。

在这种模式下,表在哪里连接?

  • 我一直在考虑现在构建可以封装答案的实体,但可以有效地为 1 个查询创建 1 个实体 + 存储库...

  • 我还认为将多个实体合并到一个接口中可能会部分解决这个问题,但这会导致我的实体出现许多空参数(在执行时很少需要所有选项卡中的所有字段)连接)

解决这个问题的正确方法/模式是什么,适合 ddd 或至少是干净的?

-- 编辑示例:

type user struct {
    id          int       `db:"id"`
    projectid      int    `db:"project_id"`
    roleid      int       `db:"role_id"`
    email       string    `db:"email"`
    firstname   string    `db:"first_name"`
    lastname    string    `db:"last_name"`
    password    string    `db:"password"`
}

type userrepository interface {
    findbyid(int) (*user, error)
    findbyemail(string) (*user, error)
    create(user *user) error
    update(user *user) error
    delete(int) errorr
}

type project struct {
    id          int       `db:"id"``
    name   string    `db:"name"`
    description    string    `db:"description"`
}

这里我有一个简单的用户存储库。我对“项目”表有类似的东西。可以创建表、获取项目的所有信息、删除等

如您所见,userid 具有其所属项目 id 的外键。

我的问题是当我需要从用户检索所有信息时,说出“项目名称”和描述。 (我实际上表/实体有更多参数)

我需要对 user.project_id 和 project.id 进行简单的连接,并在一次查询中检索用户+项目名称+描述的所有信息。

有时会更复杂,因为会有 3-4 个实体像这样链接。 (用户、项目、项目附加信息、角色等)

当然,我可以进行 n 个查询,每个实体一个。

user := userRepo.Find(user_id)
project := projectRepo.FindByuser(user.deal_id)

这将“工作”,但我正在尝试找到一种方法在一个查询中完成它。因为 user.project_id 和 project.id 上的简单 sql 连接将为我提供查询中的所有数据。


解决方案


对于 join 部分,你的问题很容易回答,但是对于 ddd 来说,当前的语言可能性存在很多障碍。但我会尝试一下..

好吧,让我们假设我们正在开发一个支持多语言的教育课程后端,我们需要连接两个表并随后映射到对象。我们有两个表(第一个包含与语言无关的数据,第二个包含与语言相关的数据) 如果您是存储库倡导者,那么您将拥有类似的东西:

// course represents e.g. calculus, combinatorics, etc.
type course struct {
    id     uint   `json:"id" db:"id"`
    name   string `json:"name" db:"name"`
    poster string `json:"poster" db:"poster"`
}

type courserepository interface {
    list(ctx context.context, localeid uint) ([]course, error)
}

然后为 sql db 实现它,我们将得到类似的东西:

type courserepository struct {
    db *sqlx.db
}

func newcourserepository(db *sqlx.db) (courserepository, error) {
    if db == nil {
        return nil, errors.new("provided db handle to course repository is nil")
    }

    return &courserepository{db:db}, nil
}

func (r *courserepository) list(ctx context.context, localeid uint) ([]course, error) {

    const query = `select c.id, c.poster, ct.name from courses as c join courses_t as ct on c.id = ct.id where ct.locale = $1`
    var courses []course
    if err := r.db.selectcontext(ctx, &courses, query, localeid); err != nil {
        return nil, fmt.errorf("courses repostory/problem while trying to retrieve courses from database: %w", err)
    }

    return courses, nil
}

这同样适用于不同的相关对象。您只需要耐心地对对象与基础数据的映射进行建模。我再举一个例子。

type city struct {
    id                      uint            `db:"id"`
    country                 country         `db:"country"`
}

type country struct {
    id   uint  `db:"id"`
    name string `db:"name"`
}

// cityrepository provides access to city store.
type cityrepository interface {
    get(ctx context.context, cityid uint) (*city, error)
}

// get retrieve city from database by specified id
func (r *cityrepository) get(ctx context.context, cityid uint) (*city, error) {

    const query = `select 
    city.id, country.id as 'country.id', country.name as 'country.name',
    from city join country on city.country_id = country.id where city.id = ?`

    city := city{}
    if err := r.db.getcontext(ctx, &city, query, cityid); err != nil {
        if err == sql.errnorows {
          return nil, errnocityentity
        }
        return nil, fmt.errorf("city repository / problem occurred while trying to retrieve city from database: %w", err)
    }

    return &city, nil
}

现在,一切看起来都很干净,直到您意识到 go 实际上(就目前而言)不支持泛型,而且在大多数情况下人们不鼓励使用反射功能,因为它会使您的程序变慢。为了让您完全不安,想象从这一刻起您需要事务功能......

如果您来自其他语言,您可以尝试使用类似的方法来实现它:

// unitofwork is the interface that any unitofwork has to follow
// the only methods it as are to return repositories that work
// together to achieve a common purpose/work.
type unitofwork interface {
    entities() entityrepository
    otherentities() otherentityrepository
}

// startunitofwork it's the way to initialize a typed uow, it has a uowfn
// which is the callback where all the work should be done, it also has the
// repositories, which are all the repositories that belong to this uow
type startunitofwork func(ctx context.context, t type, uowfn unitofworkfn, repositories ...interface{}) error

// unitofworkfn is the signature of the function
// that is the callback of the startunitofwork
type unitofworkfn func(ctx context.context, uw unitofwork) error

我故意错过了一个实现,因为它对于 sql 来说看起来很可怕,值得提出自己的问题(这个想法是工作单元的存储库版本在引擎盖下用启动的 tx 装饰),在解决这个问题后,您将拥有更多或更少

err = svc.startUnitOfWork(ctx, uow.Write, func(ctx context.Context, uw uow.UnitOfWork) error {

            // _ = uw.Entities().Store(entity)
            // _ = uw.OtherEntities().Store(otherEntity)

            return nil
        }, svc.entityRepository, svc.otherEntityRepository)

所以这里你到达了最后,在大多数情况下,人们开始说你编写的代码似乎不符合习惯,指的是 that 之类的东西。关键是概念写得太抽象,物化 ddd 是否均匀是一个哲学问题适用于 golang,或者你可以部分模仿它。如果您想要灵活性,请选择数据库一次并使用纯数据库句柄进行操作

根据您要读取的数据,解决方案会有所不同:

如果您想要连接的表形成单个聚合,则只需将它们连接到您的查询中,并始终返回并存储完整的聚合。在这种情况下,您只有根实体的存储库。这可能不是您的场景,因为您说过您有想要加入的其他实体的存储库(除非您有设计问题)。

如果您要连接的表属于不同的有界上下文,则不应连接它们。更好的方法是对每个有界上下文提交一个查询,以便它们保持解耦。这些多个查询将来自不同的地方,具体取决于您的架构:直接来自客户端、来自 api 网关、来自某种应用程序服务等。

如果表属于单个有界上下文,但来自多个聚合,那么最简洁的方法是遵循 cqrs(命令/查询隔离)。简而言之,您可以为查询定义一个特定的接口,其中包含您正在实现的用例所需的输入和输出。这种分离使您摆脱了尝试使用命令基础结构进行查询时遇到的限制(您拥有的一对一实体/存储库关系)。此查询接口的简单实现可能是对现有表进行联接的查询。这是快速且易于实现的,但这意味着您的命令和查询在代码中是分离的,但不是在数据库级别。理想情况下,您将在数据库中创建一个(非规范化)读取模型表,其中包含该特定查询所需的所有列,并在每次更新其中一个源表时更新(这通常是通过域事件完成的)。这允许您使用正确的列、数据格式和索引来优化查询的表,但缺点是,它会在写入和读取模型之间引入一些复杂性和最终的一致性。

本篇关于《go中的存储库模式和连接表》的介绍就到此结束啦,但是学无止境,想要了解学习更多关于Golang的相关知识,请关注golang学习网公众号!

版本声明
本文转载于:stackoverflow 如有侵犯,请联系study_golang@163.com删除
golang:有关接口实现继承的问题 - 代码日志golang:有关接口实现继承的问题 - 代码日志
上一篇
golang:有关接口实现继承的问题 - 代码日志
win7网络连接错误733怎么办?win7连接网络显示错误733解决方法
下一篇
win7网络连接错误733怎么办?win7连接网络显示错误733解决方法
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    542次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    511次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    498次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    484次学习
查看更多
AI推荐
  • AI边界平台:智能对话、写作、画图,一站式解决方案
    边界AI平台
    探索AI边界平台,领先的智能AI对话、写作与画图生成工具。高效便捷,满足多样化需求。立即体验!
    419次使用
  • 讯飞AI大学堂免费AI认证证书:大模型工程师认证,提升您的职场竞争力
    免费AI认证证书
    科大讯飞AI大学堂推出免费大模型工程师认证,助力您掌握AI技能,提升职场竞争力。体系化学习,实战项目,权威认证,助您成为企业级大模型应用人才。
    426次使用
  • 茅茅虫AIGC检测:精准识别AI生成内容,保障学术诚信
    茅茅虫AIGC检测
    茅茅虫AIGC检测,湖南茅茅虫科技有限公司倾力打造,运用NLP技术精准识别AI生成文本,提供论文、专著等学术文本的AIGC检测服务。支持多种格式,生成可视化报告,保障您的学术诚信和内容质量。
    561次使用
  • 赛林匹克平台:科技赛事聚合,赋能AI、算力、量子计算创新
    赛林匹克平台(Challympics)
    探索赛林匹克平台Challympics,一个聚焦人工智能、算力算法、量子计算等前沿技术的赛事聚合平台。连接产学研用,助力科技创新与产业升级。
    665次使用
  • SEO  笔格AIPPT:AI智能PPT制作,免费生成,高效演示
    笔格AIPPT
    SEO 笔格AIPPT是135编辑器推出的AI智能PPT制作平台,依托DeepSeek大模型,实现智能大纲生成、一键PPT生成、AI文字优化、图像生成等功能。免费试用,提升PPT制作效率,适用于商务演示、教育培训等多种场景。
    571次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码