DDD(Domain Driven Design) domain driven design from theory to practice

Posted by yozyk on Mon, 17 Jan 2022 04:20:16 +0100

... 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