Suppose your application manages Person
objects, with each instance having name
, age
and jobTitle
properties.
You would like to persist such objects, retrieve them from the persistence medium and maybe update (say, on their birthday, increment the age) or delete. These tasks are usually referred to as CRUD, from Create, Read, Update and Delete.
It is preferable to decouple your "business" logic from the logic that deals with the persistence of Person
objects. This allows you to change the persistence logic (e.g. going from a DB to a distributed file system) without affecting your business logic.
You do this by encapsulating all persistence logic behind a Repository
. A hypothetical PersonRepository
(or Repository<Person>
) would allow you to write code like this:
Person johnDoe = personRepository.get(p=> p.name == "John Doe");
johnDoe.jobTitle = "IT Specialist";
personRepository.update(johnDoe);
This is just business logic and doesn't care about how and where the object is stored.
On the other side of the Repository
, you use both a DataMapper
and something that translates queries from the functional description (p=> p.name == "John Doe"
) to something that the persistence layer understands.
Your persistence layer can be a DB, in which case the DataMapper
converts a Person
object to and from a row in a PersonsTable
. The query translator then converts the functional query into SELECT * FROM PersonsTable WHERE name == "John Doe"
.
Another persistence layer can be a file system, or another DB format that chooses to store Person
objects in two tables, PersonAge
and PersonJobTitle
.
In the latter case, the DataMapper
is tasked with converting the johnDoe
object into 2 rows: one for the PersonAge
table and one for the PersonJobTitle
table. The query logic then needs to convert the functional query into a join
on the two tables. Finally, the DataMapper
needs to know how to construct a Person
object from the query's result.
In large, complex systems, you want to use small components that do small, clearly defined things, that can be developed and tested independently:
- The business logic deals with a
Repository
when it wants to read or persist objects, and doesn't care how that is implemented.
- The
Repository
deals with a DataMapper
when it wants to read/write an object in a particular persistence medium.
- For querying, the
Repository
relies on a schema provided by the DataMapper
(e.g. the jobTitle
value is found in the JobTitle
column in the PersonTable
table) but not on any implementation of a mapper.
- For DB persistence, the
DataMapper
relies on a DB layer, that shield it from the Oracle/Sybase/MSSQL/OtherProvider details.
The patterns don't "differ", they just expose different basic features.