30

There's this framework that I'm helping to design. There are some common tasks that should be done using some common components : Logging, Caching and raising events in particular.

I am not sure if it's better to use dependency injection and introduce all of these components to each service (as properties for example) or should I have some kind of meta data placed over each method of my services and use interception to do these common tasks?

Here's an example of both:

Injection:

public class MyService
{
    public ILoggingService Logger { get; set; }

    public IEventBroker EventBroker { get; set; }

    public ICacheService Cache { get; set; }

    public void DoSomething()
    {
        Logger.Log(myMessage);
        EventBroker.Publish<EventType>();
        Cache.Add(myObject);
    }
}

and here's the other version:

Interception:

public class MyService
{
    [Log("My message")]
    [PublishEvent(typeof(EventType))]
    public void DoSomething()
    {

    }
}

Here are my questions:

  1. Which solution is best for a complicated framework?
  2. If interception wins, what are my options to interact with internal values of a method (to use with cache service for example?)? Can I use other ways rather than attributes to implement this behavior?
  3. Or maybe there can be other solutions to solve the problem?
3
  • 2
    I don't have an opinion on 1 and 2, but regarding 3: consider looking into AoP (Aspect-oriented programming) and specifically into Spring.NET.
    – AVIDeveloper
    Mar 10, 2012 at 0:11
  • Just to clarify: you're looking for a comparison between Dependency Injection and Aspect Oriented Programming, correct?
    – M.Babcock
    Mar 10, 2012 at 0:13
  • @M.Babcock Haven't seen it that way myself but that's correct
    – Beatles1692
    Mar 10, 2012 at 13:16

4 Answers 4

39

Cross-cutting Concerns like logging, caching etc. are not dependencies, so shouldn't be injected into services. However, while most people then seem to reach for a full interleaving AOP framework, there's a nice design pattern for this: Decorator.

In the above example, let MyService implement the IMyService interface:

public interface IMyService
{
    void DoSomething();
}

public class MyService : IMyService
{
    public void DoSomething()
    {
        // Implementation goes here...
    }
}

This keeps the MyService class completely free of Cross-cutting Concerns, thus following the Single Responsibility Principle (SRP).

To apply logging, you can add a logging Decorator:

public class MyLogger : IMyService
{
    private readonly IMyService myService;
    private readonly ILoggingService logger;

    public MyLogger(IMyService myService, ILoggingService logger)
    {
        this.myService = myService;
        this.logger = logger;
    }

    public void DoSomething()
    {
        this.myService.DoSomething();
        this.logger.Log("something");
    }
}

You can implement caching, metering, eventing, etc. in the same way. Each Decorator does exactly one thing, so they also follow the SRP, and you can compose them in arbitrarily complex ways. E.g.

var service = new MyLogger(
    new LoggingService(),
    new CachingService(
        new Cache(),
        new MyService());
6
  • 5
    The decorator pattern is a great way to keep those concerns separate, but if you have a LOT of services, that's where I would use an AOP tool like PostSharp or Castle.DynamicProxy, otherwise for each service class interface, I have to code the class AND a logger decorator, and each of those decorators could potentially be very similar boilerplate code (i.e. you get improved modularization/encapsulation, but you still are repeating yourself a lot). Mar 12, 2012 at 17:19
  • 4
    Agreed. I gave a talk last year that describes how to move from Decorators to AOP: channel9.msdn.com/Events/GOTO/GOTO-2011-Copenhagen/… Mar 12, 2012 at 17:30
  • I coded up a simple implementation based on this programgood.net/2015/09/08/DecoratorSpike.aspx Sep 8, 2015 at 5:31
  • How can we inject service and decorators with dependency injection?
    – TIKSN
    Jul 5, 2016 at 14:06
  • @TIKSN The short answer is: as shown above. Since you're asking, however, you must be seeking an answer to something else, but I can't guess what that is. Could you elaborate, or perhaps ask a new question here on the site? Jul 5, 2016 at 14:42
6

For a handful of services, I think Mark's answer is good: you won't have to learn or introduce any new 3rd party dependencies and you'll still be following good SOLID principles.

For a large amount of services, I would recommend an AOP tool like PostSharp or Castle DynamicProxy. PostSharp has a free (as in beer) version, and they just recently released PostSharp Toolkit for Diagnostics, (free as in beer AND speech) which will give you some logging features out of the box.

2

I find the design of a framework to be largely orthogonal to this question--you should focus on the interface of your framework first, and perhaps as a background mental process consider how someone might actually consume it. You don't want to do something that prevents it from being used in clever ways, but it should only be an input into your framework design; one among many.

1

I've faced this problem lots of times and I think that I have come up with a simple solution.

Initially I went with the decorator pattern and manually implemented each method, when you have hundreds of methods this becomes very tedious.

I then decided to use PostSharp but I didn't like the idea of including an entire library just to do something that I could accomplish with (a lot of) simple code.

I then went down the transparent proxy route which was fun but involved dynamically emitting IL at run time and wouldn't be something that I'd want to do in a production environment.

I recently decided to use T4 templates to automatically implement the decorator pattern at design time, it turns out that T4 templates are actually quite hard to work with and I needed this done quickly so I created the code bellow. It's quick and dirty (and it doesn't support properties) but hopefully someone will find it useful.

Here's the code:

        var linesToUse = code.Split(Environment.NewLine.ToCharArray()).Where(l => !string.IsNullOrWhiteSpace(l));
        string classLine = linesToUse.First();

        // Remove the first line this is just the class declaration, also remove its closing brace
        linesToUse = linesToUse.Skip(1).Take(linesToUse.Count() - 2);
        code = string.Join(Environment.NewLine, linesToUse).Trim()
            .TrimStart("{".ToCharArray()); // Depending on the formatting this may be left over from removing the class

        code = Regex.Replace(
            code,
            @"public\s+?(?'Type'[\w<>]+?)\s(?'Name'\w+?)\s*\((?'Args'[^\)]*?)\)\s*?\{\s*?(throw new NotImplementedException\(\);)",
            new MatchEvaluator(
                match =>
                    {
                        string start = string.Format(
                            "public {0} {1}({2})\r\n{{",
                            match.Groups["Type"].Value,
                            match.Groups["Name"].Value,
                            match.Groups["Args"].Value);

                        var args =
                            match.Groups["Args"].Value.Split(",".ToCharArray())
                                .Select(s => s.Trim().Split(" ".ToCharArray()))
                                .ToDictionary(s => s.Last(), s => s.First());

                        string call = "_decorated." + match.Groups["Name"].Value + "(" + string.Join(",", args.Keys) + ");";
                        if (match.Groups["Type"].Value != "void")
                        {
                            call = "return " + call;
                        }

                        string argsStr = args.Keys.Any(s => s.Length > 0) ? ("," + string.Join(",", args.Keys)) : string.Empty;
                        string loggedCall = string.Format(
                            "using (BuildLogger(\"{0}\"{1})){{\r\n{2}\r\n}}",
                            match.Groups["Name"].Value,
                            argsStr,
                            call);
                        return start + "\r\n" + loggedCall;
                    }));
        code = classLine.Trim().TrimEnd("{".ToCharArray()) + "\n{\n" + code + "\n}\n";

Here is an example:

public interface ITestAdapter : IDisposable
{
    string TestMethod1();

    IEnumerable<string> TestMethod2(int a);

    void TestMethod3(List<string[]>  a, Object b);
}

Then create a class called LoggingTestAdapter which implements ITestAdapter, get visual studio to auto implement all of the methods and then run it through the code above. You should then have something like this:

public class LoggingTestAdapter : ITestAdapter
{

    public void Dispose()
    {
        using (BuildLogger("Dispose"))
        {
            _decorated.Dispose();
        }
    }
    public string TestMethod1()
    {
        using (BuildLogger("TestMethod1"))
        {
            return _decorated.TestMethod1();
        }
    }
    public IEnumerable<string> TestMethod2(int a)
    {
        using (BuildLogger("TestMethod2", a))
        {
            return _decorated.TestMethod2(a);
        }
    }
    public void TestMethod3(List<string[]> a, object b)
    {
        using (BuildLogger("TestMethod3", a, b))
        {
            _decorated.TestMethod3(a, b);
        }
    }
}

This is it with the supporting code:

public class DebugLogger : ILogger
{
    private Stopwatch _stopwatch;
    public DebugLogger()
    {
        _stopwatch = new Stopwatch();
        _stopwatch.Start();
    }
    public void Dispose()
    {
        _stopwatch.Stop();
        string argsStr = string.Empty;
        if (Args.FirstOrDefault() != null)
        {
            argsStr = string.Join(",",Args.Select(a => (a ?? (object)"null").ToString()));
        }

        System.Diagnostics.Debug.WriteLine(string.Format("{0}({1}) @ {2}ms", Name, argsStr, _stopwatch.ElapsedMilliseconds));
    }

    public string Name { get; set; }

    public object[] Args { get; set; }
}

public interface ILogger : IDisposable
{
    string Name { get; set; }
    object[] Args { get; set; }
}


public class LoggingTestAdapter<TLogger> : ITestAdapter where TLogger : ILogger,new()
{
    private readonly ITestAdapter _decorated;

    public LoggingTestAdapter(ITestAdapter toDecorate)
    {
        _decorated = toDecorate;
    }

    private ILogger BuildLogger(string name, params object[] args)
    {
        return new TLogger { Name = name, Args = args };
    }

    public void Dispose()
    {
        _decorated.Dispose();
    }

    public string TestMethod1()
    {
        using (BuildLogger("TestMethod1"))
        {
            return _decorated.TestMethod1();
        }
    }
    public IEnumerable<string> TestMethod2(int a)
    {
        using (BuildLogger("TestMethod2", a))
        {
            return _decorated.TestMethod2(a);
        }
    }
    public void TestMethod3(List<string[]> a, object b)
    {
        using (BuildLogger("TestMethod3", a, b))
        {
            _decorated.TestMethod3(a, b);
        }
    }
}

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.