…接上
六. 实践:战术设计
从某种意义上说,战略设计代表了计划能力,而战术设计代表了执行力。本节我们就来执行一下,因为本领域模型虽然并不复杂,但是如果把所有模型都贴出来也不太现实。笔者这里展示两个界限上下文的设计,只是个初稿,有很多不足,非常希望都到广大读者和专业人士的指正。
用户管理上下文
CML 代码:
BoundedContext userManagementContext implements userDomain {
type = FEATURE
domainVisionStatement = "User management from system view"
implementationTechnology = "Java, SpringBoot"
responsibilities = "User", "Role"
knowledgeLevel = CONCRETE
Aggregate User {
responsibilities = "User"
knowledgeLevel = CONCRETE
securityZone "Internal"
contentVolatility = NORMAL
consistencyCriticality = HIGH
securityCriticality = HIGH
Entity User {
aggregateRoot
- UserID userId
- Status userStatus
- UserType userType
- BasicProfile profile
- List<Role> roles
- UserQualify qualify
- Account account
DateTime registeredTime
DateTime updatedTime
def int UpdateUserProfile(userIdentification id) : write;
def int CreateUser() : write [ ->CREATED ];
def int DropUser() : write [ ->REMOVED ];
def int ActivatedUser() : write [ ->ACTIVATED ];
def int DeactivatedUSer() : write [ ACTIVATED -> DEACTIVATED ];
def int UpdateUserRole();
def User ReadUserProfile() : read-only;
Repository UserRepository {
@User get(@UserID id);
List<@User> getAll();
}
}
ValueObject UserID {
long userId
}
ValueObject BasicProfile {
String userName
String password
String cellPhone
DateTime birthDate
String mailAddress
String alternativeMail
}
ValueObject Account {
- UserID userId
- List<VirtualCurrency> virtualCurr
}
abstract ValueObject VirtualCurrency {
float amount
}
ValueObject Money extends VirtualCurrency {
- Currency curr
float mapping
baseCurrency rmb
}
ValueObject Score extends VirtualCurrency {
float ratio
baseCurrency rmb
}
enum Currency {
RMB,DOLLAR
}
enum UserType {
USER, ROLEADMIN, DOMAINADMIN, SITEADMIN
}
enum Status {
aggregateLifecycle
ACTIVATED, DEACTIVATED, CREATED, REMOVED
}
abstract DomainEvent AbstractDomainEvent {
DateTime timestamp
}
DomainEvent UserProfileChanged extends AbstractDomainEvent {}
DomainEvent UserCreated extends AbstractDomainEvent {}
DomainEvent UserRemoved extends AbstractDomainEvent {}
DomainEvent UserActivated extends AbstractDomainEvent {}
DomainEvent UserDeactivated extends AbstractDomainEvent {}
DomainEvent UserRolesAsscioationChanged extends AbstractDomainEvent {}
}
Aggregate Role {
responsibilities = "Role"
knowledgeLevel = CONCRETE
ValueObject Role {
aggregateRoot
- RoleType roleType
String desc
- Status status
- Rules defaultRule
def int CreateRole();
def int CreatedCustomedRole();
def int UpdateRole();
def int ActivatedRole();
def int DeactivatedRole();
def int DropRole();
}
enum RoleType {
ORG, PARTNER, CHANNEL, MEMBER, CUSTOMED
}
DomainEvent RoleCreated extends AbstractDomainEvent {}
DomainEvent RoleRemoved extends AbstractDomainEvent {}
DomainEvent RoleActivated extends AbstractDomainEvent {}
DomainEvent RoleDeactivated extends AbstractDomainEvent {}
DomainEvent RoleProfileChanged extends AbstractDomainEvent {}
}
Aggregate UserQualify {
responsibilities = "Role"
knowledgeLevel = CONCRETE
ValueObject UserQualify {
aggregateRoot
- Qualify qualify
String desc
}
enum Qualify {
DOCTOR,RESEARCHER,LIBRARIAN,OTHERS
}
}
}
UML :
把实体(Entity)和值对象(Value Object)在一致性边界之内组成聚合(Aggregate)乍看起来是一项比较简单和轻松的任务,但在DDD的众多战术指导中却是最难理解的。一个需要明确回答的关键问题是:聚合的不变条件和一致性边界究竟是什么?笔者本人也还没有这个水平来正确回答这个问题,个人的理解是聚合本身应该保证业务规则的不变性和一致性。
DDD本身主张小的聚合,因为一个聚合如果引入了太多对象时,整个对象的加载和更新操作将会变得很沉重。例如大的聚合在维护整体事务一致性上将会面临麻烦,从而限制了系统的性能和可伸缩性。
DDD推荐聚合的实现时遵循迪米特法则和告诉非询问原则。前者强调最小知识,后者更为简单。
在上面所展示的用户管理上下文的实现中,这个上下文是由两个聚合组成的,分别是用户和角色。
订单与支付上下文
这个上下文由两个聚合来组成,分别是订单和支付。
CML 代码:
BoundedContext orderContext implements businessDomain {
type = FEATURE
domainVisionStatement = "Orders Management"
implementationTechnology = "Java, SpringBoot"
knowledgeLevel = CONCRETE
Aggregate Order {
Entity Order {
aggregateRoot
- OrderID orderId
- List<OrderItem> items
- OrderState orderState
- @Policies policies
long userId
DateTime createTime
DateTime completeTime
def calculateSumPrice();
def postOrderAction();
}
enum OrderState {
aggregateLifecycle
PAYED,UNPAYED,CANCELED
}
ValueObject OrderID {
int id
}
ValueObject OrderItem {
int productId
float price
}
DomainEvent OrderSubmitted {}
DomainEvent OrderRevokedSucc {}
DomainEvent OrderRevokedFail {}
DomainEvent OrderPostActionFinished {}
}
}
BoundedContext payContext implements businessDomain {
type = FEATURE
domainVisionStatement = "Pay Management"
implementationTechnology = "Java, SpringBoot"
knowledgeLevel = CONCRETE
Aggregate Payment {
Service PaymentService {
int doPayment(int orderId) throws PaymentFailedException;
int rollback(int paymentId) throws paymentRollbackFailedException;
}
ValueObject PaymentID {
int paymentId
}
enum PayMethod {
WEIXIN,ZHIFUBAO,CREDITCARD,SCORE
}
DomainEvent PaymentSucceed {}
DomainEvent PaymentFailed {}
DomainEvent PaymentRollbacked {}
}
}
UML :
在实体的构建中,我们应该明确实体的本质特征,挖掘其关键行为,定义其角色和职责,并使其可验证、可跟踪。很多时候,以更为轻量级的不可修改的值对象来代替实体是一个不错的选择。
本节以图和CML代码的形式分享了DDD战术设计实践,这中间有太多不够完善的地方。读者可以仔细阅读CML代码来了解具体细节,CML语法可参阅前面所提到的 context mapper 。再次强调示例中只是一个非常粗陋的初版,存在非常多的不足和缺陷,距离一个完整和全面的DDD战术设计还很远。例如如何解决N:N关系,持续集成、接口幂等性等等都没有提及,但这些也是设计过程中必须要考虑的。
读者在具体的实现时需要谨慎斟酌,结合DDD设计理念和面向对象分析技术反复迭代才能够取得好的效果,从而实现把核心业务逻辑和业务处理能力沉淀到平台层。
未完,待续…