• No results found

Using dependency management

Scoping in C#

5.3 Using dependency management

We’ll discuss two kinds of dependency management here: internal and external. When talking about internal dependencies, I’m referring to those that are part of the pro- gram you’re writing. Most frequently, these are a one-to-one mapping to physical files, but you might also have multiple modules in a single file. By modules I mean pieces of code that have a single responsibility, regardless of them being services, factories, mod- els, controllers, or something else. External dependencies are, in contrast, those in which the code isn’t governed by your application itself. You may own or have authored the package, but the code belongs to a different repository altogether, regardless.

I’ll explain what dependency graphs are, and then we’ll investigate ways of working through them, such as the caveats with resorting to the RequireJS module loader, the innocent straightforwardness made available by CommonJS, and the elegant way AngularJS (a Model-View-Controller framework built by Google) resolves dependen- cies while keeping everything modular and testable.

5.3.1 Dependency graphs

When writing out a module which depends on something else, the most common approach is to have your module create an instance of the object you depend on. To illustrate the point, bear with me through a little Java code; it should be easy to wrap your head around. The following listing displays a UserService class, which has the purpose of serving any data requests from a domain logic layer. It could consume any

IUserRepository implementation which is tasked with retrieving the data from a

repository such as a MySQL database or a Redis store. This listing is labeled ch05/08_ dependency-graphs in the samples.

public class UserService {

private IUserRepository _userRepository; public UserService () {

_userRepository = new UserMySqlRepository(); }

public User getUserById (int id) { return _userRepository.getById(id); }

}

50 CHAPTER 5 Embracing modularity and dependency management

But that doesn’t cut it; if your service is supposed to use any repository that conforms to the interface, why are you hard-coding UserMySqlRepository that way? Hard- coded dependencies make it more difficult to test a module, because you wouldn’t merely test against the interface, but rather against a concrete implementation. A bet- ter approach, which is coincidentally more testable, might be passing that depen- dency through the constructor, as shown in the following listing. This pattern is often referred to as dependency injection, which is a smart-sounding alternative to giving an object its instance variables.

public class UserService {

private IUserRepository _userRepository;

public UserService (IUserRepository userRepository) { if (userRepository == null) {

throw new IllegalArgumentException(); }

_userRepository = userRepository; }

public User getUserById (int id) { return _userRepository.getById(id); }

}

This way, you can build out your service the way it was intended, as a consumer of any repository conforming to the IUserRepository interface without any knowledge of implementation specifics. Creating a UserService might not sound like such a great deal, but it gets harder as soon as you take into consideration its dependencies, and its dependencies’ dependencies. This is called a dependency tree. The following snippet is certainly unappealing:

String connectionString = "SOME_CONNECTION_STRING";

SqlConnectionString connString = new SqlConnectionString(connectionString); SqlDbConnection conn = new SqlDbConnection(connString);

IUserRepository repo = new UserMySqlRepository(conn); UserService service = new UserService(repo);

The code shows inversion of control (IoC),3which is a wordy definition for something

rather simple. IoC means that instead of making an object responsible for the instanti- ation of its dependencies, or getting references to them, the object is given the depen- dencies through its constructor or through public properties. Figure 5.3 examines the benefits of using an IoC pattern.

Listing 5.9 Using dependency injection

3 Read a primer on inversion of control and dependency injection by Martine Fowler at http://bevacqua.io/

51

Using dependency management

The IOC code (at the bottom of the figure) is easier to test, more loosely coupled, and easier to maintain as a result, than the classic dependency management code shown at the top of the figure.

IoC frameworks are used to address dependency resolution and mitigate depen- dency hell. The basic gist of these frameworks is that you ditch the new keyword and rely on an IoC container. The IoC container is a registry that has knowledge about how to instantiate your services, repositories, and any other modules. Learning how to con- figure a traditional IoC container (such as Spring in the case of Java, or Castle Wind- sor for C#) is outside of the scope of this book, but a top-level view of the issue is required to pave the road ahead.

IS IOC IMPORTANT FOR TESTABILITY?

Ultimately, the importance of avoiding hard-coded dependencies lies in the ability to easily mock them when unit testing, as you’ll see in chapter 8.

Unit testing is about asserting whether interfaces work as expected, regardless of how they’re implemented. Mocks are stubs that implement the interface, but don’t do anything other than the bare minimum to conform to them. For example, a mocked user repository might always return the same hard-coded User object. This is useful in the context of unit testing, where you might want to test the UserService class on its own, but don’t need details about its inner workings, much less how its dependencies are implemented!

Great! Enough Java for now, though. What does any of this have to do with JavaScript Application Design? Understanding testability principles is required if you hope to