Tracing Return Types

Often in our code, we will be returning classes from function calls. We want those classes to be Traceable as well, and making them Traceable is fairly straightforward. However, returning Traceable versions requires additional work in the encapsulating class.

A function that returns a class instance is the same as discussed in Traceable Layer, but with one additional step: the returned object has to be created.

When we trace the function, we do it as outlined before, with one important difference: we are going to have the Functional class construct the object in a separate function, and the Traceable class is going to override the construction function.

Traceable Function Call with Returned Type

The example below is a sphere class that will calculate a specific point on the sphere based on the yaw and pitch passed into the function. namespace FunctionalLayer.Objects { public class Coordinates { public X { get; set; } public Y { get; set; } public Z { get; set; } ... } public class Sphere { ... public Coordinates GetSurfacePoint(double yaw, double pitch) { var point = new Coordinates(); ... x, y, z calculated and assigned return point; } } }

Making the Coordinates class Traceable is fairly straightforward; however the Sphere class will need additional changes. What we need to do specifically is to have a factory function inside of the Sphere class that creates Coordinates instances. Then, when we use a Traceable Sphere, we will have it automatically create Traceable Coordinates. namespace FunctionalLayer.Objects { public class Coordinates { public virtual X { get; set; } public virtual Y { get; set; } public virtual Z { get; set; } ... } public class Sphere { ... public virtual Coordinates GetSurfacePoint(double yaw, double pitch) { var point = CreateCoordinates(); ... x, y, z calculated and assigned return point; } // Create returned type protected virtual Coordinates CreateCoordinates() { return new Coordinates(); } } }

Design Note: we intentionally did not use the property initialization when constructing the Coordinates instance. Instead we created the object via a function call, and then assigned the values to the object afterwards. If we used initialization in the Functional Layer class, then we would also have to implement the same initialization in the Traceable Layer, which would violate the no business logic rule. The way we did it here, we avoid having to update the Traceable Layer if changes are made to the Functional Layer.

For the Traceable version of Sphere, we add the typical Traceability code into GetSurfacePoint(); however, for CreateCoordinates(), we merely have it return the Traceable version of Coordinates. When base.GetSurfacePoint() is called, it will automatically use the Traceable version of CreateCoordinates(). namespace TraceableLayer.Objects { public class Coordinates : FunctionalLayer.Objects.Coordinates { private Tracer _tracer; public override X { ... traceability code ... } public override X { ... traceability code ... } public override X { ... traceability code ... } ... public Coordinates(Tracer tracer) { _tracer = tracer; } } public class Sphere : FunctionalLayer.Objects.Sphere { private Tracer _tracer; ... public override Coordinates GetSurfacePoint(double yaw, double pitch) { _tracer.OpenNode("Sphere.GetSurfacePoint"); _tracer.AddNode("yaw", yaw) _tracer.AddNode("pitch", pitch) var results = base.GetSurfacePoint(yaw, pitch); _tracer.AddAttribute("results", results); _tracer.CloseNode(); return results; } protected override Coordinates CreateCoordinates() { return new TraceableLayer.Objects.Coordinates(_tracer); } } }

Now, whenever CreateCoordinates() is called, a Traceable Coordinates object is returned; which in turn means any time the coordinate are used, the values will be added to the trace data.

Design Note: this design pattern is essentially implementing the Factory Pattern as part of the encapsulating class. An alternative method is to have an actual factory class used by the encapsulating class instead. See Factories for more details.

Tracing Anonymous Return Types

Anonymous types are classes that are implicitly defined; which means we cannot really make a Traceable version of the class. However, we can still intercept the returned object, and trace its values.

The simplest way to trace the the values returned as an anonymous type is to loop over the object and add each value as an attribute to the node. namespace FunctionalLayer.Objects { public class AnonExample { ... public virtual object GetAnonymous() { return new { prop1="value1", prop2="value2" }; } } } namespace TraceableLayer.Objects { public class AnonExample : FunctionalLayer.Objects.AnonExample { private Tracer _tracer; ... public override object GetAnonymous() { _tracer.OpenNode("AnonExample.GetAnonymous"); var results = base.GetAnonymous(); Type type = results.GetType(); var properties = type.GetRuntimeProperties(); foreach (var property in properties) { string name = property.Name; string val = property.GetValue(results).ToString(); _tracer.AddAttribute(name, val); } _tracer.CloseNode(); return results; } } }

The trace data looks like this: <AnonExample.GetAnonymous prop1="value1", prop2="value2"> </AnonExample.GetAnonymous>

Next: Formatting Traceable Objects