TFS API: How to break an abstraction

I’ve been trying without success for the last hour to limit the results of a TFS query.  It would be, on the face of it, relatively simple: I already know how to get all of the builds of a certain type, I just want to limit it to the last week.  You might think this was achievable using LINQ, and it would be if the problem you were trying to solve was anything other than a timeout.  Instead you find yourself falling down a rabbit hole.

Basically, in order to perform such a query, you need to pass in an implementation of IBuildDetailSpec.  This, in turn references, IBuildDefinitionSpec.  They’re both great big DTOs wrapped in an interface.  You might be wondering at this point why the interface has so many set properties, but Resharper cuts the implementation work down.  However, there’s a nasty shock waiting for you: an invalid array cast exception.  You see, TFS doesn’t really expect an IBuildDetailSpec, it expects a BuildDetailSpec.  Which is an internal sealed class.  You’re actually expected to create this object through the store’s own factory.

Seriously, what benefit is there to exposing an interface when only one implementation could ever possibly work?  I understand Microsoft has different priorities from open source developers, but I genuinely can’t think of an angle in which wrapping a DTO in these layers helps.

Technorati Tags:

Building Fluent NHibernate

The Castle project has recently got an awful lot better at actually being buildable from SVN.  It used to be a nightmare.  Since Fluent NHibernate doesn’t have a recognized built version, you’ve pretty much got to use the source.  So you’d hope it was pretty easy.  Sadly, it’s not.  It’s another nightmare.  Most of the problem comes from their decision to use rake.

Now, don’t get me wrong, I love rake.  It is 100% the right idea for a build tool, but the story on windows sucks.  For one thing, you’ve got to get ruby working on your machine.  Good luck with that, since most of the installs don’t include the required files.  Secondly, rake doesn’t have any extensions to deal with .NET code, which means that you end up dropping down to invoking MSBuild on the solution file anyway.  This also means that you’re prey to the standard problems of building from a solution file, including references not pointing to where you thought they were pointing.  (When I looked in October, FNh was suffering from exactly this problem.)

  • Install the Ruby 1.8.6 one-click installer.  Don’t try anything later: it probably won’t work.  (Best way to get 1.9.1 working on windows is probably to install 1.8.6 and copy across the missing DLLs to the 1.9.1 install directory)
  • Now go into your Fluent NHibnerate folder.  Don’t run build.bat.
  • Put the c:rubybin folder onto your path:  set path=c:rubybin;%path%  (I’m assuming you installed to the default location)
  • If you’ve got a proxy server, find out what its server name and port is.
  • Open installgems.bat in notepad.  Add this to every “gem install” line  –http-proxy http://<<server>&gt;:<<port>>. (obviously, 80 is the default)
  • Now run installgems.bat.
  • Type “rake” (since that’s all build.bat does anyway)
  • Hope I haven’t forgotten anything.

Anyway, Fluent NHibernate is a fabulous project, which has a project velocity the average .NET open source project would give their source repository for, so enjoy.

Technorati Tags:

Castle Windsor Configuration by Convention Proposal

Since adding convention over configuration features is the second most popular suggestion (after actually shipping) on the Castle User Voice site, I figured I’d chip in my own thoughts.  Impressive as Krzysztof’s approach is, I’d rather that we didn’t modify the runtime behaviour of Castle in order to get these kind of features.  Instead, I’d rather make the registration API more powerful.  The AllTypes code shows us a way forward on this, and the fluent NHibernate code has a good model for convention over configuration.

So, here’s a syntax that I would like to see just for auto-registration.  You’ll note it doesn’t even address the question of sub-dependency resolution, which was Krzysztof’s original focus, but I’m trying to start small here.  🙂

using System.Linq;
using System.Reflection;
using Castle.Windsor;
using NUnit.Framework;

namespace ColourCoding.ConventionConfigurationProposal {
    [TestFixture]
    public class ExampleUsage
    {
        [Test]
        public void RegisteringImplementations()
        {
            var container = new WindsorContainer();
            // Observations: we probably need some new syntax like AllTypes.  
// Sadly, we can't use AllTypes // since it's already a convention-based configuration mechanism.
var allInterfaces = Assembly.GetExecutingAssembly().GetTypes().Where(
t => t.IsInterface && !container.Kernel.HasComponent(t)); var allClasses = Assembly.GetExecutingAssembly().GetTypes(); container.Register(UseConvention .For(allInterfaces) .ImplementedBy(allClasses) ); var result = container.Resolve<ILogger>(); Assert.IsInstanceOfType(typeof(StandardLogger), result); // Convention by name Assert.IsFalse(container.Kernel.HasComponent(typeof(IService))); container.Register(UseConvention .For(allInterfaces) .ImplementedBy(allClasses) .UsingSelectionRule((i, s) => s.Name == "Default" + i.Name.Substring(1)) ); var service = container.Resolve<IService>(); Assert.IsInstanceOfType(typeof(DefaultService), service); // Multiple Registration Assert.IsFalse(container.Kernel.HasComponent(typeof(IMultipleService))); container.Register(UseConvention .For(allInterfaces) .ImplementedBy(allClasses) .AllowMultiple() ); var services = container.ResolveAll<IMultipleService>(); Assert.AreEqual(2, services.Count()); } public interface ILogger { } public class StandardLogger : ILogger { } public interface IService { } public class DefaultService : IService { } public class NonDefaultService : IService { } public interface IMultipleService { } public class Service1 : IMultipleService { } public class Service2 : IMultipleService { } } }

 

Now, there’s a couple of problems here:

  • As I’ve already said, we haven’t dealt with resolution of sub-dependencies.
  • We probably need a syntax for the allInterfaces and allClasses variables.  There’s no way you want that syntax repeated across the world.
  • We might want to create a syntax for common naming patterns as well.

You’ll note that I’ve tried to make the conventions based stuff look similar to ComponentRegistration directly.  There’s also a general hook, like in NHibernate, for intercepting the registration.  Here’s the implementation.  All of the code compiles against Castle Windsor 2.0 RTM.

using System;
using System.Collections.Generic;
using System.Linq;
using Castle.MicroKernel;
using Castle.MicroKernel.Registration;

namespace ColourCoding.ConventionConfigurationProposal
{
    public static class UseConvention {
        public static ConventionRegistration For(IEnumerable<Type> serviceTypes) {
            return new ConventionRegistration(serviceTypes);
        }
    }

    public delegate string NameRule(Type serviceType, Type implementationType);
    public delegate IEnumerable<Type> SelectionRule(Type serviceType, 
IEnumerable<Type> implementationTypeCandidates); public delegate bool SingleSelectionRule(Type serviceType, Type implementationType); public class ConventionRegistration : IRegistration { private IEnumerable<Type> serviceTypes; private IEnumerable<Type> implementationTypes; private Func<Type, Type> findImplementationRule; private NameRule nameRule; private Action<ComponentRegistration<object>> creationRules; private SelectionRule selectionRule; private SingleSelectionRule singleSelectionRule; public ConventionRegistration(IEnumerable<Type> serviceTypes) { this.serviceTypes = serviceTypes; } public IEnumerable<Type> ImplementationTypes { get { return implementationTypes; } } public IEnumerable<Type> ServiceTypes { get { return serviceTypes; } } public void Register(IKernel kernel) { var initialComponents = InitialComponents(); if (nameRule != null) { Apply(initialComponents, c => c.Named(nameRule(c.ServiceType, c.Implementation))); } if (creationRules != null) { Apply(initialComponents, creationRules); } foreach (var component in initialComponents) { kernel.Register(component); } } void Apply(IEnumerable<ComponentRegistration<object>> components,
Action<ComponentRegistration<object>> action) { foreach (var component in components) { action(component); } } IEnumerable<ComponentRegistration<object>> InitialComponents() { if (findImplementationRule == null) { return ComponentsByMatch(); } return ComponentsByFindRule(); } IEnumerable<Type> SingleOnly(Type serviceType,
IEnumerable<Type> implementationTypeCandidates) { if (implementationTypeCandidates.Count() == 1) { return implementationTypeCandidates; } return new Type[0]; } private IEnumerable<ComponentRegistration<object>> ComponentsByMatch() { var actualSelectionRule = selectionRule ?? SingleOnly; var filter = singleSelectionRule ?? new SingleSelectionRule((s, i) => true); // Yes, this code could be faster... return from serviceType in serviceTypes from implementationType in ImplementationTypes where serviceType.IsAssignableFrom(implementationType) && filter(serviceType, implementationType) group implementationType by serviceType into candidates from selectedImplementationType in actualSelectionRule(candidates.Key, candidates) select Component.For(candidates.Key)
.ImplementedBy(selectedImplementationType); } IEnumerable<ComponentRegistration<object>> ComponentsByFindRule() { foreach (var serviceType in ServiceTypes) { var implementation = findImplementationRule(serviceType); if (implementation != null) { yield return Component.For(serviceType)
.ImplementedBy(findImplementationRule(serviceType)); } } } public ConventionRegistration ImplementedBy(Func<Type, Type> findImplementation) { this.findImplementationRule = findImplementation; return this; } public ConventionRegistration ImplementedBy(IEnumerable<Type> implementationTypes) { this.implementationTypes = implementationTypes.Where(t => !t.IsInterface); return this; } public ConventionRegistration With(Action<ComponentRegistration<object>> creationRule) { this.creationRules += creationRule; return this; } public ConventionRegistration Named(NameRule nameRule) { this.nameRule = nameRule; return this; } public ConventionRegistration UsingSelectionRule(SelectionRule selection) { this.selectionRule = selection; return this; } public ConventionRegistration AllowMultiple() { if (selectionRule == null) { selectionRule = (serviceType, candidates) => candidates; } return this; } public ConventionRegistration UsingSelectionRule(SingleSelectionRule selection) { this.singleSelectionRule = selection; return this; } } }

 

Anyway, I hope this is a useful contribution to the discussion. 

Technorati Tags: