forked from ploeh/ploeh.github.com
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path2010-09-20-InstrumentationwithDecoratorsandInterceptors.html
146 lines (144 loc) · 16.9 KB
/
2010-09-20-InstrumentationwithDecoratorsandInterceptors.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
---
layout: post
tags: [Castle Windsor, Dependency Injection, Software Design]
date: 2010-09-20 19:18:21 UTC
title: "Instrumentation with Decorators and Interceptors"
---
{% include JB/setup %}
<div id="post">
<p>One of my readers recently <a href="http://www.manning-sandbox.com/thread.jspa?forumID=607&threadID=39542">asked me an interesting question</a>. It relates to <a href="http://amzn.to/12p90MG">my book</a>'s chapter about Interception (chapter 9) and Decorators and how they can be used for instrumentation-like purposes.</p> <p>In an earlier blog post we saw how we can <a href="http://blog.ploeh.dk/2010/04/07/DependencyInjectionIsLooseCoupling.aspx">use Decorators to implement Cross-Cutting Concerns</a>, but the question relates to how a set of Decorators can be used to log additional information about code execution, such as the time before and after a method is called, the name of the method and so on.</p> <p>A <a href="http://en.wikipedia.org/wiki/Decorator_pattern">Decorator</a> can excellently address such a concern as well, as we will see here. Let us first define an IRegistrar interface and create an implementation like this:</p>
<div style="font-family: courier new; background: white; color: black; font-size: 10pt"><pre style="margin: 0px"><span style="color: blue">public</span> <span style="color: blue">class</span> <span style="color: #2b91af">ConsoleRegistrar</span> : <span style="color: #2b91af">IRegistrar</span>
{
<span style="color: blue">public</span> <span style="color: blue">void</span> Register(<span style="color: #2b91af">Guid</span> id, <span style="color: blue">string</span> text)
{
<span style="color: blue">var</span> now = <span style="color: #2b91af">DateTimeOffset</span>.Now;
<span style="color: #2b91af">Console</span>.WriteLine(<span style="color: #a31515">"{0}\t{1:s}.{2}\t{3}"</span>,
id, now, now.Millisecond, text);
}
}</pre></div>
<p>Although this implementation ‘logs' to the Console, I'm sure you can imagine other implementations. The point is that given this interface, we can add all sorts of ambient information such as the thread ID, the name of the current principal, the current culture and whatnot, while the text string variable still gives us an option to log more information. If we want a more detailed API, we can just make it more detailed - after all, the IRegistrar interface is just an example.</p>
<p>We now know how to register events, but are seemingly no nearer to instrumenting an application. How do we do that? Let us see how we can instrument the <a href="http://blog.ploeh.dk/2010/02/02/RefactoringToAggregateServices.aspx">OrderProcessor class</a> that I have described several times in past posts.</p>
<p>At the place I left off, the OrderProcessor class uses Constructor Injection all the way down. Although I would normally prefer using a DI Container to auto-wire it, here's a manual composition using <em>Poor Man's DI</em> just to remind you of the general structure of the class and its dependencies:</p>
<div style="font-family: courier new; background: white; color: black; font-size: 10pt"><pre style="margin: 0px"><span style="color: blue">var</span> sut = <span style="color: blue">new</span> <span style="color: #2b91af">OrderProcessor</span>(
<span style="color: blue">new</span> <span style="color: #2b91af">OrderValidator</span>(),
<span style="color: blue">new</span> <span style="color: #2b91af">OrderShipper</span>(),
<span style="color: blue">new</span> <span style="color: #2b91af">OrderCollector</span>(
<span style="color: blue">new</span> <span style="color: #2b91af">AccountsReceivable</span>(),
<span style="color: blue">new</span> <span style="color: #2b91af">RateExchange</span>(),
<span style="color: blue">new</span> <span style="color: #2b91af">UserContext</span>()));</pre></div>
<p>All the dependencies injected into the OrderProcessor instance implement interfaces on which OrderProcessor relies. This means that we can decorate each concrete dependency with an implementation that instruments it.</p>
<p>Here's an example that instruments the IOrderProcessor interface itself:</p>
<div style="font-family: courier new; background: white; color: black; font-size: 10pt"><pre style="margin: 0px"><span style="color: blue">public</span> <span style="color: blue">class</span> <span style="color: #2b91af">InstrumentedOrderProcessor</span> : <span style="color: #2b91af">IOrderProcessor</span>
{
<span style="color: blue">private</span> <span style="color: blue">readonly</span> <span style="color: #2b91af">IOrderProcessor</span> orderProcessor;
<span style="color: blue">private</span> <span style="color: blue">readonly</span> <span style="color: #2b91af">IRegistrar</span> registrar;
<span style="color: blue">public</span> InstrumentedOrderProcessor(
<span style="color: #2b91af">IOrderProcessor</span> processor,
<span style="color: #2b91af">IRegistrar</span> registrar)
{
<span style="color: blue">if</span> (processor == <span style="color: blue">null</span>)
{
<span style="color: blue">throw</span> <span style="color: blue">new</span> <span style="color: #2b91af">ArgumentNullException</span>(<span style="color: #a31515">"processor"</span>);
}
<span style="color: blue">if</span> (registrar == <span style="color: blue">null</span>)
{
<span style="color: blue">throw</span> <span style="color: blue">new</span> <span style="color: #2b91af">ArgumentNullException</span>(<span style="color: #a31515">"registrar"</span>);
}
<span style="color: blue">this</span>.orderProcessor = processor;
<span style="color: blue">this</span>.registrar = registrar;
}
<span style="color: blue"> #region</span> IOrderProcessor Members
<span style="color: blue">public</span> <span style="color: #2b91af">SuccessResult</span> Process(<span style="color: #2b91af">Order</span> order)
{
<span style="color: blue">var</span> correlationId = <span style="color: #2b91af">Guid</span>.NewGuid();
<span style="color: blue">this</span>.registrar.Register(correlationId,
<span style="color: blue">string</span>.Format(<span style="color: #a31515">"Process begins ({0})"</span>,
<span style="color: blue">this</span>.orderProcessor.GetType().Name));
<span style="color: blue">var</span> result = <span style="color: blue">this</span>.orderProcessor.Process(order);
<span style="color: blue">this</span>.registrar.Register(correlationId,
<span style="color: blue">string</span>.Format(<span style="color: #a31515">"Process ends ({0})"</span>,
<span style="color: blue">this</span>.orderProcessor.GetType().Name));
<span style="color: blue">return</span> result;
}
<span style="color: blue"> #endregion</span>
}</pre></div>
<p>That looks like quite a mouthful, but it's really quite simple - the cyclomatic complexity of the Process method is as low as it can be: <em>1</em>. We really just register the Process method call before and after invoking the decorated IOrderProcessor.</p>
<p>Without changing anything else than the composition itself, we can now instrument the IOrderProcessor interface:</p>
<div style="font-family: courier new; background: white; color: black; font-size: 10pt"><pre style="margin: 0px"><span style="color: blue">var</span> registrar = <span style="color: blue">new</span> <span style="color: #2b91af">ConsoleRegistrar</span>();
<span style="color: blue">var</span> sut = <span style="color: blue">new</span> <span style="color: #2b91af">InstrumentedOrderProcessor</span>(
<span style="color: blue">new</span> <span style="color: #2b91af">OrderProcessor</span>(
<span style="color: blue">new</span> <span style="color: #2b91af">OrderValidator</span>(),
<span style="color: blue">new</span> <span style="color: #2b91af">OrderShipper</span>(),
<span style="color: blue">new</span> <span style="color: #2b91af">OrderCollector</span>(
<span style="color: blue">new</span> <span style="color: #2b91af">AccountsReceivable</span>(),
<span style="color: blue">new</span> <span style="color: #2b91af">RateExchange</span>(),
<span style="color: blue">new</span> <span style="color: #2b91af">UserContext</span>())),
registrar);</pre></div>
<p>However, imagine implementing an InstrumentedXyz for every IXyz and compose the application with them. It's possible, but it's going to get old really fast - not to mention that it massively violates the DRY principle.</p>
<p>Fortunately we can solve this issue with any DI Container that supports dynamic interception. Castle Windsor does, so let's see how that could work.</p>
<p>Instead of implementing the same code ‘template' over and over again to instrument an interface, we can do it once and for all with an interceptor. Imagine that we delete the InstrumentedOrderProcessor; instead, we create this:</p>
<div style="font-family: courier new; background: white; color: black; font-size: 10pt"><pre style="margin: 0px"><span style="color: blue">public</span> <span style="color: blue">class</span> <span style="color: #2b91af">InstrumentingInterceptor</span> : <span style="color: #2b91af">IInterceptor</span>
{
<span style="color: blue">private</span> <span style="color: blue">readonly</span> <span style="color: #2b91af">IRegistrar</span> registrar;
<span style="color: blue">public</span> InstrumentingInterceptor(<span style="color: #2b91af">IRegistrar</span> registrar)
{
<span style="color: blue">if</span> (registrar == <span style="color: blue">null</span>)
{
<span style="color: blue">throw</span> <span style="color: blue">new</span> <span style="color: #2b91af">ArgumentNullException</span>(<span style="color: #a31515">"registrar"</span>);
}
<span style="color: blue">this</span>.registrar = registrar;
}
<span style="color: blue"> #region</span> IInterceptor Members
<span style="color: blue">public</span> <span style="color: blue">void</span> Intercept(<span style="color: #2b91af">IInvocation</span> invocation)
{
<span style="color: blue">var</span> correlationId = <span style="color: #2b91af">Guid</span>.NewGuid();
<span style="color: blue">this</span>.registrar.Register(correlationId,
<span style="color: blue">string</span>.Format(<span style="color: #a31515">"{0} begins ({1})"</span>,
invocation.Method.Name,
invocation.TargetType.Name));
invocation.Proceed();
<span style="color: blue">this</span>.registrar.Register(correlationId,
<span style="color: blue">string</span>.Format(<span style="color: #a31515">"{0} ends ({1})"</span>,
invocation.Method.Name,
invocation.TargetType.Name));
}
<span style="color: blue"> #endregion</span>
}</pre></div>
<p>If you compare this to the Process method of InstrumentedOrderProcessor (that we don't need anymore), you should be able to see that they are very similar. In this version, we just use the <em>invocation</em> argument to retrieve information about the decorated method.</p>
<p>We can now add InstrumentingInterceptor to a WindsorContainer and enable it for all appropriate components. When we do that and invoke the Process method on the resolved IOrderProcessor, we get a result like this:</p>
<div style="font-family: courier new; background: white; color: black; font-size: 10pt"><pre style="margin: 0px">bbb9724e-0fad-4b06-9bb0-b8c1c460cded 2010-09-20T21:01:16.744 Process begins (OrderProcessor)
43349d42-a463-463b-8ddf-e569e3170c97 2010-09-20T21:01:16.745 Validate begins (TrueOrderValidator)
43349d42-a463-463b-8ddf-e569e3170c97 2010-09-20T21:01:16.745 Validate ends (TrueOrderValidator)
44fdccc8-f12d-4057-ae03-791225686504 2010-09-20T21:01:16.746 Collect begins (OrderCollector)
8bbb1a0c-6134-4652-a4af-cd8c0c7184a0 2010-09-20T21:01:16.746 GetCurrentUser begins (UserContext)
8bbb1a0c-6134-4652-a4af-cd8c0c7184a0 2010-09-20T21:01:16.747 GetCurrentUser ends (UserContext)
d54359ff-8c32-487f-8728-b19ff0bf4942 2010-09-20T21:01:16.747 GetCurrentUser begins (UserContext)
d54359ff-8c32-487f-8728-b19ff0bf4942 2010-09-20T21:01:16.747 GetCurrentUser ends (UserContext)
c54c4506-23a8-4553-ba9a-066fc64252d2 2010-09-20T21:01:16.748 GetSelectedCurrency begins (UserContext)
c54c4506-23a8-4553-ba9a-066fc64252d2 2010-09-20T21:01:16.748 GetSelectedCurrency ends (UserContext)
b3dba76b-6b4e-44fa-aca5-52b2d8509db3 2010-09-20T21:01:16.750 Convert begins (RateExchange)
b3dba76b-6b4e-44fa-aca5-52b2d8509db3 2010-09-20T21:01:16.751 Convert ends (RateExchange)
e07765bd-fe07-4486-96f1-f74d77241343 2010-09-20T21:01:16.751 Collect begins (AccountsReceivable)
e07765bd-fe07-4486-96f1-f74d77241343 2010-09-20T21:01:16.752 Collect ends (AccountsReceivable)
44fdccc8-f12d-4057-ae03-791225686504 2010-09-20T21:01:16.752 Collect ends (OrderCollector)
231055d3-4ebb-425d-8d69-fb9c85d9a860 2010-09-20T21:01:16.752 Ship begins (OrderShipper)
231055d3-4ebb-425d-8d69-fb9c85d9a860 2010-09-20T21:01:16.753 Ship ends (OrderShipper)
bbb9724e-0fad-4b06-9bb0-b8c1c460cded 2010-09-20T21:01:16.753 Process ends (OrderProcessor)</pre></div>
<p>Notice how we care easily see where and when method calls begin and end using the descriptive text as well as the correlation id. I will leave it as an exercise for the reader to come up with an API that provides better parsing options etc.</p>
<p>As a final note it's worth pointing out that this way of instrumenting an application (or part of it) can be done following the <a href="http://en.wikipedia.org/wiki/Open/closed_principle">Open/Closed Principle</a>. I never changed the original implementation of any of the components.</p>
</div>