0

I want to make it easy to create drop-down menus within my system that are populated from the database (for example, a list of user groups). I am generally following a domain-driven design approach for this system, including a slightly adapted version of the Repository pattern. (The system is in PHP.)

Since retrieving a drop-down list for a given domain-object class is a common operation, I am wondering if it would be appropriate to create getDropDownList() methods on the relevant repositories.

For example, let's say the domain object in question is called "Category". What I'm proposing is to create a CategoryRepository::getDropDownList() method that would return an associative array of category IDs and titles, ready to be used to create an HTML <select> list.

On a past project, when I created a getDropDownList() method on a repository-like class, one of the other developers said that such a method didn't belong in that class, saying it had more to do with the view than the model. But I don't see it that way because the purpose of the method is simply to return the raw data for the list. It doesn't even need to be used to create a dropdown list; it could be converted to JSON data or any other number of things.

My main questions are:

  1. Does a getDropDownList() method like the one I described belong in a repository class? If not, where should it go instead?
  2. Is this perhaps just a naming issue? Perhaps it would be better if I called it something like getSimpleList() or getArrayForList() to indicate that it's returning an array, and not already-rendered HTML?

To continue the category example, the data returned from this method would return an associative array of category IDs as the keys and category names as the values, e.g.:

array(
    1 => 'Category A',
    2 => 'Category B',
    ...
)
Matt Browne
  • 12,169
  • 4
  • 59
  • 75

2 Answers2

1

You should do your utmost not to query your domain. Your domain should be focused around full aggregates/entities.

Rather create a separate query layer that focuses on returning data using some agnostic naming.

For instance, in C# I would have something like this:

public interface ICategoryQuery
{
    DataTable All();
}

Something like an All method isn't something you would typically find on a CategoryRepository as the domain is concerned with manipulating data (command-side). So if we ever need to perform some action on all our categories so often as to warrant an All method we probably have a design flaw. Come to think, that may indicate that we are querying our domain :)

Eben Roux
  • 12,983
  • 2
  • 27
  • 48
  • Are you talking about CQRS rather than traditional DDD? Both Fowler and Evans describe finder/querying methods as one of the main characteristics of repositories. But let's say that I had separate Query classes as you suggest; it still leaves me with the same question - does a `getDropDownList` or `getArrayForSelectList` method belong on such a Query class? The method can't just be called `all` because I need to indicate that only the ID and title fields will be selected from the database; `all` sounds to me like it will return a list of complete objects. – Matt Browne Jun 20 '13 at 13:11
  • 1
    CQRS isn't really a form of DDD :) --- but yes it is CQRS and it can be applied as simple or complex as you like. The query side of things isn't at all concerned with domain objects. If you *really* need a read model (DTO) you can go down that route. Just a lot of mapping but it will depend on what it buys you. You can call your method whatever you like and return whatever you like. If you need a more descriptive name then so be it but I would not have any UI-specific bits in there so no 'DropDown' or 'Select'. But that is just me :) – Eben Roux Jun 20 '13 at 15:51
  • Thanks...CQRS vs DDD is a separate discussion but it sounds like you're saying that it does indeed make sense to put the method on some sort of Query object rather than somewhere else. Now I just have to decide what to call it...maybe "getIdsAndNames"? I'm not sure how else to name it without having the word "dropdown" or "select" in it. – Matt Browne Jun 20 '13 at 16:58
  • New idea: I think I might instead create a method called `getKeyValueArray(keyField, valueField)`; example usage: `getKeyValueArray('id', 'name')` – Matt Browne Jun 20 '13 at 17:06
  • That is an option. One would be extracting the data using the underlying result set anyway. See how that works for you. It does seem *very* generic though but maybe it is something worth pursuing. – Eben Roux Jun 21 '13 at 04:12
1

IMHO you should seek for a business meaning of every program element. View layer is there just for the sake of presentation of business rules/data and should be easily replaceable. Your repository on the other hand is part of the business model and should definitely follow business naming (names understandable among business people). Thus, your suggested method naming is not valid. "DropDownList", "SimpleList" and "ArrayForList" have no meaning to business heads.

I suggest the following:

  • by-the-book path (if performance is not an issue) would be method CategoryRepository::findAll()/getAll() which returns all categories in form of Category instances - this way you are dealing with strictly business elements across all layers which is very nice since you don't introduce any intermediate type. In view layer you can easily format this instances into <option/> elements
  • custom method (as you suggested) but with a name understandable by business ppl - e.g. getTitlesOfAllCategories() (@return string[] Array of category ID => title)

Another problem with getDropDownList() is that it can't be "recycled" easily because of naming issue - imagine the sudden need to list categories inside <ul><li> list - is it time to duplicate your original method with getBulletedList()?:) What about checkboxes - maybe getCheckboxList()? But, the meaning is always the same, you just want to present... ta-daaaam... all categories.

gseric
  • 649
  • 4
  • 11
  • Good point about the "business meaning"...my hesitation with your "by-the-book" path is that with PHP's per-request lifecycle, I'd be retrieving a bunch of fields unnecessarily, but it might be premature optimization to be concerned about that (plus there's always caching). `getTitlesOfAllCategories` isn't quite accurate (though perhaps it's close enough) because I'm retrieving the IDs as well - I added an example to the end of my question of what data should be returned. – Matt Browne Jun 21 '13 at 16:04
  • I'm guessing you would discourage my `getKeyValueArray()` idea mentioned in my comment above, since it has no business meaning? I suppose I could accomplish something similar with the findAll/getAll approach if I created a method in the view layer for constructing dropdown lists that accepted key and value field names, e.g.: `$categories = $categoryRepository->getAll(); createDropDownList($categories, 'id', 'name', ...[other options]...);` – Matt Browne Jun 21 '13 at 16:04
  • Regarding by-the-book approach, yes - that would be a matter of caching (eg. Doctrine 2 deals with it automatically just throw it enough RAM). Missing ID issue is my typo, I really meant to write "@return string[] Array of ID => title" – gseric Jun 21 '13 at 17:51
  • Regarding, your second comment, it would be nice view helper, "id" and "name" could internally resolve to getId() and getName() to avoid public entity properties. – gseric Jun 21 '13 at 17:55
  • Thanks, your answers have been very helpful. – Matt Browne Jun 21 '13 at 22:56
  • Lately I have discovered one other solution for this that I think works well: implement `__toString()` methods on your domain model classes and have a helper for generating the dropdowns that's smart enough to handle either an associative array or an array of domain objects. If it's the latter, then the helper uses `$obj->getId()` as the option value and `(string)$obj` as the option label. – Matt Browne Mar 27 '14 at 20:03