Design Concepts

As noted before there are two separate layers: the Functional Layer and the Traceable Layer.

The Functional Layer is essentially the same as any existing service. A request comes into the service, the request is processed, database values are updated, logged information is written to the log files, and the response is returned.

Service Call

The Traceable Layer wraps around the Functional Layer, and intercepts the data at several points in the process. It is important to note that the Traceable Layer does not introduce any new business logic. If the Traceable layer did introduce any new business logic, then it would no longer reflect what was actually happening in functional layer.

The trace data is collected in a instance of the Tracer object. The trace data is then returned alongside the normal response.

Service Call with Traceability

Connecting the two layers is done via inheritance. For each class in the Functional Layer, there is a corresponding child class in the Traceable Layer. The Traceable classes implement an override for every property and method of the Functional class. These overrides allow us to to trace what is going on, but still only use the logic of the functional class.

Note: I chose to use inheritance so that any traceable class could be used in place of a functional class. This way, the Functional Layer may be (almost) designed as if the Traceable Layer doesn't exist.

A typical class hierarchy in the Functional Layer might look like this:

Simple Inheritance

In comparison, the Traceable Layer classes attach themselves to the Functional Layer's hierarchy.

Traceable Inheritance

Note that while there is a clear relationship between the FunctionalBase class and any of the children, there is no direct relationship between the TraceableBase class and the other Traceable children classes. This is an intentional design choice. As said before, the Traceable classes exists to intercept the information being used by the Functional classes, and not to provide any new business logic. Thus, there is no reason to design something that requires a TraceableSub3 instance to be passed in, and expect it to also accept a TraceableSub3_2 as well. You would instead design the system to take in a FunctionalSub3, which means it would also accept a FunctionalSub3_2, and by extension it would also accept a TraceableSub3 or TraceableSub3_2 as well.

Next: Code Organization