OOP – Handling Data Fetching for Circular Object References in Domain Models

cdomain-modelobject-oriented

I'm trying to put together some basic models in OOP (C#), and apparently I've got the wrong idea.

If I have a Workman's Comp case, it is applied to a Patient. It also has one or more PatientVisits applied to the case. Each PatientVisit though, also has a Patient associated with it, and of course a Patient can have any number of Visits associated with it.

My original thought was to create basic, real-world reflections of the business models by doing something like:

class Patient {
        int ID;
        string FirstName;
        string LastName;

        List<Visit> Visits;
    }

    class Visit {
        int ID;
        DateTime VisitDate;
        Patient Patient;

        List<DxCode> DxCodes;
    }

    class WCCase {
        int ID;
        Patient Patient;
        DateTime DateEntered;

        List<Visit> Visits;
    }

I naively thought that'll be nice, I'll just load up my model and have everything at hand, very semantic and representative of the domain/real-world, right?

So, I can go the opposite route and fill the parent object and store lists of IDs for related models:

class Patient {
    int ID;
    string FirstName;
    string LastName;

    List<int> VisitIDs;

    public static Patient GetPatient(int id) {}
}

class Visit {
    int ID;
    DateTime VisitDate;
    int PatientID;

    List<DxCode> DxCodes;

    public Static Visit GetVisit(int id) {}
}

class WCCase {
    int ID;
    int PatientID;
    DateTime DateEntered;

    List<int> VisitIDs;

    public static case GetCase(int id) {}
}

This of course allows me to load everything on demand (get an ID and grab the object through it's Get*() method), but seems a bit of a step away from the idea of a nicely representative real-world model and more like a mirror of a database table representation.

I'm guessing there must be some middle ground, but I'd sure like some advice on finding it… Can anyone give some basic examples of how we'd model this without going nuts with references and circulars, etc?

Thanks in advance.

Best Answer

Addressing the Basic Question

if I load a business entity into an object, and it has child objects as well, should I "fill out" all of the child models as well, or leave them null until needed, or...? [reference]

It depends. (thanks Bob?)

Whatever. This is an implementation detail. It should not drive domain design. I am not saying either of the (as of this writing) two answers is wrong. I'm saying this aspect should be better incapsulated and then exposed in domain terms.


Encapsulating Data Fetching

... but seems a bit of a step away from the idea of a nicely representative real-world model and more like a mirror of a database table representation.

I'm guessing there must be some middle ground ...

Domain Analysis

As with any good Gummit bureaucracy you are just a number to them. And so a workers comp thing is who you are, what you do, what they do (to you), etc. This thing sounds like a class and this thing is a number. But it is not an int, it is a "Workers Comp Case File" let's say.

Further, we have all these circular references. Nothing wrong with these per-se from an OO design perspective.


public class WorkersCompCaseFile {
    public int ID { get; protected set; }
    public Patient { get; protected set; }
    public Visits { get; protected set; }
    public WCCase { get; protected set; }

    // here we can load things as desired, and change that
    // implementation isolated from the domain entities involved.

    //Case-wide functionalities
    public bool SuspectFraud() { 
        // lots of object instantiation for complex analysis
    }
}

// CLIENT
WorkersCompCaseFile myHardCase = new WorkersCompCaseFile();
if( myHardCase.HasVisits ) 
    myHardCase.SuspectFraud();  // yeah, everyone is guilty of something.

Data Points

  • We created the proper context to ensure proper global state.
    • Lazy/Eager load as needed
    • Components do not have to construct pieces of, nor continuously examine, the larger context state.
  • With this simple encapsulation - framework - there are so many design possibilities.
  • IDataFetch interface that all classes implement. There could be delegates or event handlers so WorkersCompCaseFile transparently does it for it's components.
  • protected vice public properties
    • the case file totally controls component construction
    • protected int ID - do not expose the ID period. It's an internal unifying concept.
  • Patient, Visit, all have a reference to their containing WorkersCompCaseFile
    • Patient.Visits is a pass-through reference to the case file object.
    • Components might not have an ID property at all.
  • Least Knowledge Principle: The case file exposes components on their behalf. e.g.
    • MyHardCase.Name vice MyHardCase.Patient.Name
    • The client is blissfully ignorant of internal object instantiation and nullness.
    • Internal components can be equally, mutually ignorant.
Related Topic