I'm designing entities where one is related to another, but keeping application/database separate and also performance in mind. I've read on many architectual concepts (SOLID, separation of concerns, etc.).
ORM frameworks solve this internally and lazy load the related data when it is accessed. But is there a practical way without tightly coupling my objects to the ORM and keeping database logic out of them?
Separating the data for a relational database is simple. For example:
Orders-Table, Customers-Table, Addresses-Table, Countries-Table
Order: id, date, customerId, ...
Customer: id, company, email, defaultAddressId, ...
Address: id, street, countryId, ...
Country: id, name, code, ...
I want to keep the database-related functions separate, so I would create separate repositories, which fetch the data from the database.
For example:
$orderRepository->getById(123);
$customerRepository->getById(234);
$addressRepository->getById(345);
$countryRepository->getById(456);
Sometimes I only need the order data. Sometimes I would need the related Customer and sometimes I need to know in which country the customer lives of the current order.
If I'm only reading a single order, that would all no problem. As I could fetch the needed data in separate variables:
$customer = $customerRepository->getById($order->getCustomerId());
$defaultAddress = $addressRepository->getById($customer->getDefaultAddressId());
$country = $countryRepository->getById($defaultAddress->getCountryId());
But if I want to list many orders on one page (or any other use case with many related objects in one view) and display for each the country name, from which it comes, this would be complicated.
Ideally I would write in the view:
foreach ($orders as $order) {
...
$order->getCustomer()->getDefaultAddress()->getCountry()->getCode();
...
}
From my current knowledge there are three possible solutions:
Lazy loading
The call of $order->getCustomer()
will call (maybe a singleton of) the customer repository to fetch the customer object. Then will be the address repository called to fetch the address, then the country repository.
Disadvantage: many single database calls and each object must know anything about the needed repositories
Fetching all related data, when the orders are fetched
So maybe the repositories call the other repositories to fetch all the data, which their objects need:
OrderRepository:
function getCurrentOrders() {
...code to fetch order data from database...
$relatedCustomers = $this->customerRepository->getByMultipleIds($relatedCustomerIds);
...assigning fetched customer objects to order objects
}
The call to customer repository will lead to call to address repository, which will lead to call to country repository. This would reduce the database calls at first.
Disadvantage: Data is loaded which is most of the time not needed. It is fine for the list view, but when I only need the direct order infos or only a single order, there are still 3 other calls to the database (in larger object trees maybe many more).
Tailor-made objects for each required view
Either a customized database query which builds a new object with all needed data or some wrapper objects which keep the related objects inside.
Disadvantage: Could be really complicated when also business logic is needed, as I have to implement the same logic at several points.
How do you keep business logic separate from database code and design entities?