Aggregate

The Aggregate pattern implementation from Sculptor has been adapted within CML to represent it with a separate grammar rule. For a short introduction to the syntax of the other tactic DDD patterns, please have a look at Tactic DDD Syntax. For more details, we refer to the Sculptor project and its documentation.

Syntax

The aggregate supports the Responsibility Layers pattern and the Knowledge Level pattern. An aggregate can further contain Services, Resources, Consumers and SimpleDomainObjects (Entities, Value Objects, Domain Events, etc.) which are not further introduced here. The according rules are defined by the Sculptor DSL, as already mentioned. However, the following CML snippet illustrates an example of an aggregate to provide an impression how the rule can be used.

Aggregate Contract {
  responsibilities = Contracts, Policies
  knowledgeLevel = CONCRETE
  
  Entity Contract {
    aggregateRoot
    
    - ContractId identifier
    - Customer client
    - List<Product> products
  }
  
  ValueObject ContractId {
    int contractId key
  }
  
  Entity Policy {
    int policyNr
    - Contract contract
    BigDecimal price
  }
}
Note: Aggregate names must be unique within the whole CML model.

Further examples can be found within our Github example repository context-mapper-examples.

Aggregate Owner

CML allows to specify an owner on aggregate level. If aggregates are maintained by different teams, you can specify this as in the following example:

BoundedContext CustomerSelfServiceContext implements CustomerManagementDomain {
  type = APPLICATION
  domainVisionStatement = "This context represents a web application which allows the customer to login and change basic data records like the address."
  responsibilities = "AddressChange"
  implementationTechnology = "PHP Web Application"

  Aggregate CustomerFrontend {
    owner = CustomerFrontendTeam

    DomainEvent CustomerAddressChange {
      aggregateRoot

      - UserAccount issuer
      - Address changedAddress
    }
  }
  Aggregate Acounts {
    owner = CustomerBackendTeam

    Entity UserAccount {
      aggregateRoot

      String username
      - Customer accountCustomer
    }
  }
}

This may be used for service decomposition by using the Split Bounded Context by Owners architectural refactoring.

Note that the owner attribute refers to a team, which is a bounded context of the type TEAM: (see Bounded Context for more details)

/* Team Definitions */
BoundedContext CustomerBackendTeam {
  type = TEAM
}

BoundedContext CustomerFrontendTeam {
  type = TEAM
}

Aggregate Use Cases

With CML you can further specify by which use cases an aggregate is used. This may be used for service decomposition by using the Split Bounded Context by Use Cases architectural refactoring.

Assigning aggregates to use cases can be done with the useCases attribute as follows:

BoundedContext PolicyManagementContext implements PolicyManagementDomain {
  Aggregate Offers {
    useCases = CreateOfferForCustomer
    
    Entity Offer {
      aggregateRoot
      
      int offerId
      - Customer client
      - List<Product> products
      BigDecimal price
    }
  }
  Aggregate Products {
    useCases = CreateOfferForCustomer
    
    Entity Product {
      aggregateRoot
      
      - ProductId identifier
      String productName
    }
    ValueObject ProductId {
      int productId key
    }
  }
  Aggregate Contract {
    useCases = UpdateContract
    
    Entity Contract {
      aggregateRoot
      
      - ContractId identifier
      - Customer client
      - List<Product> products
    }
    ValueObject ContractId {
      int contractId key
    }
    
    Entity Policy {
      int policyNr
      - Contract contract
      BigDecimal price
    }
  }
}

You can also refer to multiple use cases by providing a comma-separated list:

Aggregate Offers {
  useCases = CreateOfferForCustomer, UpdateOffer

  Entity Offer {
    aggregateRoot

    int offerId
    - Customer client
    - List<Product> products
    BigDecimal price
  }
}

Use Case Declaration

The use cases you refer to have to be declared on the root level of your CML file. To declare a use case, use the keyword UseCase. A use case can be declared by simply give the case a name, as shown in the example below. If you want to provide further information about the use case you can specify which attributes of which entities are read and written by this use case (strings only; no references):

/* Simple use case (only name given) */
UseCase UpdateContract
UseCase UpdateOffer

/* Extended declaration with read and written attributes */
UseCase CreateOfferForCustomer {
  reads "Customer.id", "Customer.name"
  writes "Offer.offerId", "Offer.price", "Offer.products", "Offer.client"
}

Likelihood for Change

With the attribute likelihoodForChange you can specify how volatile an aggregate is. The attribute takes one of the following three values:

  • RARELY
  • NORMAL
  • OFTEN

This attribute may be used for service decomposition, since parts which are likely to change should typically be isolated in separate components (see Parnas). In CML you can use this by applying the Extract Aggregates by Volatility architectural refactoring.

The likelihood on an aggregate is declared as follows:

Aggregate CustomerFrontend {
  likelihoodForChange = OFTEN
  
  DomainEvent CustomerAddressChange {
    aggregateRoot
    
    - UserAccount issuer
    - Address changedAddress
  }
}