Domain driven design 从ddd聚合查询远程rest服务

Domain driven design 从ddd聚合查询远程rest服务,domain-driven-design,ddd-service,dddd,Domain Driven Design,Ddd Service,Dddd,我读过关于双分派模式的内容,它允许将服务接口传递到聚合方法中: 在我的域中,我有一个BitbucketIntegrationaggregate,它是一个远程bitbucket帐户的本地副本,包含一些其他特定于域的数据。现在,我必须同步存储库和团队等。。从云计算开始,您就可以在其上执行业务操作。在我的第一个实现中,我使用一个服务访问Bitbucket云,然后设置聚合的存储库、团队和帐户。这样,我就有了一个混合了贫血域模型的DDD,因为聚合状态的一半是使用服务中类似setter的方法设置的。通过双重

我读过关于双分派模式的内容,它允许将服务接口传递到聚合方法中:

在我的域中,我有一个
BitbucketIntegration
aggregate,它是一个远程bitbucket帐户的本地副本,包含一些其他特定于域的数据。现在,我必须同步存储库和团队等。。从云计算开始,您就可以在其上执行业务操作。在我的第一个实现中,我使用一个服务访问Bitbucket云,然后设置聚合的存储库、团队和帐户。这样,我就有了一个混合了贫血域模型的DDD,因为聚合状态的一半是使用服务中类似setter的方法设置的。通过双重分派,我可以将例如
BitbucketService
接口传递到方法参数中。通过这种方式,聚合可以更好地保护其不变量,因为某些数据只能通过连接到rest服务进行验证(例如,如果聚合的
accessToken
bitbucketAccount
存储库
同步),这是服务的责任。还有一件事是,我的聚合中有一个
accessToken
字段,这只是一个技术问题

在ddd聚合中保留远程资源副本有什么推荐模式吗?另外,如何避免技术方面的影响?或者第一种使用域服务的方法是否足够好

现在代码看起来像:

class BitbucketIntegration extends Aggregate<UUID> {

    accountId: BitbucketId 
    repos: List<Repository>
    localData: ...
    // ... and more

    Single integrateWith(accessToken, queryService) {
        var id = queryService.getAccountAsync(accessToken);
        var repos = queryService.getReposAsync(accessToken);
        return Single.zip(id, repos, 
                (i, r) -> new BitbucketIntegratedEvent(accessToken, i, r))
            .onSubscribe(event -> apply(event))
    }

    Observable doSomeBusinessLocally(data) { ... return events; } 

    // this is triggered by a saga
    Single pollForChanges(queryService) {
        var dataFromRemote = queryService.synchronizeAsync(this.accessToken);
        ....
        return event;
    }
}

class CommandHandler {
    queryService: BitbucketService

    Completable handle(integrateCmd) {
        aggregate = repo.get(integrateCmd.id);
        return aggregate.integrateWith(integrateCmd.accessToken, queryService)
            .flatMap(event -> repo.store(event));
    }
}
类BitbucketIntegration扩展了聚合{
accountId:BitbucketId
回购协议:清单
本地数据:。。。
//……还有更多
单集成(accessToken、queryService){
var id=queryService.getAccountAsync(accessToken);
var repos=queryService.getReposAsync(accessToken);
返回Single.zip(id、repos、,
(i,r)->新的BitbucketIntegratedEvent(accessToken,i,r))
.onSubscribe(事件->应用(事件))
}
可观察的DoSomeBusinessLocal(数据){…返回事件;}
//这是由一个传奇故事引发的
单轮询更改(查询服务){
var datafromrote=queryService.synchronizeAsync(this.accessToken);
....
返回事件;
}
}
类命令处理程序{
查询服务:BitbucketService
可完成句柄(integrateCmd){
聚合=repo.get(integrateCmd.id);
返回aggregate.integrateWith(integrateCmd.accessToken,queryService)
.flatMap(事件->回购商店(事件));
}
}
作为旁注,我只查询比特桶

编辑:
Martin Fowler写道,包括反腐败层的定义,该层将远程资源表示转换为域类型。

如果您将基础设施服务注入聚合(通过构造函数或方法调用),那么您将不再拥有域模型。这甚至包括在域层中定义了接口的服务。它会影响可测试性,并引入对基础架构的依赖。它还打破了规则,迫使聚合知道它并不真正需要的东西


解决方法是之前调用服务,并将结果传递给聚合的方法(即在应用层)。

我同意我不会使用纯函数,但我认为这不会影响可测试性,因为我可以轻松模拟服务,并将模拟作为参数传递。我见过一些例子,其中域服务称为基础设施服务,并返回域对象。或者它是一种反模式?@DavidSzalai当然它会影响可测试性,因为你被迫使用moking;依赖于其他组件的组件不太容易测试(不稳定)@DavidSzalai如果域层依赖于基础结构(即调用),那么是的,它是一个反模式。@DavidSzalai请尽量保持域层的纯净。如果你被迫依赖于基础设施,那么你应该修改你的架构以达到这个目的。我也要读埃文的书(只是读了一个简短的版本),我想这将涵盖缺失的部分。顺便说一句,我想我刚刚从马丁·福勒那里找到了一篇关于类似问题的文章:。它还使用反腐败层和网关分离域和外部系统。