1

I'm working on a feature where I need to send analytics data whenever a command is executed — for example, whether the command was triggered via a hotkey or a UI button. However, I want to achieve this without modifying the existing command interface.

I’m using a decorator pattern to wrap command execution and insert analytics logic. But I’m not sure how to pass extra context (like trigger source) into the decorator without changing the method signature of the interface.

Here’s a simplified version of my code:

  • An interface that defines a command capable of executing an action:
public interface IActionCommand
{
    void ExeciteAction(ActionModel actionModel);
}
  • ActionModel can contain anything related to the action (parameters, metadata, etc.)
  • A base decorator for IActionCommand:
public abstract class ActionCommandDecorator : IActionCommand
{
    protected IActionCommand Decorator { get; }

    public ActionCommandDecorator(IActionCommand decorator)
    {
        Decorator = decorator;
    }
    public abstract void ExeciteAction(ActionModel actionModel);
}
  • A concrete implementation of IActionCommand. It's the actual logic for performing some action.
public class AnyActiomCommand : IActionCommand
{
    public void ExeciteAction(ActionModel actionModel)
    {
        //Do action
    }
}
  • A decorator that wraps the command to track analytics:
public class AnyActiomCommandAnalyticsDecorator : ActionCommandDecorator
{
    private AnaliticaService _analiticaService = new AnaliticaService();
    public AnyActiomCommandAnaliticDecorator(IActionCommand decorator) : base(decorator)
    {
    }

    public override void ExeciteAction(ActionModel actionModel)
    {
        _analiticaService.SendData(new AnalyticsModel());
        Decorator.ExeciteAction(actionModel);
    }
}
  • Analytics model can contain anything

The problem: I don’t want to modify the IActionCommand interface to include metadata or a parameter like Execute(ActionModel source , AnalyticsModel analyticsorce). I want to keep the interface clean.

Question: What is the best way to pass additional context like the source of command execution to the decorator without changing the base interface or command classes?

5
  • 1
    The "MS" (object sender, object args) pattern works well, IMO. Sender can be actual or "faked". Args can obviously be anything also. Cast accordingly. Commented Jun 24 at 15:02
  • The best way i can think of is to inject a service that loads the context inside your decorator through the constructor (Exple for APIs inject IHttpAccessor)! if you have a specific context create a providerService for it and inject it. Commented Jun 24 at 15:17
  • 1
    Where would the extra data come from? What creates it? Commented Jun 24 at 16:07
  • A pedantic note on design patterns (since the question is tagged as such) if we're talking about the GoF Command Pattern, then its interface is prohibited from accepting any argument whatsoever. In other words, the method signature accepting ActionModel is already non-compliant. Obviously you're not obligated to comply with any GoF pattern; but calling this interface a Command is confusing (and contradictory) for those who are familiar with the famous patterns. I would recommend naming the interface something else. Commented Jun 24 at 21:29
  • How the additional data is known? Is it comming from executing event handler? How do you call the method in those event handlers you mentioned? You can put this additional logic right before invoking interface method ExeciteAction, that would keep the interface in place. However without seeing all the code that's just a guess. Commented Jun 25 at 9:03

1 Answer 1

1

If the goal is analytics some kind of untyped or dynamic solution might be appropriate.

One possible solution would be to capture a stack trace and store this with the command. The analytics database could then use this stack trace to group commands.

Another possible approach is to register some low level keyboard and mouse hooks and send these events to the analytics database. Then write your analytics queries to handle sequences of events. It might also be possible to set some kind of thread local context object during processing of an input event. So that all analytics messages for that event can be grouped together.

Both approach will have problems handling changes between versions, since both stack traces and order of events might change. Multi threading might be another source of problems. Writing manual queries and some kind of fuzzy matching might help, and while this is unlikely to solve all problems, it might be "good enough".

Another approach is to view analytics reporting as contractually required, and change your classes and interfaces so the compiler can help enforce that contract. Either by adding a parameter to your command interface, or move the analytics reporting to some place that know what kind of input invoked the command.

Sign up to request clarification or add additional context in comments.

1 Comment

I like the final option: realize that analytics is an essential part of the application, and change the interface to reflect that. Especially considering that trying to do it through injection or hooks is likely to be incredibly fragile.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.