Introduction
Simplify code by using composition
.NET

Simplify code by using composition

In my post about software simplicity I talked about how we introduce complexity in software without realizing it. In this post I want to discuss a pattern that allows you to get rid of the complexity introduced by using annotations or aspect oriented concepts. Let’s take a look at a basic example:

public class TaskService { private TaskRepository repo; public TaskService(ITaskRepository taskRepository) { repo = taskRepository; } public void CreateTask(string name) { repo.Insert(new Task(name)); } public void MarkTaskDone(int taskId, string userName) { var task = repo.Get(taskId); task.Status = TaskStatus.Done; task.ModifiedBy = userName; repo.Update(task); } }

This class has two small methods to create and update a task. Until now, this code is not complex and is fairly straightforward. Since these methods map one to one with use cases, they’re a good place to deal with cross-cutting concerns such as logging, authorization and exception handling.

Implementing cross-cutting concerns with annotations

A common solution is using annotations or aspect oriented programming. This allows us to describe how code behaves:

public class TaskService { [Log] [IgnoreExceptions] [Authorize("admin")] public virtual void CreateTask(string name) { repo.Insert(new Task(name)); } [Log] [LogExceptions] [Authorize("admin,user")] public virtual void MarkTaskDone(int taskId, string userName) { var task = repo.Get(taskId); task.Status = TaskStatus.Done; task.ModifiedBy = userName; repo.Update(task); } }

Apart from the attributes, the code looks equally simple and although it’s still as readable as it was before, it’s a lot more complicated. For those attributes to do their work, we need something to intercept calls to these methods. To intercept the calls, we need to make sure that anything that calls these methods, gets rewritten (either at compile time or dynamically at runtime).

This can be done by using a dynamic proxy (there are other ways, but none of them are really simple). A dynamic proxy is a runtime generated class, that inherits from your class and overrides its methods. In the overridden method, depending on the implementation of the aspect, it can do something before or after the original method or something entirely different than what the method was actually supposed to do. That’s a lot of hidden complexity:

  • A runtime generated class
    That’s definitely not simple
  • Inherits from your class and overrides the method
    This implies that you define your method as virtual. This is something you should just “know
  • It can do something before or after the original method or something entirely different than what the method was actually supposed to do
    From a client’s perspective, this is unexpected behavior. When you look at the CreateTask-method, at first sight there’s nothing to it: it creates and inserts a new Task, true to the name of the method. With the annotations, that’s not really true anymore: it now logs all calls (how? where? before? after?), ignores exceptions (all of them? does it log them?) and it authorizes the call (what happens when auth fails?).

So these three little annotations actually introduce a lot of complexity, while still keeping our code deceivingly easy.

The problem

Although the annotations introduced complexity, they still add value: we are not mixing cross-cutting concerns with domain logic. So is this complexity a necessary evil or is there a better solution? To find a better solution, we first need to find out what the problem is.

What we want to do is add behavior on top of an existing method. Why can’t we do that with plain composition? Do we need to use runtime generated code?

The problem is that we don’t have a** common interface**. If there was one, we could get rid of the dynamic proxies and annotations and just build an explicit object graph (using a decorator pattern for example). A dynamic proxy solves this by creating a common interface at runtime in the form of a PointCut. Because there’s no common interface at compile time, it generates one at runtime using reflection, so you can write an attribute implementation like this:

public void InterceptMethodCall(PointCut calleeInfo) { var methodName = calleeInfo.Name; var passedParameters = calleeInfo.Paramters; .... // Implement security, logging, ... here }

This method can now be attached to any other method. A dynamic proxy enables us to convert any type of method-signature into a PointCut. Or phrased differently: a dynamic proxy enables us to convert any method into a message.

Changing the problem

Solving this problem is impossible with simple composition (using today’s languages as far as I know). So, to come up with a simpler solution, we first need to change our problem. Instead of dealing with N different methods that have N parameters, we could say that we’re only going to have a method that has 1 parameter. To do so, we need to apply a small refactoring: “Introduce parameter object” (Fowler, Refactoring, improving the design of existing code). This is how our code looks like after applying the refactoring:

public class TaskService { private TaskRepository repo; public TaskService(ITaskRepository taskRepository) { repo = taskRepository; } public void Handle(CreateTask command) { repo.Insert(new Task(command.Name)); } public void Handle(MarkTaskDone command) { var task = repo.Get(command.TaskId); task.Status = TaskStatus.Done; task.ModifiedBy = command.userName; repo.Update(task); } }

Instead of having methods with N parameters, each method now just accepts one parameter. That parameter is an object which holds all the parameters w epreviously had and implements an interface ICommand. (ICommand is just a **marker interface, **it doesn’t have any properties or methods).

The solution

Since all our methods are now the same, we can extract a common interface:

public interface ICommandHandler where T : ICommand { void Handle(T command); }

and implement it in our class:

public class TaskCommandHandlers : ICommandHandler, ICommandHandler { private TaskRepository repo; public TaskService(ITaskRepository taskRepository) { repo = taskRepository; } public void Handle(CreateTask command) { repo.Insert(new Task(command.Name)); } public void Handle(MarkTaskDone command) { var task = repo.Get(command.TaskId); task.Status = TaskStatus.Done; task.ModifiedBy = command.userName; repo.Update(task); } }

Because we have a common interface we can use composition to implement our solution. This is an example of how you could implement logging:

public class LogHandler : ICommandHandler where T : ICommand { ICommandHandler _next; public LogHandler(ICommandHandler next) { _next = next; } public void Handle(T command) { var logMessage = serialize(command); // pseudo code log(logMessage); // pseudo code _next.Handle(command); } }

The LogHandler implements the same interface, (and thus can replace the original implementation). We accept another instance of the same interface which we’ll invoke after logging the message. This is a basic example of the decorator design pattern.

To use this, you could either wire it up manually:

ICommandHandler handler = new LogHandler( new TaskCommandHandlers( new TaskRepository() ) ); handler.Handle(new CreateTask("TaskName"));

Or you could use any other method of composition. This is an example of how to wire this up with a DI container (Ninject):

kernel.Bind<ICommandHandler() .To() .WhenInjectedInto<LogHandler>(); kernel.Bind<ICommandHandler() .To<LogHandler>();

Conclusion

By changing our problem we can eliminate complexity. The advantages of the proposed solution over an annotation based solution:

  • No dependency on an external framework (bugs, discontinuation, learning the new framework, …)
  • Simpler code, using plain composition instead of runtime code generation
  • No implicit design constraints (virtual methods, …)
  • Single responsibility: when we apply annotations to a class, we’re violating SRP (single responsibility principle): even though it’s metadata, the class is still responsible for having the correct metadata about logging, exception handling, … If we extract this into a composeable solution like we did, we can now configure this behavior at a higher level and consolidate it.
  • Testability: using annotations, you’d have to run the application to test that your pipeline works as expected (that means end-to-end testing). With simple composition, you could write an integration test (in the form of a unit test) that creates the pipeline and verifies the results

Using this pattern is just one example of how we can keep our code simple and prevent complexity from creeping in. This is not easy though. Simple != easy, (often the opposite is true) but it’s well worth it in the long run. Rich Hickey has an excellent presentation on the difference between simple and easy which I recommend watching: http://www.infoq.com/presentations/Simple-Made-Easy

Kenneth Truyers
View Comments
Next Post

How to ditch your ORM

Previous Post

Simplicity in software: what you see is not what you get