I am fairly new to using separate layers for the business logic (Domain) and database access logic, but in the course of working things out I've come across a problem to which I still feel I haven't found a great solution.
Clarification My existing solution uses Data Mappers to deal with the database interactions directly. However, as I've further investigated this issue many people have suggested that the Domain layer should not directly communicate with nor contain the Data Mappers that actually perform the database interaction. This is why I placed the Repository objects between the Domain and the necessary Data Mappers but this doesn't feel quite natural or correct. So the real question is what layer naturally exists to handle communication between the Domain and the Data Mappers? Any examples of how to structure it would be appreciated.
For example:
- How do I properly handle retrieving a collection of domain objects within the context of another domain object?
- How do I force the insertion of a single domain object or collection of objects based on an action performed against another object. The case I'm facing currently is that when a Person is attached to a Campaign, then I need to insert all of the Events that need to be executed for that Person for that Campaign.
Many systems employ an independent data layer to handle persistence to and from a database. There are several models for the organization of such a layer. Some use a sort of factory-like implementation, others employ a one-to-one mapping with one data layer class per domain class.
The model for the data layer often depends on style and preference. What is important is separating the persistence layer from the domain layer. I believe there are tools out there that will help you generate this layer, but my PHP knowledge is thin so I can't name any specifically for PHP.
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 aCampaign
, so campaign can be left out of its knowledge responsibities. On the other hand, aCampaign
is defined by theEvent
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
, andEvent
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#)...
As you can see, the namespaces represent the different tiers. The
XYZListener
interfaces define the announcements that are made by theXYZ
tier. Any other tiers that are interested in these announcements and will respond to them need to implement these interfaces, as does ourMyApplication
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 theCreatePerson
method which instantiates aPerson
then announces that this has been done, passing the new instance. The controller responds to this announcement in thePersonCreated
implementation where it spawns a facade of the persistence layer and registers itself as the listener again. It then calls theSaveData
method whichannouncesDataSaved
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 announcePersonFound(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 thePerson
.Sorry for the long answer...
I would look at the data abstraction layers used by PHPCake and Symfony.
Gabriel, this is called the "impedance matching problem." There are many solutions around, from heavyweight ones like J2EE entity beans to Ruby ActiveRecord to simply coding a hand connection.
Update
Okay, well, its hard to see exactly how to attack this without a lot more information, but here's the basic approach.
Any of these sorts of architectural issues are driven by non-functional requirements like performance; in addition, there is a correctness issue here, in that you want to make sure updates are done in the correct order. So, you're going to need to think about the workload, which is to say the pattern of usage in real-world application. With that in mind, you basically have a couple of issues: first, the base data types in your application may not map correctly to the data base (eg, what's a VARCHAR property represented as in your code?), and second your domain model may not map cleanly to your database model.
What you would like is to have the database and the dmain model work out so that one instance of a domain object is exactly a row of a table in your database model; in large-scale applications you can rarely do this because of either performance constraints or constraints imposed by a pre-existing database model.
Now, if you completely control your database model, it simplifies things somewhat, because then you can make your database model more closely resemble the domain. This might mean the database model is somewhat denormalized, but if so, you can (depending on your database) handle that with views, or just not have a completely normalized database. Normalization is a useful theoretical construct, but that doesn't mean you can't relax it in a real system.
If you don't completely control your database model, then you need a layer of objects that make the mapping. You've got a bunch of options to choose from in implementing that: you can build views or denormalized tables in the database, you can build intermediate objects, or you can do some of both, or even have several steps of both (ie, an intermediate object that accesses a denormalizaed table.)
At that point, though, you run into issues with "don't repeat yourself" and "do the simplest thing that will possibly work." Think about what is most likely to change? Your domain model? If you've got a strong domain model, that's less likely --- the business changes relatively rarely. The exact representation of data in the database? A little more common. Or, most commonly, the exact patterns of use (like discovering a need to handle concurrent updates.) So, when you think about that, what do you need to do to make it as easy as possible to deal with the most common changes.
I realize this isn't giving you very precise instructions, but I don't think we can offer precise instructions without knowing a whole lot about your applicaiton. But then I also kind of get the impression you're wondering about what the "right" way of handling this would be, while you are already working with something that more or less does the job. So, I'd end up by asking "what are you unhappy with now?" and "How would you like to solve that?"