领域驱动设计(三)

·  阅读 1147

背景

分层的目的是把领域的东西与非领域的东西隔离开,按照分层的逻辑,层与层之间的职责更加清晰。架构分层有传统的四层架构,有依赖倒置的四层架构,还有六边形架构.

应用服务作为领域对象的客户,跟领域服务有很大的区别,两者的作用也不一样,千万别混淆这两者的概念。

领域事件,大有用途...

一、传统的四层架构

基础设施层:repository实现(持久化),消息中间件(消息处理),外部接口交互实现,访问缓存等等。

领域层:Entity、Value Object、domain Service、repository接口(不包括实现)、factory等,是项目最核心的业务层。

应用层:业务任务分配、服务编排,事务控制等,与业务无关,其中的应用服务(application service)是领域层对象的客户端。

UI层:主要是跟系统用户交互层,其实像前后端分离的应用,一般是不需要的,如果是使用Spring MVC,其中的controller可以直接放置在应用层。

05D18155-1EFC-40FD-9570-A5D0E405CDA6.png

二、依赖倒置的四层架构

FBDC728A-590E-4821-973C-3C026B0B0580.png

基础设施层依赖领域层,像repository等接口可以放在领域层,而领域层只需要使用接口,其实现放在基础设施层,如此一来,分层的概念好像就不存在了,像是把层摊平了。

三、六边形架构

5A630E12-F95C-4D25-97AC-F34D8889A7AD.png

六边形架构让我们站在更高的角度看到系统架构,左上角的适配器,可以理解成是restful接口、webservice接口等,左边三角型的适配器,可以理解成接收消息接口(MQ消息、领域事件等),右边的适配器可以理解成持久化,访问内存接口,右下角的适配器,可以理解成是发送领域事件或发送MQ消息等,所有这些,都围绕着领域模型(领域层)。

采用哪种架构并没有一定标准,合适就好,当然除了上面说的三种,还有CQRS、事件驱动架构,一般来说,我觉得上面三种架构已经可以应对业务了。

AD983ED7-9421-4776-841C-148F35DBCDF3.png

这个就是典型的六边形架构,application作为应用层,也是领域层的客户端,而domain层,只有领域模型。

port.adapter就是上面六边形架构的端口和适配器,把适配器放在端口的下面,端口更能表达与外部的交互,在adapter下面,有两个,一个是messaging,和消息事件处理有关,一个是persistence,与持久化有关。很明显,这里也没有采用基础设施层。其实分层架构,主要还是怎么简单怎么来。

四、应用服务

应用服务存在于应用层,作为领域对象的客户端,主要负责:任务协调、事务控制,代码实例:

public class ProductApplicationService {


    private TimeConstrainedProcessTrackerRepository processTrackerRepository;
    private ProductOwnerRepository productOwnerRepository;
    private ProductRepository productRepository;

    ...


    // TODO: additional APIs / student assignment


    public void initiateDiscussion(InitiateDiscussionCommand aCommand) {
        ApplicationServiceLifeCycle.begin();


        try {
            Product product =
                    this.productRepository()
                        .productOfId(
                                new TenantId(aCommand.getTenantId()),
                                new ProductId(aCommand.getProductId()));


            if (product == null) {
                throw new IllegalStateException(
                        "Unknown product of tenant id: "
                        + aCommand.getTenantId()
                        + " and product id: "
                        + aCommand.getProductId());
            }


            product.initiateDiscussion(new DiscussionDescriptor(aCommand.getDiscussionId()));


            this.productRepository().save(product);


            ProcessId processId = ProcessId.existingProcessId(product.discussionInitiationId());


            TimeConstrainedProcessTracker tracker =
                    this.processTrackerRepository()
                        .trackerOfProcessId(aCommand.getTenantId(), processId);


            tracker.completed();


            this.processTrackerRepository().save(tracker);


            ApplicationServiceLifeCycle.success();


        } catch (RuntimeException e) {
            ApplicationServiceLifeCycle.fail(e);
        }
    }
   ...
}

复制代码

在产品的应用服务的initiateDiscussion方法里,可以看到,主要是完成任务协调,而不是业务逻辑。可以读者就有疑问,难道上面这些还不算是业务逻辑吗?还真不算,上述代码,首先是通过repository创建product,然后通过product.initiateDiscussion初始化Discussion对象,这里初始化-业务逻辑交给了Product聚合,然后更新该聚合,简单来说,我们基本不会在应用服务看到if-else那些东西。当然,上面的判空不算~。

InitiateDiscussionCommand其实就是传参对象,使用Command模式,让概念更加清晰。

如果这里需要事物,那么要么可以通过类似ApplicationServiceLifeCycle类实现,要么可以通过注解,如Spring 的@Transactional注解。

除此之外,应用服务还可以处理和安全相关的操作。

五、领域事件(Domain Event)

领域事件是一个很强大的建模工具,也就是说,当我们去建模的时候,会去关注一些重要的信息,比如:

当……

如果发生……

发生……时

这些是很有价值的,也应该成为通用语言的一部分。

其实,建模领域事件,我们可以简单把它理解成是,设计模式中的观察者模式,因为领域事件本身确实需要事件发送方,事件订阅者。

public class Product extends Entity {

    …
public BacklogItem planBacklogItem(
        BacklogItemId aNewBacklogItemId,
        String aSummary,
        String aCategory,
        BacklogItemType aType,
        StoryPoints aStoryPoints) {


    BacklogItem backlogItem =
        new BacklogItem(
                this.tenantId(),
                this.productId(),
                aNewBacklogItemId,
                aSummary,
                aCategory,
                aType,
                BacklogItemStatus.PLANNED,
                aStoryPoints);


    DomainEventPublisher
        .instance()
        .publish(new ProductBacklogItemPlanned(
                backlogItem.tenantId(),
                backlogItem.productId(),
                backlogItem.backlogItemId(),
                backlogItem.summary(),
                backlogItem.category(),
                backlogItem.type(),
                backlogItem.storyPoints()));


    return backlogItem;
    }
...
}

复制代码

当创建backlog后,发送创建backlog领域事件。

EDF55D7C-A65C-4989-B3F1-9870BC22729D.png

领域事件订阅者,所以这就是发布-订阅模式,如果是使用Spring框架的话,可以直接使用Spring的ApplicationEvent。

领域事件很大的作用,用来使聚合和聚合之间,模型和模型之间,甚至是上下文和上下文之间达成一致性。

ProductBacklogItemPlanned本身也是领域建模的一部分,所以它也会存放在领域层,和Entity放在一起。

引用:

1.实现领域驱动设计 Vaughn Vernon

2.github.com/VaughnVerno…

分类:
后端
收藏成功!
已添加到「」, 点击更改