Using the Domain Objects Persistence Pattern in .NET
Abstract Domain objects in an application represent the core
data and business validation rules relating to it. And, domain
objects are usually central to the entire application and used
by most subsystems. Therefore, their good design is critical for
a good application design that is robust, high performing, and
yet flexible.
When it comes to developing object oriented applications that
use relational databases, the domain object design has to be
consistent with the database design. This make them easier to
understand because they represent real-life "entities" and their
relationships with each other. Therefore, in many situations,
the domain objects are "mapped" to the relational database
tables and relationships between tables. However, it is very
easy to get this mapping wrong and end up with an undesirable
domain object design. A good design for domain objects requires
a solid understanding of object oriented and relational
fundamentals on the part of developers.
Domain Objects Persistence Pattern attempts to provide a
solution for domain object mapping to the relational databases
that decouples the domain objects from the persistence logic.
The domain objects in this pattern are unaware of the objects
that persist them because the dependency is only one-way (from
persistence objects to domain objects). This makes the domain
objects design much simpler and easier to understand. It also
hides the persistence objects from other subsystems in the
application that are using the domain objects. This also works
in distributed systems where only the domain objects are passed
around. In this context, an attempt is made to incorporate the
Factory Pattern into this pattern to help decouple domain
objects and persistence logic. Scope Domain Objects, Persisting
Domain Objects. Problem Definition Domain objects form the
backbone of any application. They capture the core data model
from the database and also the business rules that apply to this
data. It is very typical for most subsystems of an application
to rely on these common domain objects. This means that the
closer the domain objects map to the data model in the database,
the easier it is for the application developers to understand
and use them because they mimic real-life "entities" and
"relationships" as represented in the database.
If domain objects are not separated from the rest of the
application, we end up with duplication of code everywhere.
Similarly, if domain objects are not separated from the
persistence code, we face situations where any subsystem using
the domain objects also knows and depends on the persistence
objects. And, any change in persistence objects affects the
entire application, hence a bad design. Solution One way to
achieve the above mentioned goals is to separate the domain
objects into a separate subsystem and let the entire application
use them wherever it needs domain data. Additionally, we should
separate domain objects from the persistence code. This
double-decoupling allows us on one hand to avoid code
duplication and on the other to hide the persistence details
from the domain objects and make it more flexible in case it
needs to change. The domain objects and the rest of the
application is totally unaffected whether the data is coming
from a relational database or any other source (e.g. XML, flat
files, or Active Directory/LDAP).
In separating the persistence logic from domain objects, we
ensure that the domain objects have no dependency on the
persistence code. This allows the domain objects to become
available in environments where we don't even want to expose our
persistence code. Sample Code In this sample, we will look at a
Customer object from Northwind database mapped to the
"Customers" table in the database. public class Customer { //
Private data members String _customerId; String _companyName;
String _contactName; String _contactTitle;
public Customer() {}
// Properties for Customer object public String CustomerId {
get { return _customerId; } set { _customerId = value;} }
public String CompanyName { get { return _companyName; } set {
_companyName = value;} }
public String ContactName { get { return _contactName; } set {
_contactName = value;} }
public String ContactTitle { get { return _contactTitle; } set
{ _contactTitle = value;} } }
public interface ICustomerFactory { // Standard transactional
methods for single-row operations void Load(Customer cust); void
Insert(Customer cust); void Update(Customer cust); void
Delete(Customer cust);
// Query method to return a collection ArrayList
FindCustomersByState(String state);
}
public class CustomerFactory : ICustomerFactory { // Standard
transactional methods for single-row operations void
Load(Customer cust) { /* Implement here */ } void
Insert(Customer cust) { /* Implement here */ } void
Update(Customer cust) { /* Implement here */ } void
Delete(Customer cust) { /* Implement here */ }
// Query method to return a collection ArrayList
FindCustomersByState(String state) { /* Implement here */ } }
Below is an example of how a client application will use this
code.
public class NorthwindApp { static void Main (string[] args) {
Customer cust = new Customer(); CustomerFactory custFactory =
new CustomerFactory();
// Let's load a customer from Northwind database.
cust.CustomerId = "ALFKI"; custFactory.load(cust);
// Pass on the Customer object FooBar(cust);
// custList is a collection of Customer objects ArrayList
custList = custFactory.FindCustomersByState("CA"); } } As you
can see above, the "load" method loads the Customer object from
the database based on the CustomerId. Once the Customer is
loaded, then it can be passed on to any subsystem in the
application without exposing the persistence code. Similarly, if
you get an ArrayList of Customer objects, you can pass on the
ArrayList which has no persistence code dependency. Conclusion
Using the Domain Objects Persistence pattern, we have extracted
the persistence code out of the Customer object. This has made
Customer object more object-oriented and simpler to understand
because its object model is closer to the data model in the
database. And, finally, we have enabled the Customer object to
be passed around to different parts of the application (or even
to distributed applications through .NET Remoting) without
exposing its persistence code.