|
|
| |
| |
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. |
| |
|
Author:
Iqbal M. Khan works
for Alachisoft, a leading software company
providing O/R Mapping and Clustered
Object Caching solutions for .NET. You
can reach him at iqbal@alachisoft.com
or visit Alachisoft at
www.alachisoft.com. |
|
|
a |