HATEOAS REST API和域驱动设计,工作流逻辑放在哪里?

HATEOAS REST API和域驱动设计,工作流逻辑放在哪里?,rest,domain-driven-design,hateoas,hypermedia,Rest,Domain Driven Design,Hateoas,Hypermedia,这是一个跟进问题,是对问题的一个简要总结(为了更好地适应我的问题而进行了调整),内容如下: 每个域对象都包含与特定有界上下文(X)中的特定对象关联的业务逻辑。RESTAPI包含将查询或命令的结果转换为通过网络发送的数据的逻辑(例如JSON)。在使用HATEOAS和超媒体时,我们希望使用链接来模拟资源之间的关系。但是,为了确定RESTAPI应该返回哪些链接,通常需要求助于业务逻辑/规则。问题是,这些“工作流规则”在DDD应用程序中属于哪里?他们可能处于不同的有限环境中,只处理工作流规则(可能与X有

这是一个跟进问题,是对问题的一个简要总结(为了更好地适应我的问题而进行了调整),内容如下:

每个域对象都包含与特定有界上下文(X)中的特定对象关联的业务逻辑。RESTAPI包含将查询或命令的结果转换为通过网络发送的数据的逻辑(例如JSON)。在使用HATEOAS和超媒体时,我们希望使用链接来模拟资源之间的关系。但是,为了确定RESTAPI应该返回哪些链接,通常需要求助于业务逻辑/规则。问题是,这些“工作流规则”在DDD应用程序中属于哪里?他们可能处于不同的有限环境中,只处理工作流规则(可能与X有“合作伙伴”式的关系),还是属于X BC?

我不是DDD专家(仅通过Eric Evan的书中的100页),但我可以告诉你在我们的电子商务目录中发生了什么

最初,基于数据和请求用户的角色/权限,我们在应用程序的同一有限上下文中有这样的业务流。我们更改了呈现给用户的状态转换(你说的是链接,但这实际上是它的核心,链接只是呈现状态转换的一种方式)。这工作正常,但执行了大量重复的逻辑。然后,我们添加了一个搜索应用程序,它是一个不同的有界上下文,因为它呈现相同的数据,但在不同的集合/分组中,我们不希望它们失去同步

我们转向了CQRS实现,因此我们的许多业务逻辑都是“预先计算”的,因此它处于类似于“合作伙伴上下文”的位置,作为从写模型到读模型的投影的一部分。我们没有专门存储链接,而是在数据上标记允许的行为。catalog应用程序和search应用程序都使用此读取模型及其行为标志来确定要呈现给客户端的状态转换

有些事情是在请求资源时动态发生的,几乎是在序列化步骤。我们的目标是尽可能多地将其移动到预先计算的位置,但我们无法预先计算(仅因为规模)的是专门基于用户的内容。就像推荐的搜索一样,它使用搜索引擎中的BI数据返回结果。我们可以为每个用户预先计算,但是对于我们的系统来说,现在的数字太大了。因此,我们发送主应用程序计算资源(来自主上下文),并将其传递给另一个合作伙伴上下文,以进一步优化内容


另一个用例是一些链接只呈现给经过身份验证的用户,因此对匿名用户隐藏。我们在主应用程序上下文中这样做,但这开始有点阻碍,因为它们的存在表明请求背后的用户可以在其他应用程序中做什么(如更改密码),而我们的上下文并不是用户可以在其他应用程序中做什么的真正权威。最好将资源交给他们的上下文,并在我们将其返回给用户之前让他们进行处理。我们为此使用的一个简单解决方案是,我们不需要深入链接到外部上下文中的函数,而是链接到该上下文的根资源,并允许它呈现状态转换。这意味着有2个请求,但它会清理逻辑的位置/权限。

工作流实际上由领域知识和hateoas基础设施组成

如果应用程序不是使用超媒体设计的,您甚至可能不需要hateoas部件。但是领域知识仍然存在,它们只存在于最终用户的头脑中。您需要领域知识来验证用户的命令,以防他们忘记它

因此,对于hateoas,我将工作流实现分为两部分:域模型和hateoas基础设施

领域模型表示领域知识。
另一方面,hateoas基础设施是特定于hateoas的,将它们放在域之外

下面是一个java示例,使用springframework hateoas。我将工作流放在将域模型映射到资源的过程中

@Component
public class OrderResourceAssembler extends ResourceAssemblerSupport<Order, OrderResource> {

    @Override
    public OrderResource toResource(Order model) {

        OrderResource resource = mapToResource(model);
        resource.add(orderResourceHref(model).withSelfRel());//add self link
        if (model.isAvailableToCancel()) { //let the model tell
            //add cancel link
            resource.add(orderResourceHref(model).withRel("cancel")); 
        }
        if (model.isAvailableToPay()) { //let the model tell
            //add payment link
            resource.add(new Link("http://www.restfriedchicken.com/online-txn/" + model.getTrackingId(), "payment")); 
        }
        //etc


        return resource;
    }
    // omitted codes
}
@组件
公共类OrderResourceAssembler扩展了ResourceAssembler支持{
@凌驾
公共订单资源存储资源(订单模型){
OrderResource=mapToResource(模型);
resource.add(orderesourcehref(model).withSelfRel());//添加自链接
如果(model.isAvailableToCancel()){//让模型告诉你
//添加取消链接
添加(orderResourceHref(model).withRel(“取消”);
}
如果(model.isAvailableToPay()){//让模型告诉你
//添加付款链接
资源。添加(新链接(“http://www.restfriedchicken.com/online-txn/“+model.getTrackingId(),“payment”);
}
//等
返回资源;
}
//省略代码
}

如果模型本身无法判断,您可以引入specification对象来完成这项工作。

无论资源驻留在何处,都是需要封送对它的访问的同一位置

假设您有一个更大的系统,它有三个有限的上下文:员工管理、客户接纳和服务提供

从员工管理层获取员工信息?由员工管理层决定是否显示可用于发布或删除角色的链接

从客户处获得新提交的服务合同?决定是否显示可用于发布修订或后续详细信息的链接由客户自行决定。当然,员工管理层负责分配处理新服务合同所需的角色,但客户接收部负责了解需要哪些索赔和其他条件,并最终确保一切正常