The 1.2.0 release adds the ability to define WHERE conditions on relationships. (The 1.1.0 release added functionality to ignore foreign key string case when wiring up objects in memory, and 1.0.0 was released pretty quietly a couple of weeks ago.)
Try it out today, because you like keeping your persistence layer separate from your domain layer.
Now, read on for some history, if you care about that kind of thing.
Many years ago, we on the Solar project developed Solar_Sql_Model, an Active Record type of ORM. Overall I liked it well enough, though (as with anything) it had its strengths and weaknesses.
Since then, after extracting the Solar components to Aura libraries, I’ve mostly lived without ORMs. The majority of my legacy consulting work has not made use of them; where a legacy project did have an ORM of some sort, it was a custom in-house piece of work.
However, about three years ago, I hired on with a startup to build out their backend from scratch. At the time, I wanted to do “real” Domain-Driven Design, with entities and aggregates and value objects and everything else. That meant keeping the domain system separate from the persistence system, and that in turn meant Active Record was not an option. Doctrine, a domain model data mapper, was the next logical choice, but on review it was not to my liking. (The annotations, among other things about Doctrine, just rubbed me the wrong way.)
So instead of an Active Record implementation, or Doctrine, I figured that it would be enough to use a Table Data Gateway on top of Aura.Sql and Aura.SqlQuery to retrieve rows, then map the rows over to domain objects. This was fine, for as far as it went, but the problem was “relationships.” Oh dear Codd, the relationships.
Selecting one or many objects from a single table was no big deal, but doing multiple selections from multiple tables and building up the selection statements for those relationships was a tedious, tiresome, and time-consuming burden. (Wiring them up in memory once they’d been selected was not too bad, given Aura.Marshal, but it was still more effort than I’d’ve rather been expending.)
So it all “worked,” but it was just not satisfying at all. The DDD portions, for their part, worked out great, as did the separation of the domain layer from the persistence layer, so I was pretty sure I was on something like the right track.
Then I read this article from Mehdi Khalili. It’s fantastic and you should read the whole thing. In summary, Khalili points out that it is perfectly reasonable to use a persistence model in addition to a domain model. That is, you do not necessarily have to map directly from the database to domain objects; you can map from the database to persistence objects for the data, and then later compose or map the persistence model into the domain model for behaviors.
This was a revelation to me, something that I’d never considered, or even heard of before. It alleviates wide swaths of DDD-related burdens.
As a result of my relationship-related burdens at the startup, and after reading the Khalili article, I put together Atlas, a mapper for your persistence model. Like everything else I’ve been doing for the past several years, Atlas is built in discrete layers:
- PDO at the very bottom;
- Aura.Sql around that, to provide convenience methods for binding and fetching;
- Aura.SqlQuery in parallel, to support query building;
- all of those composed into a table data gateway system to emit Row objects from tables;
- and finally a mapper system on top of that to emit Record objects composed of table Rows and their relationships
As such, each Record object is composed of a Row (from the “main” table) and its Related objects (themselves Records, each of which is composed of a Row and Relateds, and so on).
Atlas uses the term “Record” to indicate that the object is not a domain entity or aggregate. You can use Records directly for straightforward CRUD/BREAD operations, or you can map them over to your domain objects.
Fetching deep relationship structures is no big deal; see this article from Andrew Shell using 25 tables in different complex relationships. (Andrew’s project also shows how to keep the persistence and domain layers separate, and incorporates a debug bar implementation for Atlas.)
So, if you want a data mapper implementation that models your persistence layer, and the relationships therein, Atlas is just the thing for you. Try it out today!
Read the Reddit discussion about this post here.