
Active Record vs. Data Mapper for saving data
These 2 design patterns are described in Martin Fowler’s book “Enterprise Application Templates” and represent ways of working with saving data in object-oriented programming.
In this simplified example, the database descriptor is entered in the Foo constructor (Using dependency injection allows you to test an object without using a real database), and Foo uses it to save its data. Do_something is just a stub method that replaces business logic.
In this case, the Foo class is much simpler and should only worry about its business logic. Not only does he not have to save his own data, he does not even know and does not care about whether all his data has been saved.
When using the Data Mapper design pattern, the calling code must select the Mapper and the business object and link them together. If this is the call code in the controller, then ultimately your model leaks into the controller, which can cause big problems with support and unit testing. This problem can be solved by introducing a service object. The service is the gateway between the controller and the model and connects the domain object with the Mapper as needed.
Remember that M in MVC is a model abstraction layer, not a model object. So there can be several types of objects in one model (in the example above, you can have a service object, a domain object, and a Mapper object acting as a single model). On the other hand, if you use Active Record models, your model can only be represented by one object.
Active Record objects have historically been very popular because they are simpler, easier to understand, and faster to write, which is why many frameworks and ORMs use Active Record by default.
If you are sure that you will never need to change the data storage layer (if you are dealing with an object that is an INI file, for example), or you are dealing with very simple objects that do not have much business logic, or just prefer to keep everything in a small number of classes, then the Active Record template is what you need.
The use of the Data Mapper, although it leads to a cleaner, easier to test and maintain code, and provides greater flexibility, - the price for this - increased complexity. If you have not tried to use it, then give it a chance - you should like it.
This is a translation of an article by Russell Walker .
Example Active Record Template
class Foo
{
protected $db;
public $id;
public $bar;
public function __construct(PDO $db)
{
$this->db = $db;
}
public function do_something()
{
$this->bar .= uniqid();
}
public function save()
{
if ($this->id) {
$sql = "UPDATE foo SET bar = :bar WHERE id = :id";
$statement = $this->db->prepare($sql);
$statement->bindParam("bar", $this->bar);
$statement->bindParam("id", $this->id);
$statement->execute();
}
else {
$sql = "INSERT INTO foo (bar) VALUES (:bar)";
$statement = $this->db->prepare($sql);
$statement->bindParam("bar", $this->bar);
$statement->execute();
$this->id = $this->db->lastInsertId();
}
}
}
//Insert
$foo = new Foo($db);
$foo->bar = 'baz';
$foo->save();
In this simplified example, the database descriptor is entered in the Foo constructor (Using dependency injection allows you to test an object without using a real database), and Foo uses it to save its data. Do_something is just a stub method that replaces business logic.
Benefits of Active Record
- Writing code with Active Record is quick and easy, when the properties of the object are directly related to the columns in the database.
- Saving takes place in one place, making it easy to learn how it works.
Disadvantages of Active Record
- Active Record models violate SOLID principles . In particular, the principle of single responsibility ( SRP - “S” in SOLID principles ). According to the principle, a domain object should have only one zone of responsibility, that is, only its own business logic. By calling it to save data, you add an additional zone of responsibility to it, increasing the complexity of the object, which complicates its support and testing.
- The implementation of data storage is closely related to business logic, which means that if you later want to use another abstraction to save data (for example, to store data in an XML file, and not in a database), you will have to do code refactoring.
Data Mapper Example
class Foo
{
public $id;
public $bar;
public function do_something()
{
$this->bar .= uniqid();
}
}
class FooMapper
{
protected $db;
public function __construct(PDO $db)
{
$this->db = $db;
}
public function saveFoo(Foo &$foo)
{
if ($foo->id) {
$sql = "UPDATE foo SET bar = :bar WHERE id = :id";
$statement = $this->db->prepare($sql);
$statement->bindParam("bar", $foo->bar);
$statement->bindParam("id", $foo->id);
$statement->execute();
}
else {
$sql = "INSERT INTO foo (bar) VALUES (:bar)";
$statement = $this->db->prepare($sql);
$statement->bindParam("bar", $foo->bar);
$statement->execute();
$foo->id = $this->db->lastInsertId();
}
}
}
//Insert
$foo = new Foo();
$foo->bar = 'baz';
$mapper = new FooMapper($db);
$mapper->saveFoo($foo);
In this case, the Foo class is much simpler and should only worry about its business logic. Not only does he not have to save his own data, he does not even know and does not care about whether all his data has been saved.
Benefits of Data Mapper
- Each object has its own area of responsibility, thereby following the principles of SOLID and keeping each object simple and to the point.
- Business logic and data storage are loosely coupled, and if you want to save data to an XML file or some other format, you can simply write a new Mapper without touching a domain object.
Disadvantages of Data Mapper
- You will have to think a lot more before writing code.
- As a result, you have more objects to manage, which complicates the code and its debugging a bit.
Service Objects
When using the Data Mapper design pattern, the calling code must select the Mapper and the business object and link them together. If this is the call code in the controller, then ultimately your model leaks into the controller, which can cause big problems with support and unit testing. This problem can be solved by introducing a service object. The service is the gateway between the controller and the model and connects the domain object with the Mapper as needed.
Remember that M in MVC is a model abstraction layer, not a model object. So there can be several types of objects in one model (in the example above, you can have a service object, a domain object, and a Mapper object acting as a single model). On the other hand, if you use Active Record models, your model can only be represented by one object.
Use cases
Active Record objects have historically been very popular because they are simpler, easier to understand, and faster to write, which is why many frameworks and ORMs use Active Record by default.
If you are sure that you will never need to change the data storage layer (if you are dealing with an object that is an INI file, for example), or you are dealing with very simple objects that do not have much business logic, or just prefer to keep everything in a small number of classes, then the Active Record template is what you need.
The use of the Data Mapper, although it leads to a cleaner, easier to test and maintain code, and provides greater flexibility, - the price for this - increased complexity. If you have not tried to use it, then give it a chance - you should like it.
This is a translation of an article by Russell Walker .