Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

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?
share|improve this question

migrated from stackoverflow.com Mar 10 '12 at 15:16

This question came from our site for professional and enthusiast programmers.

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 '12 at 0:11
    
Just to clarify: you're looking for a comparison between Dependency Injection and Aspect Oriented Programming, correct? – M.Babcock Mar 10 '12 at 0:13
    
@M.Babcock Haven't seen it that way myself but that's correct – Beatles1692 Mar 10 '12 at 13:16
up vote 33 down vote accepted

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());
share|improve this answer
4  
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). – Matthew Groves Mar 12 '12 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/… – Mark Seemann Mar 12 '12 at 17:30
    
I coded up a simple implementation based on this programgood.net/2015/09/08/DecoratorSpike.aspx – Dave Mateer Sep 8 '15 at 5:31
    
How can we inject service and decorators with dependency injection? – TIKSN Jul 5 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? – Mark Seemann Jul 5 at 14:42

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.

share|improve this answer

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.

share|improve this answer

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);
        }
    }
}
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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