...
Dependency Injection
Main Article: Tapestry IoC Overview Injection
Div | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
| ||||||||||
|
...
This code is for a metric that periodically counts the number of rows in a key database table. Other implementations of MetricProducer will be responsible for measuring CPU utilization, available disk space, number of requests per second, and so forth.
Code Block | ||
---|---|---|
| ||
public class TableMetricProducer implements MetricProducer { . . . public void execute() { int rowCount = . . .; Metric metric = new Metric("app/clients", System.currentTimeMillis(), rowCount); new QueueWriter().sendMetric(metric); } } |
...
Obviously, this code has a problem ... we're creating a new QueueWriter for each metric we write into the queue, and the QueueWriter presumably is going to open the JMS queue fresh each time, an expensive operation. Thus:
Code Block | ||
---|---|---|
| ||
public class TableMetricProducer implements MetricProducer { . . . private final QueueWriter queueWriter = new QueueWriter(); public void execute() { int rowCount = . . .; Metric metric = new Metric("app/clients", System.currentTimeMillis(), rowCount); queueWriter.sendMetric(metric); } |
...
Here's a more immediate problem: JMS connections are really meant to be shared, and we'll have lots of little classes collecting different metrics. So we need to make the QueueWriter shareable:
Code Block | ||
---|---|---|
| ||
private final QueueWriter queueWriter = QueueWriter.getInstance(); |
... and inside class QueueWriter:
Code Block | ||
---|---|---|
| ||
public class QueueWriter { private static QueueWriter instance; private QueueWriter() { ... } public static getInstance() { if (instance == null) { instance = new QueueWriter(); } return instance; } } |
Much better! Now all the metric producers running inside all the threads can share a single QueueWriter. Oh wait ...
Code Block | ||
---|---|---|
| ||
public synchronized static getInstance() { if (instance == null) { instance = new QueueWriter(); } return instance; } |
...
We'll need to change TableMetricProducer to take the QueueWriter as a constructor parameter.
Code Block | ||
---|---|---|
| ||
public class TableMetricProducer implements MetricProducer { private final QueueWriter queueWriter; /** * The normal constructor. * */ public TableMetricProducer(. . .) { this(QueueWriterImpl.getInstance(), . . .); } /** * Constructor used for testing. * */ TableMetricProducer(QueueWriter queueWriter, . . .) { queueWriter = queueWriter; . . . } public void execute() { int rowCount = . . .; Metric metric = new Metric("app/clients", System.currentTimeMillis(), rowCount); queueWriter.sendMetric(metric); } } |
...
For comparison, lets see what the Tapestry IoC implementation would look like:
Code Block | ||
---|---|---|
| ||
public class MonitorModule { public static void bind(ServiceBinder binder) { binder.bind(QueueWriter.class, QueueWriterImpl.class); binder.bind(MetricScheduler.class, MetricSchedulerImpl.class); } public void contributeMetricScheduler(Configuration<MetricProducer> configuration, QueueWriter queueWriter, . . .) { configuration.add(new TableMetricProducer(queueWriter, . . .)) } } |
...
What we are saying is that IoC techniques and discipline will lead to applications that are:
- More testable – smaller, simpler classes; coding to interfaces allows use of mock implementations
- More robust – smaller, simpler classes; use of final variables; thread safety baked in
- More scalable – thread safety baked in
- Easier to maintain – less code, simpler classes
- Easier to extend – new features are often additions (new services, new contributions) rather than changes to existing classes
...