5

We're designing a major refactor in my company system's codebase. One of the refactor points, is the (actual) definition of the business logic/domain model. Right now, every repository in the codebase will just fetch every possible piece of data related to the entity, and to "optimize" it, everything is being fetched in arrays rather than objects (technically, key-value hashmaps, but in PHP they're called arrays).

Now, I'd like to introduce the actual use of model objects that encapsulate business logic (calling methods like businessObject.canTransitionToNextProcessPhase() rather than doing a bunch of if/else in a controller, resulting in 0 readability.

For the first part of this design, I'm identifying what relational data is a must-load for every entity. It can be easily identified when an entity is dependant. For example: an Entry should always load the Category and the User (author). However, when you fetch an User, you don't necessarily need to load all their entries, friends, Etc.

Now, in some particular cases, when loading a User, you might want to load all their Entries too. How to design a repository pattern that let's you load said relational data in those particular cases?

For instance take the method UserRepository@getById(int):

Should the method receive option parameters that let decide whether to load additional relationships other than the default ones? (i.e: ContactInfo is default, the list of Entries is additional).

Should it instead be configued in a chainable method like repo.alsoLoad('entries').getById(id)? (however this feels as replicating the ORM's API, I think).

Please note the objective of this repository pattern design is not just a layer that covers the ORM unnecessarily. Methods will retrieve data based on business use cases: getAllInvoicesFromCurrentCycle() rather than getAll(hashamp /* with 5 key/values to be passed directly to the ORM's where clause*/) since the latter provides no benefit (nor readability) whatsoever (might as well just use the ORM directly).

I will update the question if more clarification is needed.

  • semi-related: youtube.com/watch?v=7xSfLPD6tiQ (Rob Norris' "Pure functional database programming with fixpoint types") - in a langauge like scala you could define your interface to return a User[A](requiredData: Foo, category: A) and then get User[List[Category]] or User[List[CategoryId]] or whatever else makes sense. – Daenyth Feb 27 '17 at 15:51
2

I'd suggest having a separate UserRepository class and UserFriendRepository class. The UserFriendRepository can take a User or UserId in its constructor.

(Unless Users and Friends are always treated together and saved together - then you'd want just one repository class - but I don't think this is your case.)

The intent of the Repository pattern is to bring a layer of simplicity, clarity (easier for devs to work with User and Friend classes than low-level data access classes), and unit-testability. It's an abstraction layer where, say, "caching" could be added (or changed or removed) without client code ever knowing.

Addendum based on comment:

I've personally had more luck keeping the Repository layer simple (otherwise I find myself re-implementing the ORM / getting tied in knots). I'd rather call UserFriendRepo.Get() when needed and UserOrderRepo.Get() when needed; rather than a more complex User repository that can be passed hints to load [friends] and [orders].

Yet a "smarter" repository might be warranted if eager-loading and/or lazy-loading help the performance of your particular application, or if it's more intuitive for the devs at your shop, etc. Patterns are just general approaches that you're free to fine-tune.

  • Yes, I have those 2 repositories. But should I load every relationship on their own repository? I'd need to have as a dependency every repository. I'm looking for a design more like.. for example: userRepository.getUser(id, ['friends', 'orders']). Get the idea? it doesn't depends on ORM features like lazy loading, and I'm barely telling it to load those entities. But I'm not sure if this is a good approach – Christopher Francisco Feb 24 '17 at 21:54
  • I've edited my answer to try to weigh-in on your follow-up question. Hope it helps a bit. – Dave Clausen Feb 27 '17 at 15:48

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.