我可以在网上找到它的任何实现方法,实际上并没有为您提供一种与该方法无关的实用框架。
我已经看到一些解决问题的建议:
使存储库方法原子化
使用例原子化
它们都不是理想的。
案例1:大多数用例不仅仅依靠一种存储库方法来完成工作。当您“下订单”时,您可能必须调用“订单存储库”的“插入”方法和“用户存储库”的“更新”方法(例如:扣除商店信用额)。如果“插入”和“更新”是原子的,那将是灾难性的-您可以下订单,但实际上无法使用户为此付费。或让用户付款,但订单失败。都不是理想的。
情况#2:没有更好。如果每个用例都生活在一个筒仓中,则可以使用它,但是除非您要重复代码,否则您常常会发现自己拥有取决于其他用例操作的用例。
假设您有一个“下订单”用例和一个“给予奖励积分”用例。两种用例均可独立使用。例如,当老板在系统发布日周年纪念日登录时,老板可能希望向系统中的每个用户“提供奖励积分”。当然,每当用户进行购买时,您当然都会使用“下订单”用例。
现在,系统启动十周年了。您的老板决定-“好的Jimbo-对于2018年7月,每当有人下订单时,我都想给我奖励积分”。
为了避免直接更改此一次性想法的“下订单”用例(可能在明年被放弃),您决定创建另一个用例(“促销中的下订单”),该用例仅称为“下订单”和“赠送积分”。精彩。
只有...你不能。我的意思是,你可以。但是你回到正题。您可以保证“下订单”是否成功,因为它是原子的。并且您可以保证“给与奖励积分”是否由于相同的原因而成功。但是,如果其中一个失败,则您将无法扮演另一个角色。它们不共享相同的事务上下文(因为它们在内部“开始”和“提交” /“回滚”事务)。
上面的场景有几种可能的解决方案,但是它们都不是很“干净”的(想到工作单元-在用例之间共享工作单元将解决此问题,但是UoW是一个丑陋的模式,仍然存在知道哪个用例负责打开/提交/回滚事务的问题)。
据我了解,JWT 刷新令牌的典型(和简化)用法如下:
因此,当用户不小心泄露密码或丢失设备时,他可以简单地更改密码,这将导致所有先前发布的刷新令牌失效。
然而,这里明显的安全漏洞是短期访问令牌在接下来的 10 分钟内仍然完全有效。而 10 分钟,无论时间多短,对于恶意用户来说仍然是足够的时间造成一些损害。
我能想到的唯一可能的解决方案是:
维护访问令牌的黑名单或白名单。这使得刷新令牌的使用看起来非常多余。如果我们要在每次请求时访问数据库或保留一个列入黑名单的访问令牌的缓存列表,那么拥有刷新令牌的意义何在?
缩短访问令牌的有效期(例如:每 1 分钟而不是每 10 分钟)。这并不能解决问题本身,它只是做了一些损害控制,因为它缩短了恶意用户必须造成损害的时间窗口。每分钟访问数据库以获取新的访问令牌似乎并不比每次请求访问数据库好多少。
说有一张桌子.该表用于跟踪书的章节.该表具有以下结构:
CREATE TABLE Chapters(
id INT PRIMARY KEY NOT NULL,
storyId INT references Books(id)
title TEXT NOT NULL,
body TEXT NOT NULL
);
Run Code Online (Sandbox Code Playgroud)
现在,在此表中,body列将包含一个大的,正文的正文.想象一下平均章节在平均小说中有多大,你会得到一个粗略的想法.它可以容纳数百千字节(甚至一兆字节)的字符串数据.
现在,当然有一些情况你不需要章节的"正文",但需要其他东西,如标题.例如,如果我试图建立一本书中章节的"书籍索引",我会执行如下查询:
SELECT title FROM Chapters WHERE storyId = 1
Run Code Online (Sandbox Code Playgroud)
当然,查询会给我回复故事章节的标题.但是,查询是否会使用大量内存,因为它必须将生成的行(列和所有行)加载到内存中,并且表中的每一行都有一个"重"列("正文"列)?
我问这个是因为(根据我的理解 - 如果我错了,请纠正我)这就是它在文档存储数据库中的工作方式.MongoDB中的每一行(或"文档")必须首先加载到内存中,即使您只想从中返回单个字段.因此,如果我在MongoDB中执行类似的查询,它会通过将大的"body"字段加载到内存中来"浪费"内存,即使我想要返回的唯一字段是"title"字段.
对于大多数SQL实现,这些问题是否相同?我特别要求PostgreSQL,但我也有兴趣知道MySQL是否有不同的方式.
让我们考虑您的典型Web应用程序。在MVC应用程序中,您最终可能希望引入一个“服务”层,该层抽象出诸如用户注册之类的复杂业务逻辑。因此,在您的控制器中,您将传递一个services.User结构实例,并只需对其调用Register()方法。
现在,如果services.User仅仅是一个结构,我们可以拥有一个相对简单的源代码结构,如下所示:
- [other directories here]/
- services/
- user.go
- [other service structs here]
- main.go
Run Code Online (Sandbox Code Playgroud)
并且services/user.go看起来像这样:
package services
type User struct { ... }
func NewUserService(){ ... }
func (u User) Register() { ... }
Run Code Online (Sandbox Code Playgroud)
到目前为止,所有这些都相当容易阅读。假设我们将其进一步发展。本着使我们的Web应用程序易于测试的精神,我们将所有Service结构转换为Service接口。这样,我们可以轻松模拟它们以进行单元测试。为此,我们将创建一个“ AppUser”结构(用于实际应用程序)和一个“ MapUser”结构(用于模拟目的)。将接口和实现放在同一个services目录中很有意义-毕竟它们仍然是service代码。
services现在,我们的文件夹如下所示:
- services/
- app_user.go // the AppUser struct
- [other services here]
- map_user.go // the MapUser struct
- [other services here]
- user.go // the …Run Code Online (Sandbox Code Playgroud)