Planet PHP
Guest
I'm happy to share with you an excerpt of my latest book, which is currently part of Manning's Early Access Program. Take 37% off Object Design Style Guide by entering fccnoback into the discount code box at checkout at manning.com.
Chapter 7: Dividing responsibilities
We've looked at how objects can be used to retrieve information, or perform tasks. The methods for retrieving information are called query methods, the ones that perform tasks are command methods. Service objects may combine both of these responsibilities. For instance, a repository (like the one in Listing 1) could perform the task of saving an entity to the database, and at the same time it would also be capable of retrieving an entity from the database.
Listing 1. The PurchaseOrderRepository can save and retrieve a PurchaseOrder entity.
interface PurchaseOrderRepository
{
/**
* @throws CouldNotSavePurchaseOrder
*/
public function save(PurchaseOrder purchaseOrder): void;
/**
* @throws CouldNotFindPurchaseOrder
*/
public function getById(int purchaseOrderId): PurchaseOrder;
}
Since saving and retrieving an entity are more or less each other's inverse operations, it's only natural to let one object have both responsibilities. However, in most other cases you will find that performing tasks and retrieving information are better off being divided amongst different objects.
Separate write models from read models
As we saw earlier, there are services, and other objects. Some of these other objects can be characterized as Entities, which model a particular domain concept. In doing so, they contain some relevant data, and offer ways to manipulate that data in valid and meaningful ways. Entities can also expose data, allowing clients to retrieve information from them, whether that is exposed internal data (like the date on which an order was placed), or calculated data (like the total amount of the order).
In practice, it turns out that different clients use entities in different ways. Some clients will want to manipulate an entity's data using its command methods, while others just want to retrieve a piece of information from it using its query methods. Nevertheless, all these clients will share the same object, and potentially have access to all the methods, even when they don't need them, or shouldn't even have access to them.
You should never pass an entity that can be modified to a client that isn't allowed to modify it. Even if the client doesn't modify it today, one day it might, and then it will be hard to find out what happened. That's why the first thing you should do to improve the design of an entity, is separate the Write model from the Read model.
We'll find out how to accomplish this by looking at an example of a PurchaseOrder entity (Listing 2). A purchase order represents the fact that a company buys a product from one of its suppliers. Once the product has been received, it's shelved in the company's warehouse. From that moment on the company has this product in stock. We'll use the same example for the remaining part of this chapter and work out different ways to improve it.
Listing 2. The PurchaseOrder entity.
final class PurchaseOrder
{
private int purchaseOrderId;
private int productId;
private int orderedQuantity;
private bool wasReceived;
private function __construct()
{
}
public static function place(
int purchaseOrderId,
int productId,
int orderedQuantity
): PurchaseOrder {
/*
* For brevity, we use primitive type values, while in
* practice, the use of value objects is recommended.
*/
purchaseOrder = new self();
purchaseOrder.productId = productId;
purchaseOrder.orderedQuantity = orderedQuantity;
purchaseOrder.wasReceived = false;
return purchaseOrder;
}
public function markAsReceived(): void
{
this.wasReceived = true;
}
public function purchaseOrderId(): int
{
return this.purchaseOrderId;
}
public function productId(): int
{
return this.productId;
}
public function orderedQuantity(): int
{
return this.orderedQuantity;
}
public function wasReceived(): bool
{
return this.wasReceived;
}
}
In the current implementation,
Truncated by Planet PHP, read more at the original (another 11989 bytes)
Читать дальше...
Chapter 7: Dividing responsibilities
We've looked at how objects can be used to retrieve information, or perform tasks. The methods for retrieving information are called query methods, the ones that perform tasks are command methods. Service objects may combine both of these responsibilities. For instance, a repository (like the one in Listing 1) could perform the task of saving an entity to the database, and at the same time it would also be capable of retrieving an entity from the database.
Listing 1. The PurchaseOrderRepository can save and retrieve a PurchaseOrder entity.
interface PurchaseOrderRepository
{
/**
* @throws CouldNotSavePurchaseOrder
*/
public function save(PurchaseOrder purchaseOrder): void;
/**
* @throws CouldNotFindPurchaseOrder
*/
public function getById(int purchaseOrderId): PurchaseOrder;
}
Since saving and retrieving an entity are more or less each other's inverse operations, it's only natural to let one object have both responsibilities. However, in most other cases you will find that performing tasks and retrieving information are better off being divided amongst different objects.
Separate write models from read models
As we saw earlier, there are services, and other objects. Some of these other objects can be characterized as Entities, which model a particular domain concept. In doing so, they contain some relevant data, and offer ways to manipulate that data in valid and meaningful ways. Entities can also expose data, allowing clients to retrieve information from them, whether that is exposed internal data (like the date on which an order was placed), or calculated data (like the total amount of the order).
In practice, it turns out that different clients use entities in different ways. Some clients will want to manipulate an entity's data using its command methods, while others just want to retrieve a piece of information from it using its query methods. Nevertheless, all these clients will share the same object, and potentially have access to all the methods, even when they don't need them, or shouldn't even have access to them.
You should never pass an entity that can be modified to a client that isn't allowed to modify it. Even if the client doesn't modify it today, one day it might, and then it will be hard to find out what happened. That's why the first thing you should do to improve the design of an entity, is separate the Write model from the Read model.
We'll find out how to accomplish this by looking at an example of a PurchaseOrder entity (Listing 2). A purchase order represents the fact that a company buys a product from one of its suppliers. Once the product has been received, it's shelved in the company's warehouse. From that moment on the company has this product in stock. We'll use the same example for the remaining part of this chapter and work out different ways to improve it.
Listing 2. The PurchaseOrder entity.
final class PurchaseOrder
{
private int purchaseOrderId;
private int productId;
private int orderedQuantity;
private bool wasReceived;
private function __construct()
{
}
public static function place(
int purchaseOrderId,
int productId,
int orderedQuantity
): PurchaseOrder {
/*
* For brevity, we use primitive type values, while in
* practice, the use of value objects is recommended.
*/
purchaseOrder = new self();
purchaseOrder.productId = productId;
purchaseOrder.orderedQuantity = orderedQuantity;
purchaseOrder.wasReceived = false;
return purchaseOrder;
}
public function markAsReceived(): void
{
this.wasReceived = true;
}
public function purchaseOrderId(): int
{
return this.purchaseOrderId;
}
public function productId(): int
{
return this.productId;
}
public function orderedQuantity(): int
{
return this.orderedQuantity;
}
public function wasReceived(): bool
{
return this.wasReceived;
}
}
In the current implementation,
Truncated by Planet PHP, read more at the original (another 11989 bytes)
Читать дальше...