我看到几个人在谈论功能核心和命令式 shell 以及它们与单元测试、避免模拟等的关系……但是,在领域逻辑很少和副作用较多的情况下,我看不到重构情况。重构和单元测试这样的场景的好方法是什么:
function createOrder(userInfo, addressInfo, orderInfo) {
const userExists = this.userApi.findUser(userInfo.email);
if(!userExists) this.userApi.createUser(userInfo);
else this.userApi.updateUser(userInfo);
const adressExists = this.userAdressApi.findAdress(addressInfo);
if(!adressExists) this.userAdressApi.createUserAdress(userInfo.email, addressInfo);
else this.userAdressApi.updateUserAdress(userInfo.email, userAdressInfo);
this.orderApi.create(orderInfo);
}
“功能核心,命令式 shell” 的要点是将通常与业务逻辑相关的所有繁重决策逻辑实现为纯函数,然后拥有一个处理所有不纯内容的命令式 shell。
OP中的代码几乎没有逻辑,只有很少的部分完全与I/O相关。OP 中没有域模型,而是一个
事务脚本。此外,这三个代码块在某种程度上是不相关的,因此它们可以并行执行。因此,引用格特鲁德·斯坦因的话,“那里不存在”。
如果您确实愿意,您可以将那个检查记录并相应更新插入的小操作转变为可重用的操作(如果您愿意),但这似乎不值得。我不知道OP是哪种语言,但在Haskell中可能是这样的:
readAndUpsert :: Monad m => m Bool -> m b -> m b -> m b
readAndUpsert r u c = do
b <- r
if b then u else c
单元测试几乎太简单了,但如果你想测试它,你可以使用确定性 monad 来测试它,最简单的是使用 Identity monad。
这是一个组合示例,它不是从数据库读取,而是从控制台获取输入并打印到控制台,而不是写入数据库:
ghci> readAndUpsert (("yes" ==) <$> getLine) (putStrLn "update") (putStrLn "create")
yes
update
ghci> readAndUpsert (("yes" ==) <$> getLine) (putStrLn "update") (putStrLn "create")
no
create
此组合检查在控制台键入的输入是否为 yes
。如果是,则执行更新;否则,它会执行创建。一个相关的例子可能是一个
用于幂等创建的小API。通常你不需要花这么大的力气,但有这个选择是很好的。
一个常见的教训是,一旦您开始从所有 I/O 中理清实际的业务决策逻辑,通常就剩下不多了。