Print

Dependency-injection Code Smell: Injecting runtime data into components

Injecting runtime data into your application components is a code smell. Runtime data should flow through the method calls of already constructed object graphs.

A recurring theme when it comes to questions about dependency injection is how to wire up and resolve components a.k.a. injectables (the classes that contain the application's behavior) that require runtime data during construction. My answer to this is always the same:

Don't inject runtime data into application components during construction; it causes ambiguity, complicates the composition root with an extra responsibility and makes it extraordinarily hard to verify the correctness of your DI configuration. Instead, let runtime data flow through the method calls of constructed object graphs.

Here's an example of a MoveCustomerCommand component that gets constructed with runtime data; the CustomerId and DestinationAddress.

interface ICommand { void Execute(); }

class MoveCustomerCommand : ICommand
{
private readonly ICustomerRepository repository;

public MoveCustomerCommand(ICustomerRepository repository) {
this.repository = repository;
}

public int CustomerId;
public Address DestinationAddress;

public void Execute() {
// use repository, Id and Address to handle the operation
}
}

In the code snippet, the construction of the component requires both the ICustomerRepository dependency in its constructor and the runtime data values for the customer ID and address through its public fields. The runtime values are specific to one particular request.

This implementation is problematic because you need request-specific information to correctly initialize this component. So to be able to create a new MoveCustomerCommand the consuming code must either new-up the component itself, delegate its creation to a factory, or call back into the container passing the runtime data - all of which cause problems of their own:

  • Creating the component in code is a Dependency Inversion Principle violation and makes it impossible to decorate, intercept or replace the component without making sweeping changes throughout the code base.
  • A factory will add a pointless extra layer of abstraction to the application, increasing complexity and decreasing maintainability. Complexity is increased because the consumer now has to deal with an extra abstraction (the factory). Maintainability is decreased, because for each component a factory method must be created and maintained that will hand-wire the component with its dependencies.
  • Calling back into the container directly leads to the Service Locator anti-pattern.

Both the factory and service locator approach cause the creation of this part of the object graph to be delayed until runtime. Although delaying the creation of the object graph until runtime isn't a bad thing per se, it makes it harder to verify your configuration because resolving the root object will only test some of the object graph.

The solution to these issues is actually quite simple: remove the injection of runtime data out of the construction phase of the component and pass it on using method calls after construction. Not surprisingly, the following design solves these problems:

interface ICommandHandler<TCommand> { 
void Handle(TCommand command);
}

class MoveCustomerCommand {
public int CustomerId;
public Address DestinationAddress;
}

class MoveCustomerHandler : ICommandHandler<MoveCustomerCommand>
{
private readonly ICustomerRepository repository;

public MoveCustomerHandler(ICustomerRepository repository) {
this.repository = repository;
}

public void Handle(MoveCustomerCommand command) {
// use repository and command to handle the operation
}
}

The command has now become a behaviorless Parameter Object that can be passed on to the new command handler component. This change solves the problems with the original design:

  • The creation of object graphs can now be verified with a single automated test.
  • No callbacks to a service locator are needed.
  • No factory is needed; code can depend directly on ICommandHandler<T>.
  • Creation of the object graph is not needlessly delayed until runtime.

The general fix here is to change the public API to expose the runtime data through its contract so that the request-specific information can be passed through. This allows the component to become stateless.

But not all violations can be solved like this. Sometimes we don't want to change the public API of our abstractions, especially when the runtime data is an implementation detail. To visualize this point let's take a look at the following example:

class CustomerRepository : ICustomerRepository
{
private readonly IUnitOfWork uow;
private readonly int currentUserId;
private readonly DateTime now;

public CustomerRepository(IUnitOfWork uow, int currentUserId, DateTime now) {
if (currentUserId <= 0) throw new ArgumentException();
if (now.Year < 2015) throw new ArgumentException();
this.uow = uow;
this.currentUserId = currentUserId;
this.now = now;
}
public void Save(Customer entity) {
entity.CreatedBy = this.currentUserId;
entity.CreatedOn = this.now;
this.uow.Save(entity);
}
}

The example shows a CustomerRepository that in addition to depending on an IUnitOfWork, also requires the current user id and the current system time. The current user id is the Id of the logged in user on whose behalf the operation is executed. This Id and current time are both used to update the Customer entity before it is saved.

Just as in our earlier example the use of runtime data is problematic. In this component there is some ambiguity in the constructor because when we examine the type we have no clear idea what we need to inject. What DateTime should be injected? Should it be the Now, Today, yesterday? In other words it would be very easy to create the CustomerRepository with incorrect values, and the only way to verify whether the configuration is correct is through manual testing or a rather awkward integration test.

In this example, however, we don't want to make the runtime data into input parameters of the Save method because that would mean the Save method gets two extra parameters. The addition of these parameters to the Save method will ripple through the system because the direct and indirect consumers of the repository will need to add these parameters to their API as well (all the way up the chain). Not only would this pollute the API, it would also cause us to make sweeping changes throughout the code base for each and every piece of runtime data that some implementation requires in the future.

When a component requires runtime state in its constructor, it becomes impossible to verify the configuration in a maintainable way. A unit test must be written for each component that verifies whether that particular object can be created, while supplied with fake -but valid- runtime data needed for the component to initialize.

The current user id and current time are runtime data but they are implementation details and consumers of the repository should not be concerned with such details. We should place these runtime values behind clearly defined abstractions, removing the ambiguity in their definition and allowing the runtime data to flow through the system with the method calls:

class CustomerRepository : ICustomerRepository
{
private readonly IUnitOfWork uow;
private readonly IUserContext userContext;
private readonly ITimeProvider timeProvider;

public CustomerRepository(IUnitOfWork uow, IUserContext userContext,
ITimeProvider timeProvider) {
this.uow = uow;
this.userContext = userContext;
this.timeProvider = timeProvider;
}
public void Save(Customer entity) {
entity.CreatedBy = this.userContext.CurrentUserId;
entity.CreatedOn = this.timeProvider.Now;
this.uow.Save(entity);
}
}

Creating implementations for the two newly defined abstractions could be as simple as the following:

class TimeProvider : ITimeProvider 
{
public DateTime Now => DateTime.Now;
}

class HttpSessionUserContext : IUserContext
{
public int CurrentUserId => (int)HttpContext.Session["userId"];
}

These two implementations are adapters; they adapt our application-specific abstractions to a specific technology, tool, or system component that we wish to hide for our application components. These adapters are part of the composition root.

Do note though that primitive values (such as int and string) are not runtime data per definition. Configuration values such as connection strings are primitives, but they are usually known at application startup, and don't change during the lifetime of the application. Those 'static' values can safely be injected into the constructor. Still, if you find yourself injecting the same configuration value into many different components you are missing an abstraction, but that's a discussion for another day.

To summarize, the solution to the problem of injecting runtime data into components is to let runtime data flow through method calls on an initialized object graph by either:

  1. pass runtime data through method calls of the API
    or
  2. retrieve runtime data from specific abstractions that allow resolving runtime data.

Happy injecting.

- Dependency injection - five comments / No trackbacks - §

The code samples on my weblog are colorized using javascript, but you disabled javascript (for my website) on your browser. If you're interested in viewing the posted code snippets in color, please enable javascript.

five comments:

Great article! Welcome back to blogging! :)
Dennis van der Stelt (URL) - 09 11 15 - 06:37

As Dennis stated; long awaited blog post from you and as always, on point. :-)

Hoping to see more in the future.
Jan Hartmann (URL) - 09 11 15 - 10:05

Awesome! Welcome back! :)
Nazaret - 17 11 15 - 13:39

Great article. But I think there is something that is still missing. Consider the example of a UI application that allows people to type in an FTP server and some other connectivity settings at runtime, and then connects to such server to do some work (like allowing the user to upload or download files). Wouldn't you in this case create an abstract factory that creates some IFtpServer implementation given the connection settings? Consuming classes of IFtpServer wouldn't want to know anything about the connection settings, so you can't put these settings as method parameters. Also you cannot obtain such information from some context interface as you did with the ITimeProvider for example. So it seems that the need for abstract factories still exists for some cases.
Yacoub Massad (URL) - 22 11 15 - 00:15

Yacoub,

In my latest article (https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=100) I go into more details about why I think Abstract Factories are a design smell and should be avoided in most cases.
Steven (URL) - 14 08 16 - 22:13


No trackbacks:

Trackback link:

Please enable javascript to generate a trackback url


  
Remember personal info?

/

Before sending a comment, you have to answer correctly a simple question everyone knows the answer to. This completely baffles automated spam bots.
 

  (Register your username / Log in)

Notify:
Hide email:

Small print: All html tags except <b> and <i> will be removed from your comment. You can make links by just typing the url or mail-address.