Saturday, July 30, 2011

How to Use 3rd-Party Components

Long time ago since my last blog, but I had to concentrate on some other things. Guess it's time to get back here :-).

After two failed tries of two, more advanced topics, I decided to write about some more basic stuff.

Let's talk about how to correctly use 3rd-Party components in your software? However, even if this topic is some kind of basic for advanced developers and architects, it's a very important practice in my opinion.

What Means 3rd-Party Component Here?

Sure, everybody has a idea of "3rd-Party Components", but I'm not just talking about guerilla frameworks. I'm talking about every component that is not, either part of the stable part of the .NET framework or part of your own project.

This includes:
  • Web-Services. In a SOA environment even those of the own project to truly live the loose coupling concept
  • Data Access Layers (DAL) like Entity Framework or NHibernate
  • New or still changing and complex parts of the .NET framework, like Workflow Foundation (WF) or Windows Communication Foundation (WCF) on client side
  • Independent, true 3rd-party frameworks like log4net, Microsoft Enterprise Library (MEL) and any other library from the web or another ISV

This does not include:
  • .NET framework core components
  • Technologies like Windows Presentation Foundation (WPF) or Silverlight, since the whole application (a GUI) does not use the technology as a component but is completely based on it. It would be way to much (mostly senseless) effort to tailor an own framework over an existing one that is basis for the complete application
  • WCF on server side
  • Helper libraries of the company or project you are working at

A brief explanation for the difference of WCF on client and server side:

When using WCF on server side, the whole process is based on this technology (like WPF in case of GUIs). If you ever need to change the technology, you will usually have to rewrite the whole service, independent of how lose you are coupled to WCF or not.

When using WCF on client side, the consumed service is usually just one component that could be replaced. E.g. you could replace a data web-service or a service that does some calculations with a local DLL. In this case it should be possible to replace the consumed web-service with any other technology, or even another web-service library.

Dangerous Ways to Consume 3rd-Party Components

I'm sure everybody has already seen things source code like... Log4net
public class MyApp {
   private static readonly ILog _log = LogManager.GetLogger(typeof(MyApp));

   static void Main(string[] args) {
      BasicConfigurator.Configure();

      _log.Info("Entering application.");
      Bar bar = new Bar();
      bar.DoIt();
      _log.Info("Exiting application.");
   }
}
Entity framework
using (var context = new MyEntityFrameworkContext()) {
   var customer = context.Customers.Where(c => c.Code == "ALFKI").First();
}
or MEL Unity (sample from MEL help)
IUnityContainer myContainer = new UnityContainer();
myContainer.RegisterType<ILogger, FileLogger>();
myContainer.RegisterType<ILogger, FastFileLogger>("FastLogger");
// ...
ILogger myLogger = myContainer.Resolve<ILogger>();

At first sight, this all looks good, pragmatic and straight forward. However, this approach can bring up serious problems. Why? The used framework/library might fit perfectly at the moment, but this can (and sometimes does) change over the years. Software systems are usually made to stay for, at least, 8 years until it is time to redesign it. Sure, if the software doesn't need many changes, it can be used for longer time.

What if, either the component becomes updated and its interface(s) changes? What if you upgrade your system to a new platform (e.g. a new .NET framework) and the component doesn't work on this platform? What if new technologies come up and are not covered by the component you are using?
You can say "Hey, I'm sure that wont happen, that's all doomsaying", but how about this:
  • Microsoft completely redesigned the Workflow Foundation from .NET 3 to .NET 4
  • ASP.NET web-services are almost obsolete and will not be extended due to WCF
  • LINQ-to-SQL will not be extended any more
  • Dino Esposito and Andrea Saltarello wrote in their book Architecting Applications for the Enterprise how they had to redesign almost their whole system as they had to switch from NHibernate to Entity Framework 1
As you see, it is not uncommon that changes happen over the lifetime of a software system. It's up to us to be armed when those things happen

Which Options do we have?

Often, it's not too hard to stay relatively independent of external components.

In case of components like log4net or MEL Unity we just have to create a wrapper class that becomes created by a factory class or a DI (sure, we will not create MEL Unity from another DI container..). The following snippets are just pseudo code to indicate what I'm talking about.

Wrap log4net:
public interface ILogger {
   void Info(string message);
}

class Log4NetLogger : ILogger {
   private static readonly ILog _log = LogManager.GetLogger("MyApp");

   void ILogger.Info(string message) {
      _log.Info(message);
   }
}
Wrap unity:
public interface IDependencyContainer {
   T Resolve<T>();
}

class UnityDependencyContainer : IDependencyContainer {
   IUnityContainer _unity = new UnityContainer();

   T IDependencyContainer.Resolve<T>() {
      return _unity.Resolve<T>();
   }
}

public class DependendcyContainerFactory {
   public static IDependencyContainer Create() {
      // if ever using more than one container, use configuration 
      // or anything else here to switch between them
      return new UnityDependencyContainer();
   }
}

In case of DAL frameworks like EF we need to think one step further. EF comes up with this cool LINQ-to-EF feature that makes database queries quite fancy. Though, do we really need this in applications/libraries that consume our DAL? Does it make sense that a GUI app knows how to query all current orders of a customer like "context.Orders.Where(o => o.Customer.Id == myCustomer.Id)"? What if the relation becomes ever changed on database level and it is not possible to change the EDL to provide this relation any more? I'd say LINQ-to-EF is a nice feature within the DAL, but should not be used from consumers.
public interface IDataContext {
   IEnumerable<Order> GetOrdersForCustomer(Customer customer);
}

public class MyEfDataContext : IDataContext {
   public IEnumerable<Order> GetOrdersForCustomer(Customer customer) {
      using (var ctx = new MyEfContext()) {
         return ctx.Where(o => o.Customer.Id == customer.Id).ToList();
      }
   }
}
Now, everybody can safely use the method to get the Orders and if the relation ever changes, it needs to be changed at only one position and non of the consumers will notice it.

Working Safely

And now, after writing a few wrapper classes and thinking about how to use features (like LINQ-to-EF), we are armed to use all those nice components in our application without the danger to be controlled by them.
public class MyApp {
   public MyApp() {
      _container = DependendcyContainerFactory.Create();
      _logger = _container.Resolve<ILogger>();
   }

   private IDependencyContainer _container;
   private ILogger _logger;

   public void DoWork() {
      _logger.Info("foo");
      IDataContext myData = _container.Resolve<IDataContext>();
      Customer customer = GetCustomerToWorkWith();
      var orders = myData.GetOrdersForCustomer(customer);
      _logger.Info("bar");
   }

   private Customer GetCustomerToWorkWith() {
      // usually the implementation to get the customer
      return null;
   }
}

If there will ever be a new, better framework. If there will ever be a feature that is not covered by our components. If there will ever be any other reason to switch to another technology. We are able to switch without redesigning the whole application.

Don't get this all wrong, it is not always easy to stay independent and switching to another 3rd-party component will always cause effort. But usually this effort is much less if our applications are designed to be ready for changes.


(It felt good to write but, after one year since my last blog, it also felt like not being perfect. Seems like I need some practice after this time, so please be patient with me for this post..)

1 comment: