2

Imagine an application that allows you to add line items to a quote. The end result is like a sales person giving you a printed sheet with information such as name of the business, name of the sales person, and various service line items printed on the quote along with pricing, description, and quantity.

To implement this on the database level I have tables Item (holds 0-to-many items per Quote) and QuoteMeta (holds quote meta-information like who created the quote, quote number, revision, status, etc).

enter image description here

Then I have a domain object called Quote, which is not aware of the database layer directly, but receives data from both Item and QuoteMeta. The Quote is the "quote sheet" described previously.

My question is how to handle this domain object when I have more than one table representing it internally. For example, with my Quote domain object I can do something like ...

Single Repository, Single Domain object

//Repository populates $quote from both database tables
$quote = (new QuoteRepository())->getQuoteById($id);

$quote->getQuoteNumber(); //gets data from quote metadata table
$quote->getItemByLineNumber(5); //gets an item from items table

The above "marries" the tables into the one domain Quote object. QuoteRepository handles queries directed to and from both database tables. My question is .. can I do this?

For example, will it be better to keep concepts separate, maybe like so?

Multiple Repositories, Multiple domain objects

//repository strictly deals with quote meta data
$quoteMeta = (new QuoteMetaRepository())->getQuoteMetaById($id);
$quote->getQuoteNumber();

//repository strictly deals with item handling
$items = (new ItemRepository())->getItemsByQuoteId($id);
$items->getItemByLineNumber(5);

Here the concerns are more separated, but I have complexity of dealing with multiple objects, when physical Quote representation is typically understood to be the items + quote metadata merged together. You don't have just items without metadata, and there is little point in having metadata without service items.

That said, is preferred to keep repositories separate, or together? And if separate, how do I handle things like table JOINs that operate on both tables? Which repository do they go to?

Dealing with multiple tables

To shift gears a bit, I have some code purpose of which is to delete an Item. An item record is deleted from the table. Then references to other data that are tied to the Item are deleted from 5 other tables (things like graphs, design, etc, and other data that belongs to the item). Should this "Item Deletion" be a single handler method that deletes data from multiple tables directly in the place of one method, or should each table have their own respective Repository handler class and method, and I can call each handler to delete their portion of the item?

Also, in the example where I delete the item, I am not really touching much of QuoteMeta table or entity. Thus I may only use the ItemRepository handler by itself, which makes me lean towards separate repositories in this case.

Notes on ORM

In my codebase I am using an ORM, where I have entities, such as Item Entity corresponds to item table, and QuotaMeta corresponds to quote_meta. The focus of my question though is not on an ORM. ORM provides a convenient way to populate my entities from tables, but without ORM I'd either be populating Entities anyway (i.e. via mysqli_fetch_object), or I'd be using arrays. The question still stands -- how do I model my domain objects and corresponding repositories? Do I ... have one per table, or do I go to a higher level of abstraction, to where I operate on multiple tables per single domain/repository?

Final Thoughts..

To be super-specific, do I have a Quote domain object that operates on both tables, or do I have separate ItemGroup and QuoteMeta objects, where one manages items that belong to the Quote but doesn't care about the metadata, and one manages Quote metadata, without the dealing with items that belong to it?

1

Your first approach is basically creating an ORM. If ORM is what you are after then go with it, but remember that objects from the ORM do not and should not represent your domain - ie. the layer enforcing your business rules presented by the stakeholders. They are objects which understand persistence and as such they should not contain business rules, because that would be breaking of the Single Responsibility Principle, which is not a rule to blindly follow but which greatly increases code readability.

With that in mind, your domain layer not working with ORM objects, you would need to transform the ORM objects to your domain objects anyway, which seems kind of pointless.

I greatly favor the direct repository approach, as it very clearly states what each object does:

  • a domain object represents a domain entity,
  • a repository is used to provide an abstraction to CRUD operations on a domain object.

You have a domain object and want more data related to it, then use a service which knows how to communicate with outer world, in this case the persistence layer.


Also, completely avoiding ORM when querying the database lifts the constraint that you need to use the ORM objects, which you don't. Many times you need different representations of the same entity, for different purposes, and for that you can sometimes use even very simple DTOs. This makes it easier to construct such objects, leaves less memory footprint and generally makes the applications faster, because you can specifically craft queries to reduce the number of times you need to go from the application server to the database server and back.

  • ok. I think I mostly understand ... I do use an ORM but not in all cases... Either way, ORM or no ORM, I see that I still need to make my own domain objects. That said. .. it's clear to me when all I need is a simple CRUD, but what do I do when I have a complex (non-ORM) join that includes 5 tables? (this is common in the codebase I am refactoring). I don't want to be creating 5 CRUD objects and juggling them.... Maybe I am confusing myself. but... how do I repositor-ize and domain object-ize a select statement like select * from quote_meta join company join office join contact join etc? – Dennis Feb 28 '17 at 22:32
  • Also to double check I understand you are favoring what I called the Multiple Repositories, Multiple domain objects. – Dennis Feb 28 '17 at 22:33
  • One more thing I suppose ... When looking at a "Quote" (aka my domain object), it is not unreasonable to think of it as quote metadata + items that belong to the quote. Even though there are two underlying tables, on a high end level I can say they are all part of a Quote domain object. i.e. when printed on paper, items do not exist on their own, there's always metadata that goes along with them, (i.e. imagine a shopping receipt). Would you be still suggesting to keep them separate internally? – Dennis Feb 28 '17 at 22:44
  • @Dennis Your queries should reflect your needs. If what you need is a join over 5 tables in order to be able to get all the necessary data from the database, then do a join over 5 tables, rather than lazy loading it and using 5 different repositories. You may then create a complete aggregate from the 5-table-join result. – Andy Feb 28 '17 at 22:49
  • @Dennis If you want to lazy load the items, then lazy load them, but you can just as well load them directly when constructing the Quote aggregate and have the items already pre-stored in memory for processing. But remember, then you are pulling the items out of the database even in cases when you do not need them at all. If you feel like you will always need the items, you will always work with them, I'd say go ahead and load them at the same time as the Quote aggregate. – Andy Feb 28 '17 at 22:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.