背景
分层的目的是把领域的东西与非领域的东西隔离开,按照分层的逻辑,层与层之间的职责更加清晰。架构分层有传统的四层架构,有依赖倒置的四层架构,还有六边形架构.
应用服务作为领域对象的客户,跟领域服务有很大的区别,两者的作用也不一样,千万别混淆这两者的概念。
领域事件,大有用途…
一、传统的四层架构
基础设施层:repository实现(持久化),消息中间件(消息处理),外部接口交互实现,访问缓存等等。
领域层:Entity、Value Object、domain Service、repository接口(不包括实现)、factory等,是项目最核心的业务层。
应用层:业务任务分配、服务编排,事务控制等,与业务无关,其中的应用服务(application service)是领域层对象的客户端。
UI层:主要是跟系统用户交互层,其实像前后端分离的应用,一般是不需要的,如果是使用Spring MVC,其中的controller可以直接放置在应用层。
二、依赖倒置的四层架构
基础设施层依赖领域层,像repository等接口可以放在领域层,而领域层只需要使用接口,其实现放在基础设施层,如此一来,分层的概念好像就不存在了,像是把层摊平了。
三、六边形架构
六边形架构让我们站在更高的角度看到系统架构,左上角的适配器,可以理解成是restful接口、webservice接口等,左边三角型的适配器,可以理解成接收消息接口(MQ消息、领域事件等),右边的适配器可以理解成持久化,访问内存接口,右下角的适配器,可以理解成是发送领域事件或发送MQ消息等,所有这些,都围绕着领域模型(领域层)。
采用哪种架构并没有一定标准,合适就好,当然除了上面说的三种,还有CQRS、事件驱动架构,一般来说,我觉得上面三种架构已经可以应对业务了。
这个就是典型的六边形架构,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领域事件。
领域事件订阅者,所以这就是发布-订阅模式,如果是使用Spring框架的话,可以直接使用Spring的ApplicationEvent。
领域事件很大的作用,用来使聚合和聚合之间,模型和模型之间,甚至是上下文和上下文之间达成一致性。
ProductBacklogItemPlanned本身也是领域建模的一部分,所以它也会存放在领域层,和Entity放在一起。
引用:
1.实现领域驱动设计 Vaughn Vernon