... connect
Vi Practice: tactical design
In a sense, strategic design represents planning capability, while tactical design represents execution. In this section, we will implement it, because although the models in this field are not complex, it is not realistic to post all the models. The author here shows the design of two boundary contexts, which is only a first draft. There are many deficiencies, and I hope to be corrected by the majority of readers and professionals.
User management context
CML Code:
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 :
At first glance, it seems to be a relatively simple and easy task to form an aggregate of entities and value objects within the consistency boundary, but it is the most difficult to understand in many Tactical Guidance of DDD. A key question that needs to be answered clearly is: what are the invariant conditions and consistency boundaries of aggregation? I don't have this level to answer this question correctly. My personal understanding is that aggregation itself should ensure the invariance and consistency of business rules.
DDD itself advocates small aggregation, because if too many objects are introduced into an aggregation, the loading and updating of the whole object will become very heavy. For example, large aggregation will face trouble in maintaining the overall transaction consistency, which limits the performance and scalability of the system.
DDD recommends that the implementation of aggregation follow the Demeter's law and tell no query principle. The former emphasizes minimum knowledge, while the latter is simpler.
In the implementation of the user management context shown above, this context is composed of two aggregations, namely, users and roles.
Order and payment context
This context consists of two aggregations, order and payment.
CML Code:
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 :
In the construction of entities, we should clarify the essential characteristics of entities, mine their key behaviors, define their roles and responsibilities, and make them verifiable and traceable. In many cases, it is a good choice to replace entities with more lightweight non modifiable value objects.
This section shares DDD tactical design practice in the form of diagram and CML code, which has too many imperfections. Readers can carefully read the CML code to understand the specific details. For CML syntax, see the above-mentioned context mapper . Again, the example is only a very crude first version, with many deficiencies and defects, which is far from a complete and comprehensive DDD tactical design. For example, how to solve the N:N relationship, continuous integration, interface idempotency and so on are not mentioned, but these must be considered in the design process.
Readers need to consider carefully in the specific implementation, combined with DDD design concept and object-oriented analysis technology, repeated iteration can achieve good results, so as to precipitate the core business logic and business processing capability to the platform layer.
Unfinished, to be continued