理解DDD中的限界上下文,整理自实现领域驱动设计

作者: admin 分类: 领域驱动设计 发布时间: 2019-11-25 15:39  阅读: 60 views

限界上下文:

主要是语言层面上的限界划分,是实现DDD的关键。一个限界上下文并不一定只包含在一个子域中。限的意思就是划分、规定,界就是界限、或者一个边界,上下文就是业务的整个流程。限界上下文定义了领域模型的边界,目的是清理子域,然后区分子域哪些是核心域、支撑子域和通用子域

限界上下文是一个显式的边界,领域模型便存在于这个边界之内。创建边界的原因在于,每一个模型概念,包括它的属性和操作,在边界之内都具有特殊的含义。而领域模型需要准确的反映通用语言。

在很多情况下,不同模型中存在名字相同或相近的对象,但是他们的意思却不同。当模型被一个显示的边界包围时,其中每个概念的含义便是确定的了。因此,限界上下文主要是一个语义上的边界,我们可以通过这一点来衡量对一个限界上下文的使用正确与否。

限界上下文主要用来封装通用语言和领域对象,但同时它包含了那些为领域模型提供交互手段和辅助功能的内容。


来看一个账户(account)模型在银行上下文和文学上下文中的不同

银行上下文:账户表示一个客户在银行的存款状态,并记录每次交易信息。【如,支票账户、储蓄账户】

文学上下文:账户表示用文字记录的在一段时间内发生的一系列事件。(如某些出售的图书)

示例

在通常情况下,我们所面对的都是一些去呗甚小的概念定义。原因在于:一个上下文中,团队通常根据通用语言来命名某个概念。我们并不会随意地命名一个概念以刻意地保持与其他上下文的不同。

一个限界上下文并不只是包含领域模型。它通常标定了一个系统、一个应用程序或者一种业务服务。

当模型驱动着数据库Schema的设计时,此时的数据库Schema也应该位于该模型所处的上下文边界内。意味着数据库中表和列的名称应该和模型的名称保持一致。如:

public class BacklogItem extends Entity{
    ...
    private BacklogItem backlogItemId;
    private BussinessPriority bussinessPriority;
    ...
}

建立的表定义应该如下:

create table 'tbl_backlog_item'(
    ....
    `backlog_item_id_id` varchar(36) not null,
    `business_priority_ratings_benefit` int not null,
    `business_priority_ratings_cost` int not null,
    `business_priority_ratings_penalty` int not null,
    `business_priority_ratings_risk` int not null
    ...
) ENGINE = InnoDB

注:(根据界面设计领域模型会造成贫血领域对象)
如用户界面被用于渲染模型,并且驱动着模型的行为设计时,同样,该用户界面也应该属于模型所在的上下文边界之内。但是,该用户界面也应该属于模型所在的上下文边界之内。但是,这并不表示我们应该在用户界面中对领域进行建模,这样会导致贫血领域对象。我们应该拒绝使用智能UI反模式(Smart UI Anti-Pattern)
,或者任何试图将领域概念带到领域模型之外的举措。

一个简单的上下文示例及领域划分(书中示例)

一个拥有清晰子域的示例限界上下文

示例上下文

让我们看看这三个模型是如何形成一个实际的、现代的企业级解决方案的。这个DDD项目中存在三个限界上下文,分别是协作上下文、身份与访问上下文和敏捷项目管理上下文。

协作上下文

负责设计和实现协作上下文的核心团队需要在第一次软件发布中包括以下功能:论坛、共享日历、博客、即时消息、wiki、留言板、文档管理、通知与提醒、活动跟踪和RSS订阅。虽然核心团队要开发的功能很多,但是每一个协作工具都可以单独使用。这些工具都属于同一个限界上下文。如下:

协作上下文
通用语言决定了哪些组件应该位于边界之内。

早些时候,错误的将安全和权限相关逻辑引入了协作模型中,如下

public class Forum extends Entity {

    public Discjussion startDiscussion(String aUsername,String aSubject) {
        if(this.isClosed()) {
            throw new IllegalStateException("Forum is closed.");
        }

        User user = userRepository.userFor(this.tenantId(),aUsername);

        if(!user.hasPermissionTo(Permission.Forum.StartDiscussion)) {
            throw new IllegalStateException("xxxx");
        }

        String authorUser = user.getUsername();
        String authorName = user.persion().name().asFormattedName();
        String authorEmailAddress  = user.persion().emailAddress();

        Discussion discussion = new Discussion(
            this.tenant(),this.forumId(),
            xxxx,xxx,xx,xx,xx,,x
        );
        return  discussion; 
    }
}

以上代码是一种不好的设计。我们不应该在这里引用user,更不用说资源库,甚至premission都不应该出现在这里。原因是,团队成员错误地将这些概念设计成了协作模型的一部分。这种错误的设计导致他们忽略了本应该存在的建模概念-Author。他们没有把关联的属性放在一个值对象中,而是使用一些分离的数据元素来解决问题。

通过DDD的学习,团队采用了隔离内核的方式(将所有与安全和权限相关的类迁移到一个单独的模块中,然后,在调用核心领域逻辑之前,使用应用服务去检查用户的安全权限。这样,核心域只需要实现和协作行为相关的功能)

书中代码如下:【只是给出了部分,有点片面,可能不太好理解】

public class ForumApplicationService ... {

    public Discussion startDiscussion(String aTenantId,String aUsername,
        String aForumId , String aSubject){

        Tenant tenant = new Tenant(aTenantId);
        ForumId forumId = new ForumId(aForumId);

        Forum forum = this.forum(tenant,forumId);

        if(forum == null){
            throw new IllegalStateExcetion("xxx");
        }

        Author author = this.collaboratorService.authorFrom(tenant,anAuthorId);

        Discussion newDiscussion = forum.startDiscussion(this.forumNavigationService(),
            author,aSubject);

        this.discussionRespository.add(newDiscussion);

        return newDiscussion;
    }
}

public class Forum extends Entiry{

    public Discussion startDiscussionFor(
        ForumNavigationService aForumNavigationService,
        Author anAuthor,
        String aSubject
    ){
        if(this.isClosed()) {
            throw new IllegalStateException("Forum is closed.");
        }

        Discussion discussion = new Discussion(
            this.tenant(),this.forumId(),
            aForumNavigationService.nextDiscussionId(),
            anAuthor,aSubject
        );

        DomainEventPublisher.instance().publish(
            new DiscussionStarted(
                discussion.tenant(),
                discussion.forumId(),
                discussion.discussionId(),
                discussion.subject())
            );
        );

        return discussion;
    }

}

以上的代码相比初版,移除了User和Permission,并将关注点完全限制在协作活动上。这里可以在将来重构成单独的限界上下文。【代码主要是隔离了业务的交集】

身份与访问上下文

很多应用程序都需要某种形式的安全和权限组件,对用户进行认证和授权。一种幼稚的做法是将这样的组件嵌入到每一个离散的系统中,这样会导致每个系统都产生简仓效应(指企业内部因缺少沟通,部门间各自为政,只有垂直的指挥系统,没有水平的协同机制,就象一个个的谷仓,各自拥有独立的进出系统,但缺少了谷仓与谷仓之间的沟通和互动。)

通过采用标准的DDD集成技术,身份与访问上下文可以被其他限界上下文所使用。上下文如下图:
身份与访问上下文

敏捷项目管理上下文

用户和角色在另外的身份与访问上下文中进行管理。通过使用该上下文,订阅者通过自助式的服务来管理他们自己的身份和访问信息。

身份与访问上下文

这节内容主要是介绍限界上下文的。关于示例部分,需要理解一番。因为和编码习惯不一样,这里是结合业务、任务协作等方面为出发点进行设计[看图,理解模块功能的划分]。


   原创文章,转载请标明本文链接: 理解DDD中的限界上下文,整理自实现领域驱动设计

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

发表评论

电子邮件地址不会被公开。 必填项已用*标注

更多阅读