Understanding Association, Aggregation, and Composition in Domain Modelling

When modeling your domain, not all relationships between classes are equal.

In Object-Oriented Design (OOD) and Domain-Driven Design (DDD), three core relationship types define how objects relate to each other: Association, Aggregation, and Composition.

These relationships go beyond mere “connections” — they express ownership, lifecycle, and dependency between entities.
Understanding the difference is critical for designing clean, maintainable software — and for making the right calls in your ORM (like Entity Framework or JPA) implementation.

Let’s unpack these three with examples from our GoProcure project 👇


1. Association — A Simple Link

Association is the most basic relationship between two classes. It simply means: “These two classes know about each other or interact in some way.”
There’s no ownership or lifecycle dependency implied.

In the GoProcure system, consider the relationship between Vendor and Item.

A Vendor supplies many Items, and each Item belongs to one Vendor.
But deleting a Vendor doesn’t necessarily delete the Items. In our business case, Item is an independent entity. If we terminate a vendor’s contract, it doesn’t mean the item/product is lost. We can simply pick another vendor next time as a preferred supplier for the item.

public class Vendor
{

   public Guid Id { get; private set; }

    public string Name { get; private set; }

   // Association

    public ICollection<Item> Items { get; private set; } = new List<Item>();

}

public class Item
{
    public Guid Id { get; private set; }
    public string Name { get; private set; }
    public Guid VendorId { get; private set; }
    public Vendor Vendor { get; private set; }
}

Here, both entities exist independently — this is a loose coupling.
In UML, we represent this as an arrow (—>) or solid line without diamonds.

💡 ORM Note:
In EF Core, this is typically configured with a HasOne().WithMany() relationship.
You can decide the cascade delete behavior depending on whether items should survive if the vendor is deleted.


2. Aggregation — “Has-A” but Independent Lifecycle

Aggregation expresses a “whole-part” relationship, but the parts can exist independently of the whole. This relationship suggests ownership without dependency.

In GoProcure, consider PurchaseRequest ↔ Approvals.

A PurchaseRequest may have multiple Approvals.
If the request is deleted, we may still keep the Approval records for audit or compliance — so the lifecycles are loosely connected.

public class PurchaseRequest
{
    public Guid Id { get; private set; }
    public string Title { get; private set; }
    // Aggregation
public ICollection<Approval> Approvals { get; private set; } = new List<Approval>();
}
public class Approval
{
    public Guid Id { get; private set; }
    public string ApproverName { get; private set; }
    public DateTime DateApproved { get; private set; }
    public Guid PurchaseRequestId { get; private set; }
    public PurchaseRequest PurchaseRequest { get; private set; }
}

In UML, Aggregation is represented with a hollow diamond (o—).

💡 Think of it like this:
A PurchaseRequest has Approvals, but those approvals can outlive the request itself.

Real-world analogy:
A University Department aggregates Professors.
If the department closes, professors don’t cease to exist — they can transfer elsewhere.


3. Composition — “Part-Of” and Dependent Lifecycle

Composition is the strongest form of relationship — it implies ownership and dependency. If the parent (whole) dies, all the children (parts) die with it.

In GoProcure, the classic example is PurchaseOrder ↔ PurchaseOrderLine.

A PurchaseOrderLine cannot exist without its PurchaseOrder.
If we delete the order, all its lines must be deleted as well.

public class PurchaseOrder
{
    public Guid Id { get; private set; }
    public DateTime DateCreated { get; private set; }
    // Composition
public ICollection<PurchaseOrderLine> Lines { get; private set; } = new List<PurchaseOrderLine>();
   public void AddLine(PurchaseOrderLine line)
{
       Lines.Add(line);
   }
}
public class PurchaseOrderLine
{
    public Guid Id { get; private set; }
    public string ItemName { get; private set; }
    public int Quantity { get; private set; }
    public Guid PurchaseOrderId { get; private set; }
}

In UML, Composition is represented with a filled diamond (◆).

💡 Think of it like this:
A Purchase Order owns its lines.
If you destroy the order, there’s no reason for its line items to exist.

Real-world analogy:
A House is composed of Rooms.
If the house is demolished, its rooms no longer exist.


Quick Comparison Table

Concept UML Symbol Lifecycle Dependency Ownership Example (GoProcure)
Association → or — Independent None Vendor ↔ Item
Aggregation o— Part can outlive whole Weak ownership PurchaseRequest ↔ Approval
Composition Part dies with whole Strong ownership PurchaseOrder ↔ PurchaseOrderLine

Implementation Tips (EF Core / JPA)

  • Use cascade delete only for composition relationships.

  • For aggregation, prefer manual cleanup or soft delete.

  • For association, configure relationship direction carefully — use WithMany() only where needed.

  • Avoid bidirectional relationships unless navigation both ways is truly required — they increase complexity and risk circular references. I will discuss how to handle bidirectional realtionship in another post.


Summary

  • Association → Entities interact, but no ownership.

  • Aggregation → “Has-A” relationship, weak ownership.

  • Composition → “Part-Of” relationship, strong ownership and shared lifecycle.

Understanding these distinctions ensures you:

  • Design cleaner class models

  • Avoid unintended cascading deletes

  • Communicate business intent clearly through your code

In the next article, we’ll begin implementing our domain models in code — applying these principles inside our .NET 8 GoProcure project, with equivalent notes for Java (JPA) developers.

Stay tuned — the engineering part starts to get exciting from here


Leave a Comment

Your email address will not be published. Required fields are marked *