Strategy is a pattern that is often useful in encapsulating variations in logic. In working with a domain model there are often scenarios where it would be advantageous to be able to apply a strategy. There are a number of ways in which this may be achieved involving varying design tradeoffs.

In making our selection there are a number of considerations that may be applicable:

  • Dependency injection into domain objects is generally undesirable. They will often be managed by an ORM which would require us to integrate into the ORM in order to allow the DI framework to operate and this may be complicated. Additionally most things we would wish to inject are services which are dependent upon domain objects which represents an unwanted circular reference.
  • The domain object should ideally be ignorant of the strategies available and the selection criteria used. This simplifies the domain object and isolates it from concerns not directly related to its purpose.
  • Modifying the strategies should be possible with minimal modification to other code. Ideally strategies should be able to be dynamically loaded at runtime which implies the core system has no prior knowledge of their existence.

Push the logic into the service layer

This removes the selection concern from the domain. It also makes it easier to cleanly apply dependency injection and to dynamically load strategies. However it also leads to an anaemic domain model and possible duplication in the service layer if multiple services must use the strategy.

Use selection logic in the domain

This option is simple and easily implemented for small numbers of strategies and avoids using DI in the domain object. However it makes the domain object directly aware of the available strategies and introduces conditional logic into it for the purpose of determining a strategy. Additionally strategies with dependencies may need to use a service locator which couples them to the infrastructure.

Hook into the ORM and perform Dependency Injection on the domain object

This allows the strategy to automatically be available on the domain object without introducing significant logic into the domain. The trade-off is that potentially complex infrastructure is required to make this happen, and there may be corner cases that exist as potential points of failure.

Make the strategies into domain objects

This approach encapsulates the strategy logic into domain objects that derive from a common base class. With ORMs with support for inheritance this allows the domain object that uses a strategy to have a property of this common base class. A strategy is assigned to the domain object by assigning an instance of the appropriate strategy type to this property. This may be a shared instance or a separate instance may be created for each domain object with the ability to carry custom data on each instance. The class diagram below illustrates this approach.

Class Model

This approach switches the responsibility for selecting a strategy to the creator of the domain object. It is an extension of the common practice of having properties that determine types or states, where the type of the strategy becomes significant in determining behaviour. It differs in that this behaviour is encapsulated in the strategy and the domain object will rarely consider the strategy type, instead using the common interface shared by the strategies in order to invoke the behaviour.

The primary disadvantage of this approach is that a domain object is needed for each strategy. Although modern ORMs such as NHibernate may load domain objects from multiple assemblies this will generally still require some changes to the database, either in terms of schema or data in support tables. Additionally as domain objects it would be undesirable to inject dependencies which may have implications for some uses.

Selecting an approach

Of the approaches above I am most likely to make strategies into domain objects. This approach is flexible and easy to implement using existing infrastructure. Where there is a significant need for dependencies with the strategies I would then fall back to putting the logic in the service layer. In these cases the dependencies are most likely to be services and therefore the strategies should be proximate to them. Selection logic in the domain is undesirable due to the introduction of additional concerns to domain objects, and hooking into the ORM is complex and may also violate layering.