go中的存储库模式和连接表
来源:stackoverflow
时间:2024-03-17 19:24:29 216浏览 收藏
在领域驱动设计中,处理实体之间的连接表时面临着挑战。直接连接表虽然简单,但效率低下。而将实体合并到一个接口或构建封装实体来解决此问题,可能会导致实体包含许多空参数。因此,需要寻找一种干净且有效的解决方案来实现连接表中的查询。
我目前正在尝试围绕域驱动设计、实体、服务、存储库构建我的应用程序......
所有基本的增删改查操作都很简单,基本上 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学习网公众号!
-
502 收藏
-
502 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
139 收藏
-
204 收藏
-
325 收藏
-
477 收藏
-
486 收藏
-
439 收藏
-
357 收藏
-
352 收藏
-
101 收藏
-
440 收藏
-
212 收藏
-
143 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 508次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习