Thanks for the replies. From what I'm hearing, this
seems to be a topic that hasn't been fully explored yet, so I'll offer my
perspective on it.
There are at least three different ways of using
Maps:
1. A concrete map implementation can be used internally to
enforce a uniqueness constraint and provide optimized lookup of
objects.
2. A public method can return a map interface, usually by
returning a member variable that holds the internal state, as in case 1.
There are two different variations on this:
a. Returning an immutable map that is only used
to inspect the state of the map.
b. Returning a mutable map that can be used to
add and remove map entries.
3. To the extent supported by the modeling language, maps
can be used in a conceptual domain model.
I think the first use of maps, as an private implementation
detail, is completely benign. The second and third uses are problematic,
for a variety of reasons.
Developers approaching the conceptual model from an
object-oriented programming perspective are often tempted to use a map as part
of the conceptual model. UML allows this in the form of qualified
associations:
This says that the association of Portfolio to
Position is "keyed" by some value called ProductID, which may or may
not be the same as the ProductID attribute that we see in Position. (See
below...) It also implies that, within a given Portfolio, there is at
most one Position for any given product, though I'm not sure how to
differentiate scenarios where a product resolves to a single Position vs. a
collection of Positions.
From a conceptual modeling perspective, I think this
construct has problems:
- Qualified associations are conceptually ambiguous. They
may suggest certain cardinality or uniqueness constraints, but those should be
made explicit as part of the conceptual model. Leaving that information in a map
suggests that each map is its own little entity, an arbitrary association of
some (typically primitive) key with some value, when in fact it is obscuring a
deeper meaning.
- Information can be "hidden"
inside keys without being properly attributed to a first-class entity in
the model. In the above example, it wouldn't
be uncommon to see ProductID omitted from the Position entity, because it's
provided in the map. This is bad practice. Maps in pubic interfaces
are, at best, a programmatic convenience rooted in the "truth" of the model. We
shouldn't be using maps to define truths that are not otherwise
evident.
So I
would argue that, instead of a qualified association, we should use an explicit
uniqueness constraint.
(Note: the constraint could be expressed in OCL,
or maybe through some more compact and visual notation. I'm using plain
english here mainly because it's what I know.
)
This
would also accommodate a more nuanced use case where Product ID resides on a
separate Product entity, referenced by Position. It would also accommodate
multiple uniqueness constraints over the same association. For example, if
positions were ranked by some criteria (say "watch list priority"), it might
also be the case that no two positions on a single portfolio can have the same
rank.
Separately, there is the issue of whether Maps should
be exposed through public APIs. I would argue that they shouldn't be, for
related reasons:
- Map-based APIs are not
self-describing. While Map<Integer, Allocation> may conceptually relate an
AllocationID to the Allocation having that ID, that relationship is not evident
in the type, nor in the method signatures (put, containsKey, etc.) that operate on it. The
Integer just looks like an arbitrary primitive value. We can use names of
contextual objects to help clarify: For example, the getter that retrieves the
map could be called getAllocationIdToAllocationMap(), and/or we could create a
new class called AllocationId that wraps around the Integer value. But these may
not be optimal choices. The more complex and nested the map structure, the more
this problem of self-description manifests.
- Mutable maps are not fully
encapsulated, and can be corrupted by client programs. In the above example, the
underlying concptual model is that the context object exposing the map has a
collection of Allocations, and a client program can retrieve an Allocation by
its AllocationId. Accordingly, an internal constraint must hold that the
AllocationId used as the key in any given map entry must be that of the
Allocation that is the value for that entry. But the map doesn't enforce this
business logic. It only enforces the uniqueness of the key.
- Maps tend to be used as
special cases where there is only one lookup function (though you can expose
multiple maps), where the lookup function only has one parameter, and where the
parameter is unique over the value set (though the map value can be a
Collection). While this meets the needs of simple use cases, lookups are
potentially useful for a much broaderset of cases. If maps don't meet all of
those cases, then exposing them through a public API tends to produce APIs that
are unnaturally divergent.
In cases where a map is used as the internal
representation, I'd favor a solution that exposes the map by a combination of
two or maybe three methods: (1) a getter that returns an immutable list of
values; (2) a lookup method that returns the value(s) for a given key; and, if
appropriate, (3) an add method that adds a new value, and populates the
internal map according to the appropriate business rules. In most cases,
this add method won't need the keys, since the keys are derived directory or
indirectly from the value being
added.
So, do people generally agree that these guidelines help to
keep the conceptual model and programming APIs closer to the business
domain?
Thanks,
- Ted
If you're using generics then I'm not sure I agree with this. e.g.
public Map<Customer, List<Order>> getAllOrdersByCustomer()
{..}
seems relatively clear to me as returning "a mapping of each customer to a
list of orders", which makes sense even at a non-technical level. The
alternative of, for example, creating a CustomerToListOfOrdersMapping class
to do the same thing seems to introduce extra clutter for not much gain
...
IMHO the objective is not necessarily producing code that a non-technical
business user can just sit down and read, even though you are trying to
establish and maintain a common ubiquitous language.
Alasdair
> Hi,
>
> I'm new to this group, and don't mean to start a
flame war with this
> somewhat provocative subject line.
>
>
But I've been working on a complex domain model that will be available
>
as part of a runtime framework. Some of the developers working on this
>
have prototyped public interfaces that return maps, or dictionaries.
>
I've instinctively avoided using maps in public APIs, and have recently
>
had to examine my reasons for this more closely, for discussion within
>
the team.
>
> In thinking through this, I'm increasingly of the
opinion that maps
> shouldn't be regarded as first-class modeling
abstractions, and in most
> cases shouldn't be exposed through public
APIs. Before I get to the
> reasons for this, I'm looking for some
pointers from the DDD community
> to past discussions, blog entries,
references, etc. I did a cursory
> search of this mail group and a a few
google searches, but didn't come
> up with much.
>
> Is this
something that has been discussed or addressed in DDD, modeling
> or API
design literature? Any background information would be much
>
appreciated.
>
>Thanks and Best Regards,
>
>- Ted
Epstein