会员系统DSL描述

会员系统DSL描述

如花是一名架构师,对DDD也非常熟悉,而且有过几个项目的DDD实践,最近他加入会员线,负责完成对会员系统的改造,更好地配合公司的微服务化的设计思路。会员线之前就是三个应用:会员中心对外提供的大量的REST API服务;会员注册和登录应用;会员中心,处理会员登录后如修改个人密码、基本信息、SNS第三方绑定和支付方式绑定等。

如花加入会员团队后,和大家沟通了基于DDD + MicroServices的架构思想,大家都表示同意,但是如何落实到具体的架构设计和文档上,大家就犯难啦。

SubDomain开始

如花开始DDD的第一步,也就是Subdomain的划分;如花首先将会员先划分为几个Sub Domain,如处理账号相关的Account,处理会员打标的UserTag,处理支付方式的PaymentProfile,处理社交平台集成的SnsProfile,还有一个其他Profiles,这里我们不涉及GenericSupporting Doman的规划,主要从业务核心Domain出发。一个同学用PPT阐述了划分结构和出发点,如下:

会员 Sub Domain

UML图如下:

会员 Sub Domain UML

DDD的第一步:SubDomain的划分和展现,就有不同的理解方式,如何描述、如何图形化展现,都有不少的分歧。回到问题的出发点,我们就想划分一下SubDomain,那么是不是下述的DSL代码也可以:

Domain User {
    domainVisionStatement = "User domain to manage account, tags, profiles and payment profile."
    Subdomain AccountDomain {
       type = CORE_DOMAIN
       domainVisionStatement = "Account domain to save sensitive data and authentication"
    }
    Subdomain UserTagDomain {
       type = GENERIC_SUBDOMAIN
       domainVisionStatement = "UserTag domain manage user's KV and Boolean tag"
    }
    Subdomain PaymentProfileDomain {
        type = CORE_DOMAIN
        domainVisionStatement = "User payment profile domain to manage credit/debit card, Alipay payment information"
    }
    Subdomain SnsProfileDomain {
        type = CORE_DOMAIN
        domainVisionStatement = "User Sns profile domain to manage user Sns profile for Weibo, Wechat, Facebook and Twitter."
    }
    Subdomain ProfilesDomain {
        type = CORE_DOMAIN
        domainVisionStatement = "User profiles domain to manage user basic profile, interest profile etc"
    }
}

虽然目前我们还不知道对应的DSL代码语法,但是我们已经知道Domain的名称、domain类型以及domain的愿景陈述(visionStatement),至于后期以何种方式展现系统Domain,如表格、图形等,这个可以考虑基于现在的数据进行展现。其中的UserTagDomain类型为GENERIC_SUBDOMAIN,这个表示打标是通用性Domain,如我们后期可以和商品、图片或者视频团队合作,大家可以一起共建打标系统。

注意,Subdomain不只是简单包括typedomainVisionStatement,同时你可以添加EntityService,其目的主要是突出核心特性并方便你对Domain的理解,如Account中添加resetPasswordauthBySmsCode,相信大多数人都知道这是什么含义。但是注意不要将其他对象添加到Subdomain,如VO, Repository, Domain Event等,这些都是辅助开发的,应该用在BoundedContext中。

Subdomain AccountDomain {
    type = CORE_DOMAIN
    domainVisionStatement = "Account domain to save sensitive data and authentication"
    Entity Account {
        long id
        String nick
        String mobile
        String ^email
        String name
        String salt
        String passwd
        int status
        Date createdAt
        Date updatedAt
    }
    Service AccountService {
        void updatePassword(long accountId, String oldPassword, String newPassword);
        void resetPassword(long acountId);
        boolean authByEmail(String email, String password);
        boolean authBySmsCode(String mobile, String code);
    }
}

Context Map

ContextMap主要是描述各个Domain中各个BoundedContext间的关联关系,你可以理解为BoundedContext的拓扑地图。这里我们先不详细介绍BoundedContext,你现在只需要理解为实现Domain的载体,如你编写的HSF服务应用、一个处理客户请求的Web应用或者手机App,也可以是你租用的一个外部SaaS系统等。举一个例子,你的系统中有一个blogSubDomain,你可以自行开发,也可以架设一个WordPress,或者用Medium实现Blog。回到微服务的场景,如何划分微服务应用?SubDomain对应的是业务或者虚拟的领域,而BoundedContext则是具体支持SubDomain的微服务应用,当然一个SubDomain可能对应多个微服务应用。

既然是描述各个BoundedContext关系,必然会涉及到关联关系,如DDD推荐的Partnership([P]<->[P])、Shared Kernel([SK]<->[SK])、Customer/Supplier([C]<-[S])、Conformist(D,CF]<-[U,OHS,PL])、Open Host Service、Anticorruption Layer([D,ACL]<-[U,OHS,PL])、Published Language等,详细的介绍大家可以参考DDD图书。这些对应关系都有对应的缩写,就是括号内的表述方法。这里给出关联关系Cheat Sheet说明图:

Context Map CheatSheet

ContextMap UserContextMap {
   type = SYSTEM_LANDSCAPE
   state = TO_BE
   contains AccountContext
   contains UserTagContext
   contains PaymentProfileContext
   contains SnsProfileContext
   contains ProfilesContext
   contains UserLoginContext
   contains UserRegistrationContext
    UserLoginContext [D]<-[U] AccountContext {
        implementationTechnology = "RSocket"
        exposedAggregates = AccountFacadeAggregate
    }
    ProfilesContext [D]<-[U] UserTagContext {
        implementationTechnology = "RSocket"
        exposedAggregates = UserTags
    }
    UserRegistrationContext [D,C]<-[U,S] UserTagContext {
        implementationTechnology = "RSocket"
        exposedAggregates = UserTags
    }
    UserRegistrationContext [D,C]<-[U,S] SnsProfileContext {
        implementationTechnology = "RSocket"
    }
}

大家可以看到Map图中包含的各个BoundedContext名称,然后描述了它们之间的关系。在关联关系描述中,涉及到对应的描述。前面我们说明BoundedContextDomain的具体系统和应用的承载,所以涉及到对应的技术实现。如HTTP REST API、RPC、Pub/Sub等,如blog系统为Medium的话,那么implementationTechnology = ”REST API"。还有exposedAggregates,表示暴露的聚合信息,如class对象和字段,服务接口等,方便通讯双方做对接,这个我们会在BoundedContext中进行介绍。

BoundedContext

ContextMap中我们描述了它们之间的关联关系,接下来我们要进行BoundedContext的详细定义。BoundedContext包含的内容相信大多数同学都知道,如Entity,ValueObject,Aggregate,Service,Repository、DomainEvent等,这个大家应该都比较熟悉。这里我们给出一个ContextMapperBoundedContext的代码,如下:

BoundedContext AccountContext implements AccountDomain {
    type = APPLICATION
    domainVisionStatement = "Managing account basic data"
    implementationTechnology = "Kotlin, Spring Boot, MySQL, Memcached"
        responsibilities = "Account", "Authentication"
    Aggregate AccountFacadeAggregate {
       ValueObject AccountDTO {
          long id
          String nick
          String name
          int status
          Date createdAt
          def toJson();
       }
       /* AccountFacade as Application Service */
       Service AccountFacade {
          @AccountDTO findById(Integer id);
       }
    }
    Aggregate Accounts {
         Entity Account {
            long id
            String nick
            String mobile
            String ^email
            String name
            String salt
            String passwd
            int status
            Date createdAt
            Date updatedAt
         }
   }
}

这里对BoundedContext再说明一下:

  • BoundedContext的名称,这个不用说啦,这个和ContextMap中名称一致。

  • implements AccountDomain:表示要实现哪一个SubDomain,我们都知道一个Subdomain可能会包含多个BoundedContext,这些BoundedContext配合起来完成Subdomain的业务需求。ContextMap还提供refines,来表示BoundedContext要实现一些user case,官方文档有对应的说明。

  • BoundedContext的属性字段:type表示类型,如APPLICATIONSYSTEM等。domainVisionStatement描述一下BoundedContext的职责。implementationTechnology表示具体的技术,前面我们说到BoundedContext已经涉及具体的应用和系统等,所以要说明对应的技术方案实现,核心的部分描述一下就可以。responsibilities表示BoundedContext的职责列表,这里只需要关键字就可以,如Account要负责安全验证等。

  • AccountFacadeAggregate:表示提供给外部调用的聚合,这里DTO的对象定义、服务接口的定义等。

  • Aggregate Accounts:这个表示BoundedContext内部的聚合,如entity、value object、service等。这里说明一下,DDD中的那个Aggregateentityvalue object的聚合对象,而ContextMapper中的Aggregate表示为一些资源的集合,如Service集合等。

BoundedContext的更多信息,可以参考sculptor的文档,根据实际的情况可以添加对应的部分,如DomainEventRepository等。

其它特性

UserStory

这个DSL比较明确的,主要是三元素:作为 “aaa",我希望能"xxx",我希望能”yyyy",以便 “zzz”,也是符合UserStory的典型三要素:角色、活动和商业价值。

UserStory Customers {
    As a "Login User"
        I want to update a "Avatar"
        I want to update an "Address"
    so that "I can manage the personal data."
}

UseCase

Use Case是描述需求的一种方式,在UML图就有对应的UseCase图,核心就是actor,交互动作和商业价值,对应的DSL代码如下:

UseCase UC1_Example {
  actor = "Insurance Employee"
  interactions = create a "Customer", update a "Customer", "offer" a "Contract"
  benefit = "I am able to manage the customers data and offer them insurance contracts."
}

Aggregate聚合中,你可以设置useCases属性来描述对应的UseCase,如下:

Aggregate Contract {
  useCases = UC1_Example, UC2_Example
}