Why dependency injection rocks

May 19, 2008 at 12:59 PMAndre Loker

Since I started using IoC containers like Castle Windsor or Spring 18 months or so ago the way I write code has changed quite a lot. Let me give an example. Let's assume I am currently writing the code to create a user account that can be used to login into an application. Let us further assume that I am using the following domain driven architecture:

architecture

That is, we have a domain model that is surrounded by a service layer. The latter is accessed by the application. Data moved back and forth between application and service layer comes in the form of data transfer objects. The service layer and domain model have access to the repositories that can be used to access all entities in the domain.

Now that you have an overview of the architecture, let us assume that I am busy writing a class in the service layer that handles account related stuff. Let us call it AccountService. In this case I want to write a method that creates a new account in the system. This method has some additional constraints:

  • Only currently logged in administrators may create an account
  • The username and email must not already be in use
  • The code should run within a single database transaction. This constraint actually is a consequence of the previous one: checking for duplicate user names/email addresses and saving a new account should be atomic.
  • We would like to apply some general exception handling policies

Let's have a look at the method filled with pseudo code comments:

   1: namespace BeautyOfInjection.Service {
   2:     public class AccountService {
   3:         /// <summary>
   4:         /// Creates a new account.
   5:         /// </summary>
   6:         /// <param name="userName">User name for the new account.</param>
   7:         /// <param name="email">E-mail address for the new account.</param>
   8:         /// <remarks>
   9:         ///  Only administrators can create new accounts. 
  10:         /// </remarks>
  11:         public void CreateAccount(string userName, string email) {
  12:             // start transaction
  13:             // check whether current user has permission to create an account
  14:             // check whether user name and email address are unique
  15:             // create a new account instance
  16:             // save instance to db
  17:             // commit transaction
  18:             // on failure: rollback transaction and handle exception
  19:         }
  20:     }
  21: }

This should be straight forward so far. Let us begin to write code. First, we start off with the transaction handling, but let us try to think in terms of dependency injection. Personally I think that the burden of transaction handling should not lie on a single method in the service layer. Maybe we want CreateAccount to run within a larger transaction. What we need is the possibility to declare the requirement of a transaction. All transaction pluming should then be handled externally. This is a good example of separation of concerns. To achieve this, let us define an interface that allows us handle transaction requirements.

   1: using System;
   2:  
   3: namespace BeautyOfInjection.Persistence {
   4:  
   5:     /// <summary>
   6:     /// Allows the client to declare its requirement for a transaction.
   7:     /// </summary>
   8:     public interface ITransactionControl{
   9:         /// <summary>
  10:         /// Opens a new transaction context.
  11:         /// </summary>
  12:         /// <returns>A new transaction context</returns>
  13:         ITransactionContext EnsureTransaction();
  14:     }
  15:  
  16:     /// <summary>
  17:     /// A (possibly nested) transaction scope.
  18:     /// </summary>
  19:     public interface ITransactionContext : IDisposable {
  20:         /// <summary>
  21:         /// Considers this transaction context a success.
  22:         /// </summary>
  23:         /// <remarks>
  24:         /// If <see cref="VoteRollback"/> is not called by this transaction context or 
  25:         /// any parent or child context, the database transaction will be committed.
  26:         /// </remarks>
  27:         void VoteCommit();
  28:  
  29:         /// <summary>
  30:         /// Votes for a rollback on this transaction.
  31:         /// </summary>
  32:         /// <remarks>
  33:         /// Calling this method will finaly cause a rollback of the database transaction.
  34:         /// </remarks>
  35:         void VoteRollback();
  36:     }
  37: }

The actual working of those interfaces is not that important here. Let us just assume that we can declare the requirement for a transaction using EnsureTransaction and the returned ITransactionContext can be used to decide whether the transaction should commit or rollback. This design by the way resembles the Active Record transaction management. So, how do we gain access to a ITransactionControl instance? Remember that we are looking for inversion of control with dependency injection. We should therefore declare the need for an ITransactionControl object somewhere the IoC container recognizes. Two common approaches are Constructor Injection and Setter Injection. I chose for setter injection. With the new C# 3.0 auto property syntax it's a snap to declare the property.

   1: public class AccountService {
   2:     // This dependency will be injected by the container
   3:     public ITransactionControl Transactions { get; set; }
   4:  
   5:     /// <summary>
   6:     /// Creates a new account.
   7:     /// </summary>
   8:     /// <param name="userName">User name for the new account.</param>
   9:     /// <param name="email">E-mail address for the new account.</param>
  10:     /// <remarks>
  11:     ///  Only administrators can create new accounts. 
  12:     /// </remarks>
  13:     public void CreateAccount(string userName, string email) {
  14:         // start transaction
  15:         using (var txn = Transactions.EnsureTransaction()) {
  16:             // check whether current user has permission to create an account
  17:             // check whether user name and email address are unique
  18:             // create a new account instance
  19:             // save instance to db
  20:             // commit transaction
  21:             txn.VoteCommit();
  22:             // on failure: rollback transaction 
  23:             // (will be done automatically if VoteCommit is not called before dispose)
  24:         }
  25:         // ... and handle exception
  26:     }
  27: }

This was easy. We simply do not care about were we get our ITransactionControl instance from. We simply assume that it will be resolved by the IoC container or explicitly set during a unit test.

Next step, check that the current user has the permission to create the account. I like to have this kind of code in a separate class. It simplifies unit testing a lot. So let us define a new dependency interface:

   1: using System.Security;
   2:  
   3: namespace BeautyOfInjection.Security {
   4:     /// <summary>
   5:     /// 
   6:     /// </summary>
   7:     public interface IPermissions {
   8:         /// <summary>
   9:         /// Checks that the current user may create a new account.
  10:         /// </summary>
  11:         /// <exception cref="SecurityException">Permission was not granted.</exception>
  12:         void TryCreateAccount();
  13:     }
  14: }

Before I return to CreateMethod, I'll define another dependency interface, regarding exception handling. Resemblance with the Exception Handling Block is no coincidence:

   1: using System;
   2:  
   3: namespace BeautyOfInjection.Service {
   4:     /// <summary>
   5:     /// Handles exceptions
   6:     /// </summary>
   7:     public interface IExceptionHandler {
   8:         /// <summary>
   9:         /// Handles the exception and returns whether the original exception should be rethrown.
  10:         /// </summary>
  11:         /// <param name="exception">The exception.</param>
  12:         /// <returns></returns>
  13:         bool HandleAndCheckForRethrow(Exception exception);
  14:     }
  15: }

Let us see how far we are with CreateUser:

   1: using System;
   2: using BeautyOfInjection.Persistence;
   3: using BeautyOfInjection.Security;
   4:  
   5: namespace BeautyOfInjection.Service {
   6:     public class AccountService {
   7:  
   8:         #region dependencies
   9:         public ITransactionControl Transactions { get; set; }
  10:         public IPermissions Permissions { get; set; }
  11:         public IExceptionHandler ExceptionHandler { get; set; }
  12:         #endregion
  13:  
  14:         /// <summary>
  15:         /// Creates a new account.
  16:         /// </summary>
  17:         /// <param name="userName">User name for the new account.</param>
  18:         /// <param name="email">E-mail address for the new account.</param>
  19:         /// <remarks>
  20:         ///  Only administrators can create new accounts. 
  21:         /// </remarks>
  22:         public void CreateAccount(string userName, string email) {
  23:             try {
  24:                 // start transaction
  25:                 using (var txn = Transactions.EnsureTransaction()) {
  26:                     // check whether current user has permission to create an account
  27:                     Permissions.TryCreateAccount();
  28:  
  29:                     // check whether user name and email address are unique
  30:                     // create a new account instance
  31:                     // save instance to db
  32:  
  33:                     // commit transaction
  34:                     txn.VoteCommit();
  35:                 }
  36:             } catch (Exception e) {
  37:                 // on error, handle exception
  38:                 if(ExceptionHandler.HandleAndCheckForRethrow(e)) {
  39:                     throw;
  40:                 }
  41:             }
  42:         }
  43:     }
  44: }

Not bad, if you ask me. Throw in some more external depencies: we want to access entities using the repository pattern, so let's define a account repository interface. Furthermore, we want accounts to be created using a factory. I think that the factory would also be a useful place to check for uniqueness of user name and email. I am always a bit struggling on this topic: on the one hand I want the domain model to use the repositories as little as possible. On the other hand I do not like it when business rules (i.e. uniqueness of email and user name) are not checked in the domain model. As I consider the factory to be part of the model, checking those rules in the factory seems reasonable to me. Be aware though that if you support changing a user name/email address you will have to check for uniqueness again and this time not in the factory. So you might as well put the uniqueness check into the model class (and skip the factory altogether). Anyway, this is just an example, so let's stick to the factory/repository pair.

Here are the required interfaces:

   1: namespace BeautyOfInjection.Model {
   2:     /// <summary>
   3:     /// An account
   4:     /// </summary>
   5:     public interface IAccount {
   6:         string UserName { get; }
   7:         string EmailAddress { get; }
   8:     }
   9:  
  10:     /// <summary>
  11:     /// 
  12:     /// </summary>
  13:     public interface IAccountFactory {
  14:         /// <summary>
  15:         /// Creates a new account instance.
  16:         /// </summary>
  17:         /// <param name="userName">Name of the user.</param>
  18:         /// <param name="email">The email.</param>
  19:         /// <returns>
  20:         /// The newly created account.
  21:         /// </returns>
  22:         /// <remarks>
  23:         /// The method validates the user name and email and ensures that both are unique.
  24:         /// </remarks>
  25:         /// <exception cref="ValidationException">
  26:         /// The user name and/or email were not valid or not unique.
  27:         /// </exception>
  28:         IAccount Create(string userName, string email);
  29:     }
  30:  
  31:     /// <summary>
  32:     /// 
  33:     /// </summary>
  34:     public interface IAccountRepository {
  35:         void Save(IAccount account);
  36:         IAccount GetAccountByUserName(string userName);
  37:         IAccount GetAccountByEmail(string email);
  38:     }
  39: }

ValidationException would be a custom exception type that is used for business rule violations.

Now we are ready to present the final version of CreateAccount:

   1: using System;
   2: using BeautyOfInjection.Model;
   3: using BeautyOfInjection.Persistence;
   4: using BeautyOfInjection.Security;
   5:  
   6: namespace BeautyOfInjection.Service {
   7:     public class AccountService {
   8:  
   9:         #region dependencies
  10:         public ITransactionControl Transactions { get; set; }
  11:         public IPermissions Permissions { get; set; }
  12:         public IExceptionHandler ExceptionHandler { get; set; }
  13:         public IAccountFactory Factory   { get; set; }
  14:         public IAccountRepository Repository{ get; set; }
  15:         #endregion
  16:  
  17:         /// <summary>
  18:         /// Creates a new account.
  19:         /// </summary>
  20:         /// <param name="userName">User name for the new account.</param>
  21:         /// <param name="email">E-mail address for the new account.</param>
  22:         /// <remarks>
  23:         ///  Only administrators can create new accounts. 
  24:         /// </remarks>
  25:         public void CreateAccount(string userName, string email) {
  26:             try {
  27:                 // start transaction
  28:                 using (var txn = Transactions.EnsureTransaction()) {
  29:                     // check whether current user has permission to create an account
  30:                     Permissions.TryCreateAccount();
  31:  
  32:                     var account = Factory.Create(userName, email);
  33:                     // the previous line would have thrown an exception
  34:                     // if userName and/or email had been invalid/not unique
  35:  
  36:                     // add instance to repository
  37:                     Repository.Save(account);
  38:  
  39:                     // vote for transaction commit
  40:                     txn.VoteCommit();
  41:                 }
  42:             } catch (Exception e) {
  43:                 // on error, handle exception
  44:                 if(ExceptionHandler.HandleAndCheckForRethrow(e)) {
  45:                     throw;
  46:                 }
  47:             }
  48:         }
  49:     }
  50: }

You see that the service layer class becomes a mediator between all the external dependencies. What are the advantages of this design?

  • Separation of concerns to the max, every aspect is neatly extracted to a separate interface
  • High (unit-) testability. As we can mock/stub any of the external dependencies we have full control over their behavior. In this case we can easily test whether the method correctly requests a transaction, votes commit on success, handles exceptions, checks the required permissions etc. by simply passing mock objects with the right expectations.
  • The code in the service layer gets simplified. We can concentrate on the core functionality of CreateAccount instead of cluttering the code with loads of unrelated code.
  • We do not need to worry about how dependencies get injected. Just add the dependencies as e.g. properties (or constructor arguments) and assume that they will be provided externally. Of course you ultimately have to setup your IoC container or provide mocks/stubs. But while writing AccountService you just don't care.

Note: you will want to extract an interface from AccountService (say: IAccountService). This can then be used to define dependencies on this service in other layers, for example in your controllers (if you use an MVC framework like MonoRail).

Posted in: Snippets | Patterns

Tags: , , , ,

Comments (8) -

Stefan Lieser
Germany Stefan Lieser says:

Hi Andre,

impressive post!

The only thing I'm struggling with is the usage of exceptions for regular flow control. I see exceptions as exceptional, things that went wrong because no developer thought about it or because infrastructure is not working as expected. The usage of exceptions to tell the caller of the service that the current user doesn't have the permission to do the operation smells to me. But maybe I have to think about this a bit more.

Regards,
Stefan

Andre Loker
Germany Andre Loker says:

Trying to do something that you are not allowed to do is considered exceptional from my personal point of view. I think that while it is the task of the business layer to enforce permissions it is the task of the user interface to prevent the user from doing things he is not allowed to do.

Therefore, under normal circumstances the GUI wouldn't show a button to delete a user if the current user has not sufficient rights.

If the user now deliberately tries to execute a specific forbidden action (e.g. by entering a URL manually) or if the GUI accidentally shows an option which is not accessible for the current user the business layer will catch the security issue and handle it as an exceptional case.

Of course, that's just my personal opinion, your personal experience may vary.

Stefan Lieser
Germany Stefan Lieser says:

Hi Andre,

you are right. If the caller of the service is responsible for evaluating user rights before calling the service then the service should throw an exception if permissions don't allow this call. But... then you have to do the permission check two times: before the call and inside the call. I don't have any idea how to avoid this of course Wink But maybe I should try an example...

Regards,
Stefan Lieser

C. Baptiste
United States C. Baptiste says:

Andre,

How is the repository's Save() method processed under the transaction started by EnsureTransaction? I do not see the link between ITransactionControl / ITransactionContext and IAccountRepository.

Thanks in advance for any assistance.

Carlos

Andre Loker
Germany Andre Loker says:

@Carlos:
The repository has no direct knowledge of the current transaction, let alone control over it. I think it is not the task of the repository to handle transactions.

What happens is this: I'm using NHibernate, and the repository implementations uses an NHibernate ISession to query and update objects. If I call EnsureTransaction on the transaction control object, what happens under the hood is that the object checks whether there is an active ISession for the current request. If not, it opens one and stores it at a "well known place". It then calls BeginTransaction on the session.

The IAccountRepository implementation has a dependency on an object that is capable of fetching the currently active session from the well known place mentioned above. So when the account repository is created it knows where to get the current ISession that it can use to query the database. It will never try to control the transaction, though.

When the ITransactionContext is disposed it checks whether it is the last open context. If it is and it has received a VoteCommit it commits the transaction that was opened on the ISession.

The transaction contexts can be nested. If I for example need CreateAccount and another method on the service object to run in the same transaction, I can let the caller open a transaction that spans both calls. That's why VoteCommit is called VoteCommit and not Commit. It only says, that the current transaction context is alright so far. If any of the nested transaction contexts fails to call VoteCommit, the parent transaction will be rolled back. This approach is very similar to the one found in Castle ActiveRecord.

I hope I could clarify the design a bit. If not, feel free to ask further!

C. Baptiste
United States C. Baptiste says:

Andre,

Thanks again for your clarification and your excellent article. Your explanation is quite clear.

While awaiting your response I came across this article (www.codeproject.com/.../...atternsForUnitTest.aspx) . Here the ISession is passed to the Repository directly rather than grabbing the reference from the "well known" storage. What are your thoughts on this approach?

I look forward to your response.

Carlos

Andre Loker
Germany Andre Loker says:

You are referring to the repository design under the "The Repository pattern" section, right?

Granted, I think the design is rather flawed. On the one hand, the author tries to create an abstraction of a repository by creating the interface and two implementations (one "real" for NHibernate, one "fake" for testing). But at the same time he binds the method signatures to one specific implementation (NHibernate) by having an implementation detail (ISession) as a parameter. The fake implementation (InMemoryRepositoryBase) does not need the ISession at all, but still gets it as an argument.

To overdraw the scenario a bit, why not make the methods look like:
AddOrUpdate(ISession session, ILog log, IMailSender mailSender, T domainModel)
- after all one of the implementations might want to log something or send an email Smile

What I want to say: keep the interface of your repository (and in general) clean, don't pollute it with implementation specifics. If a concrete implementation of IRepository needs an ISession, that's fine. But let the implementation either look it up or (preferably) have the dependency injected automatically on creation (think: NHibernateRepositoryBase(ISession session))

Next thing to note is that now thanks to the design the caller of AddOrSave has to provide an ISession to the method. Where does the caller get the ISession from? For me this is way too much an "infrastructure detail". The author himself quotes that a repository "Mediates between the domain and data mapping layers". But with this design he'll be intermingling ORM details with domain code throughout the application.

If you unit test the caller (say, a service layer class), you have to somehow stub or mock the ability to create an ISession - your unit tests get more complicated.

What's furthermore noticeable about the author's IRepository is that some methods have ISession as a parameter, but some (Load and FetchByExample) don't. Where do those methods get their ISession from? We have a consistency problem here.

What could have been the motivation of the author to have such a design? I assume he wants the repositories to be created only once. In this case of course he would to pass the current session on which the methods should operate on. But is the requirement of having only one repository instance (e.g. per aggregate domaindrivendesign.org/.../Aggregates.html) really reasonable? I don't think so. Repositories can be very lightweight. In the case of an NHibernate repository they generally don't need anything more than an ISession (or an object that can provide the current ISession) as member variable. So creating a bunch of repositories per web request is no problem at all.

C. Baptiste
United States C. Baptiste says:

Andre,

I appreciate your analysis. I was getting lost in the abstractions and how to fill in the implementation specifics. Your explanation has cleared this up for me.

Thanks again.

Carlos

Pingbacks and trackbacks (2)+