Dennis Traub has already pointed out what you can do to improve query performance. That approach is much more efficient for querying, but also even more bulky, because you now need additional code to keep your view model in sync with the aggregates.
If you don't like that approach or cannot use it for other reasons, I don't think that the first approach that you are suggesting is more ineffective or bulky than using direct object references. Suppose for a moment that you were using direct object references in the aggregates. How would you persist those aggregates to durable storage? The following options come to mind, when you are using a database:
- If you are using a denormalized table for
Company
(e.g., with an document database such as MongoDB), you are effectively optimizing for a view query already. However, you'll need all the extra work to keep yourCompany
table in sync withCity
,Province
. Efficient, but bulky, and you might consider persisting the real view models instead (one per use-case). - If you are using normalized tables with a relational database, you would use foreign keys in the
Company
table to reference the respectiveCity
,Province
etc. by their id. When querying for aCompany
, in order to retrieve the fields ofCity
,Province
etc that are needed to populate your view model, you can either use a JOIN over 4+ tables, or use 4 independent queries to theCity
,Province
, ... tables (e.g., when using lazy loading for the foreign key references). - If you are using normalized tables in a non-relational database, usually people use application side joins exactly as in the code you suggested. For some databases, ORM tools such as Morphia or Datanucleus can save you some programming work, but under the hood, the independent queries remain.
Therefore, in the 2nd and 3rd option, you save a bit of trivial programming work if you let an ORM solution generate the database mapping for you, but you don't get much improved efficiency. (JOIN
s can be optimized by proper indices, but getting this done right is non-trivial).
However, I'd like to point out that you remain full control over the view model object construction and database queries when you are referencing by Id and using a programmatic application side joins as in the code that you suggested.
In particular, names of cities, provinces etc are usually changing very seldomly and there are only few of them and they easily fit into the memory. Hence you can make extensive use of in-memory caching for the database queries -- or even use in-memory-repositories that are populated from flat-files on application startup. When done right, to construct your view model for Company
, only one database call to the Company
table is required, and the other fields are retrieved from the in-memory cache/repository, which I would consider extremely efficient.