Do we achieve 100% Persistence Ignorance solution if we’re not using ORM’s POCO objects to model the Domain?

Persistence ignorance is ability to retrieve/persist standard objects, where standard objects are considered as classes focused on particular business problem and thus don’t contain any infrastructure-related logic. In DDD a Repository pattern is used to achieve PI.

Assuming we don’t use ORM’s POCO objects to model the Domain, then our Domain objects don’t have to adhere to design requirements of a particular O/RM ( such as using default constructor or marking properties as virtual ). Such Domain objects would be considered Persistence Ignorant.

But if we want these Domain Objects to also support features such as lazy loading, then their properties must contain some logic which at some point would need to check whether related data was already loaded and if it wasn’t, it would contact the appropriate Repository and request the related data.

Now even though these Domain Object are completely unaware of how they are being persisted and as such their Domain is completely decoupled from the underlying DAL provider, couldn’t we still argue that such Domain Ojects contain Infrastructure-related logic and thus still violate PI?

10

After coming across the same problem, here is the solution I implemented for it.

Domain

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>public class DomainObject : AggregateRootBase //implements IAggregateRoot
{
private ChildEntity _ChildEntity = null;
public ChildEntity ChildEntity
{
get
{
OnChildEntityRequested();
return _ChildEntity;
}
}
public event EventHandler ChildEntityRequested;
protected void OnChildEntityRequested()
{
if (ChildEntityRequested != null) { ChildEntityRequested(this, new EventArgs()); }
}
public static void SetChildEntity(DomainObject destination, ChildEntity child)
{
//To set or not to set, that is the question.
destination._ChildEntity = child;
}
}
</code>
<code>public class DomainObject : AggregateRootBase //implements IAggregateRoot { private ChildEntity _ChildEntity = null; public ChildEntity ChildEntity { get { OnChildEntityRequested(); return _ChildEntity; } } public event EventHandler ChildEntityRequested; protected void OnChildEntityRequested() { if (ChildEntityRequested != null) { ChildEntityRequested(this, new EventArgs()); } } public static void SetChildEntity(DomainObject destination, ChildEntity child) { //To set or not to set, that is the question. destination._ChildEntity = child; } } </code>
public class DomainObject : AggregateRootBase //implements IAggregateRoot
{
    private ChildEntity _ChildEntity = null;
    public ChildEntity ChildEntity
    {
        get
        {
            OnChildEntityRequested();
            return _ChildEntity;
        }
    }

    public event EventHandler ChildEntityRequested;
    protected void OnChildEntityRequested()
    {
        if (ChildEntityRequested != null) { ChildEntityRequested(this, new EventArgs()); }
    }

    public static void SetChildEntity(DomainObject destination, ChildEntity child)
    {
        //To set or not to set, that is the question.
        destination._ChildEntity = child;
    }
}

Repository Implementation

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>public class DomainObjectRepository : List<DomainObjectTracker>, IDomainObjectRepository
{
private void Load() //Method called by constructor or other loading mechanism.
{
foreach (DomainObjectDataModel dodm in ORMSystem) //Iterating through each object returned from ORM.
{
DomainObject do = Translate(dodm); //Translate into domain object.
do.ChildEntityRequested += new EventHandler(DomainObject_ChildEntityRequested);
DomainObjectTracker tracker = new DomainObjectTracker(do, dodm.Key);
base.Add(tracker);
}
}
protected void DomainObject_ChildEntityRequested(object sender, EventArgs e)
{
DomainObject source = sender as DomainObject;
//Here, you could check to see if it is null or stale before loading it.
//if (source.ChildEntity == null || IsStale(source.ChildEntity))
DomainObjectTracker tracker = base[IndexOf(source)];
ChildEntity entity = LoadChildEntity(tracker.Key); //Load the child entity from ORM.
DomainObject.SetChildEntity(source, entity);
}
}
</code>
<code>public class DomainObjectRepository : List<DomainObjectTracker>, IDomainObjectRepository { private void Load() //Method called by constructor or other loading mechanism. { foreach (DomainObjectDataModel dodm in ORMSystem) //Iterating through each object returned from ORM. { DomainObject do = Translate(dodm); //Translate into domain object. do.ChildEntityRequested += new EventHandler(DomainObject_ChildEntityRequested); DomainObjectTracker tracker = new DomainObjectTracker(do, dodm.Key); base.Add(tracker); } } protected void DomainObject_ChildEntityRequested(object sender, EventArgs e) { DomainObject source = sender as DomainObject; //Here, you could check to see if it is null or stale before loading it. //if (source.ChildEntity == null || IsStale(source.ChildEntity)) DomainObjectTracker tracker = base[IndexOf(source)]; ChildEntity entity = LoadChildEntity(tracker.Key); //Load the child entity from ORM. DomainObject.SetChildEntity(source, entity); } } </code>
public class DomainObjectRepository : List<DomainObjectTracker>, IDomainObjectRepository
{
    private void Load() //Method called by constructor or other loading mechanism.
    {
        foreach (DomainObjectDataModel dodm in ORMSystem) //Iterating through each object returned from ORM.
        {
             DomainObject do = Translate(dodm); //Translate into domain object.
             do.ChildEntityRequested += new EventHandler(DomainObject_ChildEntityRequested);
             DomainObjectTracker tracker = new DomainObjectTracker(do, dodm.Key);
             base.Add(tracker);
        }
    }

    protected void DomainObject_ChildEntityRequested(object sender, EventArgs e)
    {
        DomainObject source = sender as DomainObject;
        //Here, you could check to see if it is null or stale before loading it.
        //if (source.ChildEntity == null || IsStale(source.ChildEntity))

        DomainObjectTracker tracker = base[IndexOf(source)];
        ChildEntity entity = LoadChildEntity(tracker.Key); //Load the child entity from ORM.
        DomainObject.SetChildEntity(source, entity);
    }
}

After examining my solution, you may be thinking, “Isn’t calling an event infrastructure related code, it certainly doesn’t relate to my domain?”. While that may be true, if you think about it solely in terms of PI, then you realize that you’re just offering a message from your domain saying, “Hey, I’m about to access this child entity.” which allows your repository to respond with, “Hold on, if you need one (or a new one), I got it right here in my db so let me give this to you before you continue!”.

There are a few caveats here. One, it means that you need an event for every child entity within an aggregate. Two, it breaks encapsulation because you are allowing a public way of setting the child entity. On the flip side, the benefits are expressive code, infrastructure implementations are easily separated from the domain, domain objects are PI, and the ORM remains encapsulated behind the repository implementation.

If someone has a better way of solving this problem, I’m all eyes!

EDIT
Also, even though my answer provides a solution for PI, I agree with the commenters above. You need to evaluate if DDD is even the right answer for you because, while it makes a project easier to maintain and simplifies what would otherwise be a highly complex project, it comes at a cost (usually up-front development time and training your team to organize code properly and utilize the various patterns).

3

Any ORM worth its salt is not going to pollute your model with persistence concerns such as lazy loading. Typically this will be accomplished using a technique such as a transparent proxy, which has the interface of the fully loaded object/collection), but defers loading it until it is accessed.

If your ORM doesn’t do this for you, I would humbly suggest finding another ORM.

An ORM should not force you to have a persistence-oriented version of your model and a domain-oriented version of your model, and require you to map between the two models. This is what an object-relational mapper does; that’s the whole reason it exists.

If you choose to not use ORM POCOs for your domain logic, fine. You also give up everything that come with them, such as lazy loading and caching, which become features that you need to account for (if you want) as part of your domain or assume they will be provided by the ORM at a lower layer.

Another challenge is the extra step to shuttle the data back and forth from the ORM POCOs to your Domain POCOs, which can be done with tools like Automapper but cause issues such as when updating an object you may need to force the ORM to retrieve it so it can be updated, depending on how the ORM is implemented.

How much are you really gaining by requiring 100% purity? Is not violating PI really so important?

2

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật