go中的存储库模式和连接表
在领域驱动设计中,处理实体之间的连接表时面临着挑战。直接连接表虽然简单,但效率低下。而将实体合并到一个接口或构建封装实体来解决此问题,可能会导致实体包含许多空参数。因此,需要寻找一种干净且有效的解决方案来实现连接表中的查询。
我目前正在尝试围绕域驱动设计、实体、服务、存储库构建我的应用程序......
所有基本的增删改查操作都很简单,基本上 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学习网公众号!
golang:有关接口实现继承的问题 - 代码日志
- 上一篇
- golang:有关接口实现继承的问题 - 代码日志
- 下一篇
- win7网络连接错误733怎么办?win7连接网络显示错误733解决方法
-
- Golang · Go问答 | 1年前 |
- 在读取缓冲通道中的内容之前退出
- 139浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 戈兰岛的全球 GOPRIVATE 设置
- 204浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何将结构作为参数传递给 xml-rpc
- 325浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何用golang获得小数点以下两位长度?
- 478浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何通过 client-go 和 golang 检索 Kubernetes 指标
- 486浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 将多个“参数”映射到单个可变参数的习惯用法
- 439浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 将 HTTP 响应正文写入文件后出现 EOF 错误
- 357浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 结构中映射的匿名列表的“复合文字中缺少类型”
- 352浏览 收藏
-
- Golang · Go问答 | 1年前 |
- NATS Jetstream 的性能
- 101浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何将复杂的字符串输入转换为mapstring?
- 440浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 相当于GoLang中Java将Object作为方法参数传递
- 212浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何确保所有 goroutine 在没有 time.Sleep 的情况下终止?
- 143浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3211次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3425次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3454次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4563次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3832次使用
-
- GoLand调式动态执行代码
- 2023-01-13 502浏览
-
- 用Nginx反向代理部署go写的网站。
- 2023-01-17 502浏览
-
- Golang取得代码运行时间的问题
- 2023-02-24 501浏览
-
- 请问 go 代码如何实现在代码改动后不需要Ctrl+c,然后重新 go run *.go 文件?
- 2023-01-08 501浏览
-
- 如何从同一个 io.Reader 读取多次
- 2023-04-11 501浏览

