# HasOne vs HasMany vs BelongTo in DDDplus: Association Types Explained

> Understand HasOne, HasMany, and BelongTo associations in DDDplus. Learn how to model domain relationships for single ownership, collection aggregation, and external references to build robust applications.

- Repository: [Funky Gao/cp-ddd-framework](https://github.com/funkygao/cp-ddd-framework)
- Tags: deep-dive
- Published: 2026-03-02

---

**DDDplus models domain relationships as first-class association interfaces where `HasOne` represents single ownership, `HasMany` represents collection aggregation, and `BelongTo` represents external references without lifecycle control.**

The `funkygao/cp-ddd-framework` treats object associations as explicit contracts rather than implicit fields, allowing compile-time reasoning about ownership and cardinality. These three association types—defined in the `dddplus-spec` module—enable developers to express whether a domain object contains, aggregates, or merely references another entity while keeping infrastructure concerns out of the domain layer.

## HasOne: Single Ownership Relationships

The **HasOne** association expresses a strict one-to-one containment where the host object owns exactly one instance of the associated entity. According to the source code in [[`dddplus-spec/src/main/java/io/github/dddplus/model/association/HasOne.java`](https://github.com/funkygao/cp-ddd-framework/blob/main/dddplus-spec/src/main/java/io/github/dddplus/model/association/HasOne.java)](https://github.com/funkygao/cp-ddd-framework/blob/master/dddplus-spec/src/main/java/io/github/dddplus/model/association/HasOne.java#L13-L19), this interface signifies that the associated object shares the same aggregate lifecycle as its host.

When a `Task` has one `Operator`, the `Task` acts as the aggregate root responsible for the `Operator`'s consistency. The domain model declares this through an interface extending `HasOne<T>`:

```java
@KeyRelation(whom = Operator.class, type = KeyRelation.Type.HasOne)
public interface Task extends IAggregateRoot {
    interface OperatorRef extends HasOne<Operator> { }
    @Delegate private OperatorRef operator;
}

```

The concrete implementation resides in the infrastructure layer, typically delegating to a DAO:

```java
public class TaskOperator implements Task.OperatorRef {
    private final OperatorDao dao;
    private final String taskId;

    @Override
    public Operator get() {
        return dao.findOperatorByTaskId(taskId);
    }
}

```

## HasMany: Collection Aggregation

The **HasMany** association handles one-to-many relationships where the host aggregates zero or more instances of another entity. As defined in [[`dddplus-spec/src/main/java/io/github/dddplus/model/association/HasMany.java`](https://github.com/funkygao/cp-ddd-framework/blob/main/dddplus-spec/src/main/java/io/github/dddplus/model/association/HasMany.java)](https://github.com/funkygao/cp-ddd-framework/blob/master/dddplus-spec/src/main/java/io/github/dddplus/model/association/HasMany.java#L68-L71), this interface extends `IBag` to provide collection-like semantics while maintaining domain encapsulation.

This pattern applies when a `Task` manages multiple `Orders`. The domain interface can expose domain-specific query methods beyond basic collection operations:

```java
@KeyRelation(whom = Order.class, type = KeyRelation.Type.HasMany)
public interface Task extends IAggregateRoot {
    interface Orders extends HasMany<Order> {
        List<Order> pendingOrders();
    }
    @Delegate private Orders orders;
}

```

Infrastructure implementations handle the persistence details:

```java
public class TaskOrders implements Task.Orders {
    private final OrderDao dao;
    private final String taskId;

    @Override
    public List<Order> pendingOrders() {
        return dao.findPendingByTaskId(taskId);
    }
}

```

## BelongTo: External References Without Ownership

The **BelongTo** association represents the inverse relationship—when an object references an external entity it does not own. As implemented in [[`dddplus-spec/src/main/java/io/github/dddplus/model/association/BelongTo.java`](https://github.com/funkygao/cp-ddd-framework/blob/main/dddplus-spec/src/main/java/io/github/dddplus/model/association/BelongTo.java)](https://github.com/funkygao/cp-ddd-framework/blob/master/dddplus-spec/src/main/java/io/github/dddplus/model/association/BelongTo.java#L33-L39), this indicates that the host merely holds a pointer to an object managed by another aggregate.

A `Carton` that belongs to a `CheckTask` uses this pattern to reference its parent without controlling the `CheckTask`'s lifecycle:

```java
@KeyRelation(whom = CheckTask.class, type = KeyRelation.Type.BelongTo)
public class Carton {
    private BelongToCheckTask ownerTask;
    
    public interface BelongToCheckTask extends BelongTo<CheckTask> { }
}

```

Accessing the reference follows a simple getter pattern:

```java
Carton carton = repo.get(cartonNo);
CheckTask task = carton.ownerTask().get();

```

## KeyRelation Annotations and Visualization

All three association types integrate with the [`KeyRelation`](https://github.com/funkygao/cp-ddd-framework/blob/master/dddplus-spec/src/main/java/io/github/dddplus/dsl/KeyRelation.java#L49-L59) annotation system, which enables static analysis and diagram generation. The annotation's `Type` enum distinguishes between ownership models:

- **HasOne**: Renders as `*--*` in PlantUML (composition)
- **HasMany**: Renders as `*--N` in PlantUML (aggregation)
- **BelongTo**: Renders as `--|>` in PlantUML (dependency/association)

The [`PlantUmlRenderer`](https://github.com/funkygao/cp-ddd-framework/blob/master/dddplus-visualization/src/main/java/io/github/dddplus/ast/view/PlantUmlRenderer.java) processes these annotations to generate architectural diagrams that reflect the actual domain model structure.

## Implementation Patterns and Lifecycle Boundaries

Choosing between these associations depends on aggregate boundaries and lifecycle ownership:

**Use HasOne when:**
- The associated object cannot exist independently of the host
- You need strict one-to-one cardinality with shared transaction boundaries

**Use HasMany when:**
- The host manages a collection of related entities
- Domain logic requires filtering or querying subsets of the collection (e.g., `pendingOrders()`)

**Use BelongTo when:**
- The referenced object belongs to a different aggregate root
- The host needs read-only access without persistence responsibility
- Avoiding circular dependencies between aggregates

## Summary

- **HasOne** ([`dddplus-spec/.../HasOne.java`](https://github.com/funkygao/cp-ddd-framework/blob/main/dddplus-spec/.../HasOne.java)): One-to-one ownership where the host contains exactly one instance of the associated entity and manages its lifecycle.
- **HasMany** ([`dddplus-spec/.../HasMany.java`](https://github.com/funkygao/cp-ddd-framework/blob/main/dddplus-spec/.../HasMany.java)): One-to-many aggregation exposing collection semantics through domain-specific interfaces that extend `IBag`.
- **BelongTo** ([`dddplus-spec/.../BelongTo.java`](https://github.com/funkygao/cp-ddd-framework/blob/main/dddplus-spec/.../BelongTo.java)): Reference association indicating the host belongs to an external entity without ownership or lifecycle control.
- **KeyRelation annotation**: Marks associations for compile-time analysis and PlantUML visualization (`*--*`, `*--N`, `--|>` respectively).
- **Separation of concerns**: Domain interfaces declare relationships via `@Delegate` fields while infrastructure layers provide concrete implementations (DAO, cache, remote services).

## Frequently Asked Questions

### What is the difference between HasOne and BelongTo in DDDplus?

**HasOne** indicates the host owns the associated entity and controls its lifecycle (composition), while **BelongTo** indicates the host merely references an external entity it does not own (association). In the `cp-ddd-framework`, `HasOne` is used when a `Task` contains an `Operator`, whereas `BelongTo` is used when a `Carton` references its parent `CheckTask` without managing it.

### Can HasMany collections contain domain-specific query methods?

Yes. Unlike generic collections, `HasMany` implementations in DDDplus often expose domain-specific methods such as `pendingOrders()` or `activeUsers()` that encapsulate business rules. The interface extends `HasMany<T>` (which itself extends `IBag`) while adding custom methods that delegate to appropriate infrastructure implementations.

### How does DDDplus visualize these associations in UML diagrams?

The framework uses the `@KeyRelation` annotation with a `Type` enum to drive the [`PlantUmlRenderer`](https://github.com/funkygao/cp-ddd-framework/blob/master/dddplus-visualization/src/main/java/io/github/dddplus/ast/view/PlantUmlRenderer.java). HasOne renders as a filled diamond composition (`*--*`), HasMany as a collection aggregation (`*--N`), and BelongTo as a simple association or dependency (`--|>`), accurately reflecting ownership semantics in generated documentation.