There is a distinction between a domain model and the implementation of it. Just because your model shows a relationship Person ---> Campaign ---> Event
does not mean that you have to implement it in this way. IOW, your model shows your analysis and design in an object-oriented way, yet you implement that model in OOP which is limited in how well it can replicate that model in code.
Consider the following.
A Person
is not defined by its ownership of a Campaign
, so campaign can be left out of its knowledge responsibities. On the other hand, a Campaign
is defined by the Event
s that occur as part of its execution, so it is fair to have a collection of events within a campaign. The point that I am making is that each class should have just enough behaviour and knowledge to make it whole.
As for communication between the domain and the persistence layers, consider them as two very distinct systems that are not concerned with the other. All each of them knows is what its responsiblities are and what announcements it makes. For example, the persistence layer knows how to persist data passed to it and to announce that data have been saved. However, the persistence layer does not necessarily need to understand the domain objects. Similarly, the domain layer understands Person
, Campaign
, and Event
but knows nothing about persistence.
The implication of the above is that the domain layer needs to be a whole by itself and should not be dependent on the persistence layer for its data. However, it still needs to be supplied with data to perform its responsibilities. That data can come from either the user interface or the database and is passed to it via a third-party that knows about both domain and persistence layers.
So, in code (pseudo-C#)...
namespace DomainLayer
{
interface IDomainListener
{
void PersonCreated(Person person);
}
class Person
{
private string name;
public Person(string name)
{
this.name = name;
}
public string Name
{
get { return name; }
}
}
class Domain
{
private IDomainListener listener;
public Domain(IDomainListener listener) {
this.listener = listener;
}
public void CreatePerson(string name) {
Person person = new Person(name);
listener.PersonCreated(person);
}
}
}
namespace PersistenceLayer
{
interface IPersistenceListener
{
void PersonDataSaved(int id, object data);
}
class Persistence
{
private IPersistenceListener listener;
public Persistence(IPersistenceListener listener)
{
this.listener = listener;
}
public void SaveData(object data)
{
int id = ...; // save data and return identifier
listener.DataSaved(id, data);
}
}
}
namespace MyApplication
{
class MyController : IDomainListener, IPersistenceListener
{
public void CreatePersonButton_Clicked()
{
Domain domain = new Domain(this);
domain.CreatePerson(NameTextbox.Text);
}
public void PersonCreated(Person person)
{
Persistence persistence = new Persistence(this);
persistence.SavePersonData(person.Name);
}
public void DataSaved(int id, object data)
{
// display data on UI
}
}
}
As you can see, the namespaces represent the different tiers. The XYZListener
interfaces define the announcements that are made by the XYZ
tier. Any other tiers that are interested in these announcements and will respond to them need to implement these interfaces, as does our MyApplication
tier.
When the "create button" is clicked, the controller creates the Domain
facade object for the domain layer and registers itself as a listener. It then calls the CreatePerson
method which instantiates a Person
then announces that this has been done, passing the new instance. The controller responds to this announcement in the PersonCreated
implementation where it spawns a facade of the persistence layer and registers itself as the listener again. It then calls the SaveData
method whichannounces DataSaved
when completed. The implementation of that method then displays the data on the UI.
As you can see, the domain layer and the persistence layer are each aware of only tmemselves and are not concerned with the responsibilities of the other. It is the application logic, manifested here as the controller, that wires the two together.
Back to your specific problem, you could have a method FindPerson
on the persistence, which would announce PersonFound(int id)
. The response by the controller would be to call the persistence layer to retrieve data about campaign and events, then call the domain layer with that data to build the Person
.
Sorry for the long answer...