Overview

jMolecules provides a set of Domain-Driven Design (DDD) annotations to model and structure the domain layer of your application. These annotations help enforce the key building blocks of DDD while making the roles of your entities, aggregates, and other components explicit.

This guide explains each annotation, its constraints, and provides examples to demonstrate their usage.


jMolecules DDD Annotations and Their Constraints

Here’s a table summarizing the allowed and restricted dependencies for each annotation:

AnnotationMust Depend OnMust Not Depend OnRepresents
@AggregateRootDomain objects (@Entity, @ValueObject)Infrastructure or service layersThe root of an aggregate, responsible for enforcing consistency.
@EntityDomain objects (@Entity, @ValueObject, @AggregateRoot)Infrastructure or service layersA mutable object with an identity, part of an aggregate.
@ValueObjectNone (pure logic only)Mutable state or infrastructureAn immutable object that defines a concept or value in the domain.
@Repository@AggregateRootOther repositories, servicesThe abstraction for retrieving and storing aggregates.
@ServiceDomain objects, RepositoriesApplication or infrastructure logicA stateless service encapsulating domain-specific operations.
@Factory@AggregateRoot, @Entity, @ValueObjectInfrastructure logicA mechanism for creating complex domain objects.
@DomainEventNone (pure event data)Infrastructure logicAn event representing something of significance in the domain.

Explanation of Annotations

1. @AggregateRoot

  • Definition: Represents the root of an aggregate. It’s the only entry point for manipulating the aggregate’s state.
  • Constraints:
    • Must Depend On: @Entity, @ValueObject (other domain objects within the aggregate).
    • Must Not Depend On: Infrastructure code or services.
  • Purpose:
    • Ensures that all changes within the aggregate are consistent.
    • Acts as the single source of truth for its contained entities.

2. @Entity

  • Definition: Represents a mutable object with an identity that is part of an aggregate.
  • Constraints:
    • Must Depend On: @AggregateRoot, other @Entity, or @ValueObject.
    • Must Not Depend On: Infrastructure or service layers.
  • Purpose:
    • Represents a piece of the aggregate that can change over time but is controlled by the aggregate root.

3. @ValueObject

  • Definition: Represents an immutable object that models a concept in the domain.
  • Constraints:
    • Must Depend On: None (should encapsulate pure domain logic).
    • Must Not Depend On: Mutable state, infrastructure, or services.
  • Purpose:
    • Models values like monetary amounts, coordinates, or dates without tracking identity.

4. @Repository

  • Definition: Provides an abstraction for persisting and retrieving aggregates.
  • Constraints:
    • Must Depend On: @AggregateRoot.
    • Must Not Depend On: Other repositories or application services.
  • Purpose:
    • Hides persistence details and serves as the interface for accessing aggregates.

5. @Service

  • Definition: Represents a stateless service encapsulating domain-specific operations.
  • Constraints:
    • Must Depend On: Domain objects (e.g., @Entity, @ValueObject, @AggregateRoot) and @Repository.
    • Must Not Depend On: Application services or infrastructure.
  • Purpose:
    • Encapsulates domain logic that doesn’t naturally fit within an entity or value object.

6. @Factory

  • Definition: Used for constructing complex domain objects.
  • Constraints:
    • Must Depend On: @AggregateRoot, @Entity, or @ValueObject.
    • Must Not Depend On: Infrastructure code.
  • Purpose:
    • Handles creation logic that involves multiple steps or collaborators.

7. @DomainEvent

  • Definition: Represents a significant occurrence in the domain.
  • Constraints:
    • Must Depend On: None (self-contained).
    • Must Not Depend On: Infrastructure code or mutable state.
  • Purpose:
    • Communicates changes in the domain to other parts of the system.

Example: E-Commerce Order System

Components in the Domain

  1. AggregateRoot: Order

    • The root of the aggregate, ensuring consistency for all order-related data.
  2. Entity: OrderItem

    • Represents individual items within an order.
  3. ValueObject: Money

    • Encapsulates currency and amount as an immutable concept.
  4. Repository: OrderRepository

    • Abstracts persistence for Order aggregates.
  5. Service: OrderService

    • Handles domain-specific operations like calculating the total price of an order.
  6. DomainEvent: OrderPlacedEvent

    • Published when an order is placed.

Code Example

AggregateRoot (Order)

@AggregateRoot
class Order(val id: UUID, val customerId: UUID, val items: List<OrderItem>) {
 
    fun calculateTotal(): Money {
        return items.fold(Money(0.0)) { total, item -> total.add(item.totalPrice) }
    }
 
    fun placeOrder(): OrderPlacedEvent {
        return OrderPlacedEvent(id, customerId, calculateTotal())
    }
}

Entity (OrderItem)

@Entity
class OrderItem(val productId: UUID, val quantity: Int, val price: Money) {
    val totalPrice: Money
        get() = price.multiply(quantity)
}

ValueObject (Money)

@ValueObject
data class Money(val amount: Double, val currency: String = "USD") {
    fun add(other: Money): Money {
        require(currency == other.currency) { "Currencies must match!" }
        return Money(amount + other.amount, currency)
    }
 
    fun multiply(multiplier: Int): Money {
        return Money(amount * multiplier, currency)
    }
}

Repository (OrderRepository)

@Repository
interface OrderRepository {
    fun save(order: Order)
    fun findById(id: UUID): Order?
}

Service (OrderService)

@Service
class OrderService(private val orderRepository: OrderRepository) {
 
    fun placeOrder(order: Order): OrderPlacedEvent {
        val event = order.placeOrder()
        orderRepository.save(order)
        return event
    }
}

DomainEvent (OrderPlacedEvent)

@DomainEvent
data class OrderPlacedEvent(val orderId: UUID, val customerId: UUID, val total: Money)

Why These Rules Matter

  1. Encapsulation:

    • Aggregates enforce consistency by restricting direct access to their internal entities and values.
  2. Testability:

    • Domain logic remains decoupled from infrastructure, enabling unit tests without external dependencies.
  3. Domain-Focused Design:

    • The application aligns with the problem domain, improving clarity and maintainability.

Conclusion

jMolecules DDD annotations provide a structured way to implement Domain-Driven Design principles. By adhering to the constraints, you can maintain a clean separation between your domain logic and infrastructure, ensuring a more robust and scalable system.