I've written myself a nice simple little domain model, with an object graph that looks like this:
-- Customer
-- Name : Name
-- Account : CustomerAccount
-- HomeAddress : PostalAddress
-- InvoiceAddress : PostalAddress
-- HomePhoneNumber : TelephoneNumber
-- WorkPhoneNumber : TelephoneNumber
-- MobilePhoneNumber : TelephoneNumber
-- EmailAddress : EmailAddress
This structure is completely at odds with the legacy database I'm having to work with, so I've defined a flat DTO which contains the data for each element in the customer graph - I have views and stored procedures in the database which allow me to interact with the data using this flat structure in both directions, this all works fine & dandy :)
Flattening the domain model into a DTO for insert/update is straightfoward, but what I'm having trouble with is taking a DTO and creating the domain model from it... my first thought was to implement a visitor which would visit each element in the customer graph, and inject values from the DTO as necessary, something a bit like this:
class CustomerVisitor
{
public CustomerVisitor(CustomerDTO data) {...}
private CustomerDTO Data;
public void VisitCustomer(Customer customer)
{
customer.SomeValue = this.Data.SomeValue;
}
public void VisitName(Name name)
{
name.Title = this.Data.NameTitle;
name.FirstName = this.Data.NameFirstName;
name.LastName = this.Data.NameLastName;
}
// ... and so on for HomeAddress, EmailAddress etc...
}
That's the theory and it seems like a sound idea when it's laid out simply like that :)
But for this to work the entire object graph would need to be constructed before the visitor erm, visited, otherwise I'd get NRE's left right and centre.
What I want to be able to do is let the visitor assign objects to the graph as it visits each element, with the goal being to utilize the Special Case pattern for objects where data is missing in the DTO, eg.
public void VisitMobilePhoneNumber(out TelephoneNumber mobileNumber)
{
if (this.Data.MobileNumberValue != null)
{
mobileNumber = new TelephoneNumber
{
Value = this.Data.MobileNumberValue,
// ...
};
}
else
{
// Assign the missing number special case...
mobileNumber = SpecialCases.MissingTelephoneNumber.Instance;
}
}
Which I honestly thought would work, but the C# throws me an error on:
myVisitor.VisitHomePhone(out customer.HomePhoneNumber);
Since you can't pass ref/out parameters in this way :(
So I'm left with visiting independent elements and reconstructing the graph when its done:
Customer customer;
TelephoneNumber homePhone;
EmailAddress email;
// ...
myVisitor.VisitCustomer(out customer);
myVisitor.VisitHomePhone(out homePhone);
myVisitor.VisitEmail(out email);
// ...
customer.HomePhoneNumber = homePhone;
customer.EmailAddress = email;
// ...
At this point I'm aware that I'm quite far away from the Visitor Pattern and am much closer to a Factory, and I'm starting to wonder whether I approached this thing wrong from the start..
Has anyone else run into a problem like this? How did you overcome it? Are there any design patterns which are well suited to this scenario?
Sorry for posting such a looong question, and well done for reading this far :)
EDIT In response to the helpful answers from Florian Greinacher and gjvdkamp, I settled on a relatively simple factory implementation that looks like this:
class CustomerFactory
{
private CustomerDTO Data { get; set; }
public CustomerFactory(CustomerDTO data) { ... }
public Customer CreateCustomer()
{
var customer = new Customer();
customer.BeginInit();
customer.SomeFoo = this.Data.SomeFoo;
customer.SomeBar = this.Data.SomeBar
// other properties...
customer.Name = this.CreateName();
customer.Account = this.CreateAccount();
// other components...
customer.EndInit();
return customer;
}
private Name CreateName()
{
var name = new Name();
name.BeginInit();
name.FirstName = this.Data.NameFirstName;
name.LastName = this.Data.NameLastName;
// ...
name.EndInit();
return name;
}
// Methods for all other components...
}
I then wrote a ModelMediator class to handle interaction between the data layer and the domain model...
class ModelMediator
{
public Customer SelectCustomer(Int32 key)
{
// Use a table gateway to get a customer DTO..
// Use the CustomerFactory to construct the domain model...
}
public void SaveCustomer(Customer c)
{
// Use a customer visitor to scan for changes in the domain model...
// Use a table gateway to persist the data...
}
}