Implementing Data Persistence and Object Creation in Domain-Driven Design

Decoupling the Domain and Infrastructure Layers Domain-Driven Design enforces a strict layered architecture where each layer only interacts with the layer direct below it. The user interface layer depends on the application layer, which in turn depends on the domain layer, which finally interacts with the infrastructure layer.

In traditional Controller-Service-Dao code structures, it's common to find SQL statements embedded within Service layer code or frequent data object manipulations coupled with DAO calls. This pattern results in infrastructure logic leaking into the core business logic.

Within DDD's layered structure, this leakage means infrastructure concerns contaminate the domain layer. Domain models become distracted from pure business logic, creating dependencies on lower technical layers. Any changes to data logic then require modifications within the domain layer, forcing re-testing of domain-infrastructure interactions. Switching to a different database technology becomes particularly troublesome, requiring extensive domain layer adjustments to reconcile business logic with new SQL or data object requirements.

The Repository pattern addreses this by creating a separation layer between the domain and infrastructure layers, reducing coupling and mutual impact.

Repository Pattern This pattern inserts a thin repository layer between domain logic and data handling logic. It consists of a repository interface and its implementation. The interface provides domain layer access to data persistence operations, while the implementation contains the actual persistence logic. Each aggregate is paired with a repository responsible for persisting its data. The domain layer programs against the repository interface. The persistence process within an aggregate involves converting Domain Objects (DO) to Persistence Objects (PO).

When database technology changes or data logic requires modification, business logic interfaces remain untouched. Only the repository implementation needs updating, preserving the purity of domain layer business logic.

The following example demonstrates repository implementation for a Staff aggregate.

Staff Domain Object and Persistence Object Definitions:

/**
 * Staff Aggregate
 */
public class StaffMember {
    private String employeeId;
    private String fullName;
    private WorkLocation workLocation;

    public void commenceShift() {
        // Implementation
    }

    public void concludeShift() {
        // Implementation
    }

    // Accessor and mutator methods
    public String getEmployeeId() { return employeeId; }
    public void setEmployeeId(String employeeId) { this.employeeId = employeeId; }
    public String getFullName() { return fullName; }
    public void setFullName(String fullName) { this.fullName = fullName; }
    public WorkLocation getWorkLocation() { return workLocation; }
    public void setWorkLocation(WorkLocation workLocation) { this.workLocation = workLocation; }
}
/**
 * Persistence Object for Staff Aggregate
 */
public class StaffMemberPO {
    private String employeeId;
    private String fullName;
    private WorkLocation workLocation;

    // Accessor and mutator methods
    public String getEmployeeId() { return employeeId; }
    public void setEmployeeId(String employeeId) { this.employeeId = employeeId; }
    public String getFullName() { return fullName; }
    public void setFullName(String fullName) { this.fullName = fullName; }
    public WorkLocation getWorkLocation() { return workLocation; }
    public void setWorkLocation(WorkLocation workLocation) { this.workLocation = workLocation; }
}
/**
 * Value Object for Work Location
 */
public class WorkLocation {
    private String state;
    private String city;
    private String district;
}

Repository Interface Definition:

/**
 * Staff Aggregate Repository Interface
 */
public interface StaffRepository {
    void storeMember(StaffMemberPO memberPO);
    void modifyMember(StaffMemberPO memberPO);
    StaffMemberPO fetchById(String id);
}

Repository Implementation:

/**
 * Staff Repository Implementation
 */
public class StaffRepositoryImpl implements StaffRepository {
    @Resource
    private StaffDao staffDao;

    @Override
    public void storeMember(StaffMemberPO memberPO) {
        staffDao.insertMember(memberPO);
    }

    @Override
    public void modifyMember(StaffMemberPO memberPO) {
        staffDao.updateMember(memberPO);
    }

    @Override
    public StaffMemberPO fetchById(String id) {
        return staffDao.selectById(id);
    }
}

Domain Service Implementation: Subsequent changes to the infrastructure layer require no code alterations in the domain layer. As long as the repository interface remains stable, domain logic stays unchanged, ensuring the domain layer's stability. Domain services aim for enterprise-level reusability, making this stability essential.

import javax.annotation.Resource;

/**
 * Staff Domain Service Aggregate
 */
public class StaffDomainService {
    @Resource
    private StaffRepository staffRepository;

    public void registerMember(StaffMemberPO memberPO) {
        staffRepository.storeMember(memberPO);
    }
}

Factory Pattern When creating a Domain Object, the aggregate root and its dependent objects must be instantiated together. Placing this initialization logic directly in the aggregate root constructor would make it excessively complex. Instead, common DO initialization logic is centralized in a factory.

The Factory pattern encapsulates the complex object creation process with in an aggregate, handling the instantiation of the aggregate root, entities, and value objects. During DO creation, a repository fetches the PO from the database, and the factory converts the PO to a DO.

Factories can also manage the reverse conversion from DO to PO, facilitating data persistence.

/**
 * Factory for Staff Aggregate
 * Handles conversion between DO and PO
 */
public class StaffFactory {
    /**
     * Initializes a Domain Object from a Persistence Object.
     * @param memberPO The source Persistence Object.
     * @return The initialized Domain Object.
     */
    protected StaffMember buildStaffMember(StaffMemberPO memberPO) {
        StaffMember member = new StaffMember();
        member.setEmployeeId(memberPO.getEmployeeId());
        member.setFullName(memberPO.getFullName());
        member.setWorkLocation(memberPO.getWorkLocation());
        return member;
    }

    /**
     * Converts a Domain Object to a Persistence Object.
     * @param member The source Domain Object.
     * @return The converted Persistence Object.
     */
    protected StaffMemberPO buildStaffMemberPO(StaffMember member) {
        StaffMemberPO memberPO = new StaffMemberPO();
        memberPO.setEmployeeId(member.getEmployeeId());
        memberPO.setFullName(member.getFullName());
        memberPO.setWorkLocation(member.getWorkLocation());
        return memberPO;
    }
}

Tags: Domain-Driven Design Repository Pattern Factory Pattern Software Architecture data persistence

Posted on Mon, 28 Sep 2026 16:27:05 +0000 by AliasXNeo