Traceable Layer

The Traceable Layer consists of several decorator objects which have these requirements:

  1. There should be a one to one correspondence between a class in the Functional Layer and the Traceable Layer.
  2. The traceable class must directly derive from the functional class it is mimicking.
  3. The Traceable Layer must never have business logic.
  4. Factories must be used to generate any new instances of a class.
  5. Auto-generated code should be used create most traceable classes.
  6. The Traceable Layer should be kept in a separate library from the Functional Layer.

The one-to-one ratio is fairly straightforward: if we want to track what is going inside of a class in the Functional Layer, then we want to make a corresponding class in the Traceable Layer. What is less straightforward is that any class in the Traceable Layer may only inherit directly from the corresponding Functional Layer. This requirement ensures that the Traceable class is able to take the place of the Functional class with no issues. The idea behind this is that then a Traceable class may be introduced at any point, and there is no fear its presence will will cause any disruption in the service.

Making sure that no new logic is introduced in the Traceable Layer is of the utmost importance. The purpose of the Traceable Layer is to give us visibility into what is happening inside of the Functional Layer. If we add new logic in the Traceable Layer, we no longer have an accurate view. See Single Responsibility Principle for more details.

By using factories we are able to greatly reduce the complexity of maintaining the Traceable Layer. See Factories for more details.

Finally, by keeping the code of the Traceable Layer in a separate library, we are better able to ensure that the Traceable Layer is not accidentally used in a performance critical environment. Simply put, if the Traceable library is not present on the machine, we know it is not being used.

Trace Data

The data the gathered from the Traceable Layer is referred to as Trace Data. I have found that modeling the data as XML nodes is a very useful representation. Each function is its own node, with any internal function calls being represented as a child node.

One significant deviation from standard XML is that attributes are used to show results. This way a collapsed node shows the function call and returned values. Any parameters instead will be the first child nodes contained function node.

The Tracer provides more details on how the specific calls below will handle the data.


The interface of a Traceable Layer class will only differ from the interface of the corresponding Functional Layer class in one way: all of the constructors in the Traceable class will take an additional parameter: the Tracer instance. For example: namespace FunctionalLayer.Objects { public class Example { ... public Example() { ... } Public Example(DataStream dataStream) { ... } } } namespace TraceableLayer.Objects { public class Example : FunctionalLayer.Objects.Example { private ITracer _tracer; ... public Example(ITracer tracer) { _tracer = tracer; } Public Example(ITracer tracer, DataStream dataStream) : base(dataStream) { _tracer = tracer; } } }

This single difference is why we will use Factories to create everything. The factories will allow us to to switch between using Functional classes and Traceable classes transparently since their object construction calls will appear the same.

Note: I have found it better to place the ITracer parameter at the front of the parameter list for any constructor. This reinforces the fact that the Trace class's purpose is to generate trace data. It also protects any default values that parameters may be using. The second fact is less important, since we should be using factories to generate any instance, but it is there just in case.

Tracing Function Calls

If we look at a function call as a few discrete steps, it becomes very easy to intercept and store important information about the function. The specific steps are:

  • Call the function.
  • Pass it the parameters.
  • Process the function.
  • Retrieve the results.

When we trace a function call, add a few steps into it:

  • Call the function.
  • Store the function name
  • Store the parameters
  • Pass it the parameters.
  • Process the function.
  • Store the results.
  • Retrieve the results.
Traceable Function Call

Here is a more concrete example. We have a class used to search for customer data, and it has a function that locates the customer based on their last and first name. namespace FunctionalLayer.Customers { public class CustomerSearch { ... public virtual CustomerData FindCustomerByName(string firstName, string lastName) { ... return customerData; } } }

The Traceable version of the class stores the class name and function name, stores the parameters, and then calls the base function. Finally it stores the results that are returned, and then actually returns the results. namespace TraceableLayer.Customers { public class CustomerSearch : FunctionalLayer.Customers { private ITracer _tracer; ... public override CustomerData FindCustomerByName(string lastName, string firstName) { _tracer.NewNode("CustomerSearch.FindCustomerByName"); _tracer.Parameter("lastName", lastName); _tracer.Parameter("firstName", firstName); var result = base.FindCustomerByName(lastName, firstName); return _tracer.CloseNode(result); } } }

The trace data that results from this call looks like this: <CustomerSearch.FindCustomerByName result="Smith,John"> <lastName>Smith</lastName> <firstName>John</firstName> ... additional trace data </CustomerSearch.FindCustomerByName>

It should be noted that the trace data does not display the actual logic in the function. But that is not a problem. Since we have the class and function name, we are able to easily look up the code that is being called. The values in the trace data show us the values being used in the code. Since we know where to find the code, and the values being used in it, we are able to look at the code, and verify it visually.

Tracing Properties

Tracing properties is very similar to tracing functions; this should not come as too much as surprise, since defining a property is actually just syntactic shorthand that defines a getter and setter function with a common name.

From an external view, we can do two things with a property: read a property, or write to a property. The Traceable Layer is primarily concerned with intercepting the read component, since that allows us to store the values when the property is used. Traceable Properties

We can use the CustomerData class from the prior example as a way to show Traceable properties. namespace FunctionalLayer.Customers { public class CustomerData { public virtual string LastName { get; set; } public virtual string FirstName { get; set; } ... } }

The Traceable class intercepts the reads, and stores the class name, property name, and property value in the trace data. namespace TraceableLayer.Customers { public class TraceCustomerData : FunctionalLayer.Customers { private ITracer _tracer; public override string LastName { get { return _tracer.GetProperty("CustomerData.LastName", base.LastName); } set { base.LastName = _tracer.SetProperty("CustomerData.LastName", value); } } public override string FirstName { get { return _tracer.GetProperty("CustomerData.FirstName", base.FirstName); } set { base.FirstName = _tracer.SetProperty("CustomerData.FirstName", value); } } } }

Anytime LastName was requested in the code, the following node would be added to the Trace data. <CustomerData.LastName_get>Smith</CustomerData.LastName_get>

Tracing Memoizing Properties

In some cases, a property may memoize the data. Prior to memoizing the data, it would need to detect that the data is not present, and then request it. One way to handle it is to treat the property as a function.

namespace FunctionalLayer.BusinessLogic { public class Example { private decimal? _expensiveCall; public virtual decimal ExpensiveCall { get { if (!_expensiveCall.HasValue) { _expensiveCall = RetrieveData(); } return _expensiveCall.Value; } } protected virtual decimal RetrieveData() { ... } } }

The trace class will handle the situation the same way as the previous example: namespace TraceableLayer.BusinessLogic { public class TraceExample : FunctionalLayer.BusinessLogic { private Tracer _tracer; public override decimal ExpensiveCall { get { return _tracer.GetProperty("Example.ExpensiveCall", base.ExpensiveCall); } } protected override decimal RetrieveData() { _tracer.NewNode("Example.RetrieveData"); var result = base.RetrieveData(); _tracer.AddAttribute("result", result); return _tracer.CloseNode(result); } } }

The first time ExpensiveCall is called, we will see trace data for RetrieveData(), and then see the node for the property: <Example.RetrieveData result="value">...</Example.RetrieveData><Example.ExpensiveCall_get>value</Example.ExpensiveCall_get>

Subsequent calls to the property will simply result in the trace data for the property appearing. The only time the trace data for RetrieveData() will appear again is if memoized value is reset.

Tracing Copies

Copying an instance has the potential to generate several lines of trace data; and most likely this extra data is unnecessary. One way to handle this extra data is to simply remove it. However, you may still want to at least add a single line to the trace data so that you can see that a copy did occur. This way, you can detect situations where perhaps copying is occurring too much, or should not even be occurring at all. namespace FunctionalLayer.Customers { public class CustomerData { ... public virtual CustomerData Copy() { var to = new CustomerData { ... copy data ... }; return to; } } } namespace TraceableLayer.Customers { public class CustomerData : FunctionalLayer.Customers.CustomerData { Tracer _tracer; ... public override CustomerData Copy() { _tracer.NewNode("IgnoreCopy"); var value = base.Copy(); _tracer.CloseAndRemoveNode(); _tracer.AddNode("CustomerData.Copy", ToString()); } public string ToString() { return base.LastName + "," + base.FirstName + "(" + base.CustomerId + ")"; } } }

The output of the copy would look this: <CustomerData.Copy>Smith,John(C123-723J6Q)</CustomerData.Copy>

Next: Traceable Layer Return Types

Copyright © 2017-2019 Adin H. Baber, all rights reserved.