Skip to content

Enhancement: Re-implement the Tracing overall implementations - Addressing the Simplicity and Extensibility #941

@mikependon

Description

@mikependon

TL;DR Currently, the ITrace interface as being the base of all tracing capabilities, contains the methods that are equivalent to the existing extended operations for the IDbConnection.

See below for the Insert operation.

/// <summary>
/// A method that is being raised before the actual 'Insert' operation execution.
/// </summary>
/// <param name="log">The cancellable log object referenced by the Insert' execution.</param>
void BeforeInsert(CancellableTraceLog log);

/// <summary>
/// A method that is being raised after the actual Insert' operation execution.
/// </summary>
/// <param name="log">The log object referenced by the 'Insert' execution.</param>
void AfterInsert(TraceLog log);

This is true to all other operations (i.e.: BatchQuery, Query, Update, Merge, Update, etc etc).

In the actual operation, inside the library, if the trace argument is passed with the ITrace object, we do call the Before<Method> BEFORE the actual execution. We also do call the After<Method> AFTER the actual execution with some important information captured during the execution.

The implementation is good and robust on its own and it is also addressing the operational-tracing scenarios if we are to look at it on the users POV.

Concerns

The current implementation is very close for extensibility in which it does not conform with the "O" on the SOLID principles. In addition to this, even though the user can trace the execution of the operations, but there is no way for the user to customize its own execution (on its own perusal/way).

We therefore found out that, the current implementation requires a major refactoring when it comes to implementation.

Proposal

To cover the simplicity of the implementations and the freedom of the tracing to the users of the library, we are planning to refactor the ITrace interface to only contains 2 methods.

  • BeforeExecution(CancellableTraceLog log);
  • AfterExecution(TraceLog log);

The method BeforeExecution stands as the entry point for all the operations BEFORE the actual execution towards the database. The method AfterExecution stands as the exit point of the existing operation AFTER the actual execution from the database.

With this, a new property that handles the execution key is needed to be added into the TraceLog object, the base class for the argument that is being used for all the tracing (i.e.: CancellableTraceLog and TraceLog itself).

On the actual extended methods, the new argument named traceKey is also needed to be added to all the existing operations (i.e.: Insert, Delete, Update, Merge, etc)

See the sample proposal code below.

In the Insert operation, we will add an additional argument named traceKey. But the default value would be "Insert". In case the key is not passed, the "Insert" tracing will be executed.

public static object Insert<TEntity>(this IDbConnection connection,
	string tableName,
	TEntity entity,
	IEnumerable<Field> fields = null,
	string hints = null,
	int? commandTimeout = null,
	IDbTransaction transaction = null,
	string traceKey = "Insert",
	ITrace trace = null,
	IStatementBuilder statementBuilder = null)
	where TEntity : class
{
	...
}

Then, when you call this operation, you as a user will have an option to pass your customized key. In the example below, you call a TraceFactory.Create() that creates an instance of your customized ITrace object, and passing your own customized trace key.

var trace = TraceFactory.Create();
using (var connection = new SqlConnection(ConnectionString))
{
     var entity = new EntityModel
     {
          Property = "Value",
          ....
     };
     var id = connection.Insert<EntityModel>(entity,
          traceKey: "MyCustomTraceKeyForInsert",
          trace: trace);
}

The default value of the traceKey argument is "Insert" since you are calling an Insert operation. For the Query operation, it will be "Query", same goes for the others.

Sample code for your customized ITrace object.

public class MyCustomTrace : ITrace
{
     public BeforeExecution(CancellableTraceLog log)
     {
          if (log.Key == "Insert")
          {
               // This is for the insert operation
          }
          else if (log.Key == "MyCustomTraceKeyForInsert")
          {
               // This is for the insert operation (with your customized tracing key)
          }
          else if (log.Key == "Query")
          {
               // This is for the query operation
          }
          else
          {
               log.Cancel(true);
          }
     }

     public AfterExecution(TraceLog log)
     {
          if (log.Key == "Insert")
          {
               // This is for the insert operation
          }
          else if (log.Key == "MyCustomTraceKeyForInsert")
          {
               // This is for the insert operation (with your customized tracing key)
          }
          else if (log.Key == "Query")
          {
               // This is for the query operation
          }
     }
}

The implementation is very extensible and dynamic in the user's POV.

Drawbacks

This new proposal will create a breaking changes to those users who had already implemented the tracing on their solutions.

Mitigations

To avoid the breaking changes on the users POV, we can create a gist named TraceBase that would stand as the base trace object when tracing tracing all the operations.

Then, in the existing implementation (your class), you have to inherit this TraceBase class and have a minimal code change to override the existing method, instead of implementing the ITrace object.

In short, instead of this...

public class YourTraceClass : ITrace
{
     ...
}

Do this...

public class YourTraceClass : TraceBase
{
     ...
}

We are happy if the community of .NET could share their thoughts on this. 🙇🏼

Metadata

Metadata

Assignees

Labels

breaking-changesA breaking changes (issue, request, enhancement, etc)enhancementNew feature or requestfeatureDefined as a big development item (feature)needs-collaborationDependent to the community decisionoptimizationAn optimization task

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions