Fluent NHibernate: A great project


I have to admit, I stopped lurking to the Fluent NHibernate mailing list a while ago. Ironically, it wasn't because I thought nothing was going on, simply that the sheer volume of emails was producing massive amounts of noise in my inbox.  The unfortunate aspect of this is that often with .NET projects, there's very little evidence of life outside of their mailing lists (for example, a lot of activity on the Castle project is focusing on Mono and Silverlight, try finding that information on their web site).

Now, I know from personal experience just quite how painful it is to produce a patch for an open source project, never mind actually maintain one.  The people doing this are typically doing it because they find the end product useful in its own right.  End users, who don't contribute to the improvement of the project, are necessarily second class citizens in amateur open source projects.

Now, take a look at this bug report and comments #4 and #5.  #4 was me in a pretty bad mood, I'd just lost a day trying to work out what was going on.  #5 is a completely reasonable response from someone who has put a lot of work in to the project.  It does two things

  • It explains the current priorities and why the issue is not fixed.
  • It reassures the end user that his concerns are appreciated.

As I say, no-one forces people to work on amateur open source projects, and trawling through bug lists is definitely pretty far down most developers list of favourite things to do.  That's what I mean when I say that FH is a great project.  (The code's quite useful too...)

The aspect that I haven't mentioned is that projects that don't take end users seriously tend to wither, if only because they run out of new contributors.  Whereas I'm not qualified to help with the code, I can produce a decent bug report.  I'm going to go do that right now...

Technorati Tags:

author: Julian Birch | posted @ Thursday, June 18, 2009 3:56 PM | Feedback (0)

Doing a self join with Fluent NHibnerate AutoMapping


This is definitely in the category of posts "write it down before you stop understanding the solution".  Let's say that you're trying to track your servers.  Servers are related to other servers.  So, for instance, a live server has a corresponding DR server and a corresponding UAT server.  Let's say also you have a simple table with two foreign key references that implements this relationship.  You can implement this in fairly short order:

mapping.HasManyToMany(s => s.RelatedSites)
    .Cascade.All()
    .WithTableName("RelatedSite")
    .WithChildKeyColumn("RelatedSiteId")
    .WithParentKeyColumn("LiveSiteId")
    .Inverse()
    .FetchType.Join();
mapping.IgnoreProperty(s => s.RelatedSites);

The only problem with this is that it doesn't work.  Now, if you automatically export your mapping files (and you should) you'll see that it's ignoring the key columns you specify.  The automatic configuration is doing its own thing.

The problem is that you also added a foreign key convention by using the helper.  Take a look at the code that implements this.  You'll see that the Accept code looks a bit dodgy when you compare it to the Apply method.  Instead, you can try the following code:

class ForeignKey : ForeignKeyConvention, IRelationshipConvention
{
    private readonly Func<PropertyInfo, Type, string> standardRule;

    public ForeignKey(Func<PropertyInfo, Type, string> standardRule)
    {
        this.standardRule = standardRule;
    }

    protected override string GetKeyName(PropertyInfo property, Type type)
    {
        return standardRule(property, type);
    }

    public bool Accept(IRelationship target)
    {
        if (target is IManyToManyPart) return base.Accept((IManyToManyPart)target);
        return true;
    }

    public void Apply(IRelationship target)
    {
        if (target is IOneToManyPart) base.Apply((IOneToManyPart)target);
        if (target is IManyToManyPart) base.Apply((IManyToManyPart)target);
        if (target is IManyToOnePart) base.Apply((IManyToOnePart)target);
    }
}

This code fixes the problem.

Anatomy of a Bug

This is a fix that raises as many questions as it answers.  One is why Accept still doesn't do the obvious thing, the other is why I'm not going to submit this as a patch.  The second is pretty easy to answer: this is nothing like a solution to the underlying problem.  The first takes a bit longer...

If you make Accept do the obvious thing, it stops applying your foreign key rules anywhere.  The problem is that the rules for one to many and many to many check to see if they've had their keys set already.  And they already have, by the factory foreign key convention.  So the user rule does the wrong thing in order to make up for that.  So two wrongs nearly make a right, until you introduce a many to many part.  The fix I've described above fixes it for me, but it's far from obvious that the underlying problem is fixable.

This is the second time I've found the factory conventions broke my code through no fault of my own (the other is the well documented, but still present, nullable enum bug) and I have to admit that I'm starting to wonder if the conventions code is more trouble than it's worth.  Certainly the interaction between the unremovable factory conventions and your own conventions is at best hard to understand and at worst an unsolvable problem.  Well, I'm still learning...

Technorati Tags:

author: Julian Birch | posted @ Friday, June 12, 2009 5:16 PM | Feedback (0)

AutoGen: Extending the Convention Approach to Event Wiring


I couldn't quite leave this code alone.  I have to admit, the whole reason I wrote the AutoGen stuff in the first place was out of frustration at the amount of plumbing factories I was having to write in a recent project.  Well, the plumbing of event wiring was getting at me as well, so I decided to do something about that as well.

Let me explain the problem I wanted to solve, first.  I recently had to modify a program that took one fix connection to handle one or two, depending on the deployment scenario.  Now, obviously, handling forty is pretty much like handling two from an API perspective, but going from one to two tends to involve refactoring.  The annoyance was, in this particular case, it was absolutely obvious what I wanted to do.  I wanted both to open, close and reset at the same time.  This was done through the obvious interface.

interface IFixConnection {
    void Open();
    void Close();
    void Reset();
}

Now, here, there's no complex API.  It's just a series of commands.  All I want is for it to pass the commands on to the implementors of the interface.  So, I wanted to be able to write.

container.Register(Component.For<IFixConnection>().Named("AggregateConnection").AutoWire("principalConnection", "controlConnection"));

and for it to "just work".  Actually, I wanted to go further than that.  Large parts of my system need to know when a reset occurs, but I didn't have to have to wire them up by hand.  So actually the interface looked like this:

internal interface IResettable {
    void Reset();
}
interface IFixConnection2 : IResettable {
    void Open();
    void Close();
}

Where quite a lot of objects implemented IResettable.  Now, I wanted the following:

  • The controller object shouldn't need to know that some people only implemented part of the interface.
  • I shouldn't have to wire up the objects to the AutoWire aggregate by hand.
  • It should keep track of creation and release.

So, I added in the following syntax:

container.Register(Component.For<IFixConnection2>().Named("AggregateConnection").AutoWire(s => s.WithCurrentObjects().UseWeakReferences()));

This supports the above behaviour.  It also has the advantage of requiring the minimum rewiring for different deployment scenarios.  The linkage works on the basis of whether or not the interface is implemented, not whether it is exposed.  This is a deliberate design decision to sacrifice a small amount of control for ease of use.  It certainly didn't simplify the implementation.  Finally, the aggregate object exposes an IBroadcaster interface, defined as follows:

public interface IBroadcaster {
    void Add(object listener);
    bool Remove(object listener);
    void Clear();
    IEnumerable<object> GetListeners();
}

which can be used to dynamically modify the list at run-time.  It also implements a chain of responsibility pattern if the target interface method returns bool or a class.  Finally, it's worth noting that the AutoWire does the opposite of AutoGen when presented with a Dispose method.  A dispose method clears the listeners, it doesn't dispose of the listeners.  This is a special rule, but I think a useful one.  Anyone who really did want to dispose of the listeners can always get the list through GetListeners.

Anyway, it's on the patch list in donjon.

Technorati Tags: ,

author: Julian Birch | posted @ Sunday, June 07, 2009 5:30 PM | Feedback (0)

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:

author: Julian Birch | posted @ Friday, May 29, 2009 5:30 PM | Feedback (0)

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:\ruby\bin folder onto your path:  set path=c:\ruby\bin;%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>>:<<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:

author: Julian Birch | posted @ Friday, May 15, 2009 2:07 PM | Feedback (1)

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:

author: Julian Birch | posted @ Friday, May 08, 2009 1:26 PM | Feedback (0)

Why containers shouldn't auto mock


I was recently reading a fairly detailed comparison by Andrey Shchekin of the various injection containers out there in the .NET space.  I found it interesting that the last feature he regarded as important was the ability to tell the container to mock a particular object. 

Now, when I wrote an article about evaluating IoC containers, it didn't even occur to me to include this.  Now, Andrey includes many points that I haven't covered, like the fact that Castle hasn't had a stable release in well over a year, but on this point I think it's best left out.

Why?  Because I think that using the feature is a code smell.  Certainly, I've injected mocks into containers before today (StructureMap 1.0 with old school Rhino Mocks, not a thing of beauty) but frankly, it was because I didn't understand how to use containers as well as I do now.

So, how should you use them?  You should inject your dependencies into the constructor, period.  There's times you need setter injection (configuring Retlang, sadly, falls into this category, so does most of Microsoft's stuff), but I wouldn't recommend designing that way, it vastly complicates your analysis.

You might want to dynamically reference components, but that's why I wrote AutoGen.  (As a way to tell if someone feels strongly about something, see how many weekends he's spent on it.)  Even if you don't have something like this, you still ought to be using your own builder interface and passing that in.  At the very least, you're explicitly documenting your actual interaction with the container.

So why should you be injecting mocks into your container?  Well, the only reason I can think for doing this is if you're referring to your container statically.  You shouldn't be doing this, for any number of reasons.  It doesn't help that most of the StructureMap examples still use static paradigm, but it's not actually a recommended method of doing it.

In short, if you are looking for the ability to inject mocks into a container, I would recommend you take a look at how you interact with the container in the first place.  Chances are you've actually managed to use the container in a style that containers are meant to discourage.

author: Julian Birch | posted @ Sunday, April 26, 2009 8:19 PM | Feedback (1)

Still making a patch for Castle Windsor


So, my patch got rejected for a variety of good reasons.  One is the standard problem of not having checked everything: I spent so much time making sure that the nant build worked I missed that I hadn't included the relevant csproj files.  Of course, I have any number of problems getting castle to build at the best of times, but I've started to regard this as a constant of the universe.

Another is the Castle coding standards.  Now, trivial coding standards are important, by which I mean that whilst it doesn't matter if tabs are two or four spaces, it definitely matters that the entire code base applies the same convention.  Jeff wrote more eloquently than I can manage on the subject, so go read what he has to say rather than let me labour the point.  What i will say is that I am heartily tired of all of the arguments I've witnessed (and to my shame, sometimes participated in) on the subject of code formatting.  Castle uses a couple of conventions I don't usually, mostly because it's more convenient for me to use different ones, but these things are easily fixable with some hack coding.  More seriously was the lack of any documentation other than the tests, so I went back and fixed that up.

Then there's the question of the Common Service Locator code.  I mean, it's all very well that AutoGen passes Ayende's tests, but the CSL is an MS-PL codebase, and Castle is Apache.  It's not clear that you can even include the assemblies.  This is a conceptual minefield at the least.  So, I've removed the CSL dependency and rewritten the tests.  This may not be the end of the matter.

Anyway, here's the code to hack source files into Castle compliance.  Not elegant, but it gets the job done.

namespace CastleStandard {
    using System;
    using System.Collections.Generic;
    using System.IO;

    class Standardizer {

        private string licenseHeader =
@"// Copyright 2004-2009 Castle Project - http://www.castleproject.org/
// 
// Licensed under the Apache License, Version 2.0 (the ""License"");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an ""AS IS"" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.";
        private int tabSize = 4;

        public string Standardize(string code) {
            var result = InternalStandardize(code);
            if (result != InternalStandardize(result)) {
                throw new Exception("Standardization code was not idempotent.");
            }
            return result;
        }
        string InternalStandardize(string code) {
            var reader = new StringReader(code);
            var licenseReader = new StringReader(licenseHeader);
            var usingStatements = new List<string>();
            var writer = new StringWriter();
            string codeLine = null;
            bool isLicensePresent = true;
            foreach (var licenseLine in Lines(licenseReader)) {
                if (isLicensePresent) {
                    codeLine = reader.ReadLine();
                }
                writer.WriteLine(licenseLine);
                isLicensePresent = codeLine == licenseLine;
            }
            // If the license wasn't present, we have a line we need to push back onto the reader
            var lines = isLicensePresent
                ? Lines(reader)
                : Prepend(codeLine, Lines(reader)); 
            bool hasEncounteredNamespace = false;
            foreach (var sourceLine in lines) {
                if (usingStatements != null) {
                    usingStatements = ProcessUsingStatements(writer, usingStatements, ref hasEncounteredNamespace, sourceLine);
                } else {
                    writer.WriteLine(Tabify(sourceLine));
                }
            }
            if (usingStatements != null) {
                throw new Exception("Using statements were never dealt with.");
            }
            return writer.ToString();
        }

        private List<string> ProcessUsingStatements(TextWriter writer, List<string> usingStatements, ref bool hasEncounteredNamespace, string sourceLine) {
            if (!hasEncounteredNamespace && sourceLine.StartsWith("using")) {
                usingStatements.Add(sourceLine);
            } else if (sourceLine.StartsWith("namespace")) {
                hasEncounteredNamespace = true;
                if (!sourceLine.Contains("{")) {
                    writer.WriteLine(Tabify(sourceLine));
                }
            }
            if (hasEncounteredNamespace && sourceLine.Contains("{")) {
                writer.WriteLine(sourceLine);
                if (usingStatements.Count > 0) {
                    foreach (var usingLine in usingStatements) {
                        writer.WriteLine("\t" + usingLine);
                    }
                    writer.WriteLine();
                }
                usingStatements = null;
            }
            return usingStatements;
        }

        IEnumerable<T> Prepend<T>(T value, IEnumerable<T> list) {
            yield return value;
            foreach (var t in list) {
                yield return t;
            }
        }

        string Tabify(string line) {
            int spaces = 0;
            int chars = 0;
            foreach (char c in line) {
                if (c == '\t') {
                    chars++;
                    spaces += tabSize;
                } else if (c == ' ') {
                    chars++;
                    spaces++;
                } else {
                    break;
                }
            }
            return string.Concat(
                new string('\t', spaces / tabSize),
                new string(' ', spaces % tabSize),
                line.Substring(chars)
                );
        }

        IEnumerable<string> Lines(TextReader reader) {
            string line;
            while (null != (line = reader.ReadLine())) {
                yield return line;
            }
        }

    }
}

 

author: Julian Birch | posted @ Sunday, April 26, 2009 7:04 PM | Feedback (0)

Getting the NServiceBus Distributor Working


All of this assumes that you've built NServiceBus in line with what I said on the previous post.  This is just enough to modify the FullDuplex sample to load balance.  Most of this has previously appeared on the yahoo groups, but I've just organized it for comprehension.

Step 1: Modify the configs

Amazingly, all you need to do is to modify the configs of the samples.  Here's what you need to do.

Client

c:\NSB\Samples\FullDuplex\Client\bin\debug\Client.exe.config

<UnicastBusConfig DistributorControlAddress="" DistributorDataAddress="">
<
MessageEndpointMappings>
<
add Messages="Messages" Endpoint="distributordatabus" />
</
MessageEndpointMappings>
</
UnicastBusConfig>

Distributor

c:\NSB\src\distributor\NServiceBus.Unicast.Distributor.Runner\bin\Debug\NServiceBus.Unicast.Distributor.Runner.exe.config

Requires no changes.  It just works.  (!)

Server 1

C:\NSB\Samples\FullDuplex\Server\bin\Debug\Server.exe.config

<MsmqTransportConfig
InputQueue="server1messagebus"
ErrorQueue="error"
NumberOfWorkerThreads="1"
MaxRetries="5"
/>

<
UnicastBusConfig DistributorControlAddress="distributorcontrolbus" DistributorDataAddress="distributordatabus">
<
MessageEndpointMappings>
<
add Messages="Messages" Endpoint="IGNORE" />
</
MessageEndpointMappings>
</
UnicastBusConfig>

Obviously, Endpoint "IGNORE" is never used.  I'm just setting this to emphasize that messages from the server don't use this setting in this scenario.

Server 2

Go to C:\NSB\Samples\FullDuplex\Server\bin\Debug\

Copy the entire folder to C:\NSB\Samples\FullDuplex\Server\bin\Debug2\

Change C:\NSB\Samples\FullDuplex\Server\bin\Debug2\Server.exe.config

<MsmqTransportConfig
InputQueue="server2messagebus"
ErrorQueue="error"
NumberOfWorkerThreads="1"
MaxRetries="5"
/>

<
UnicastBusConfig DistributorControlAddress="distributorcontrolbus" DistributorDataAddress="distributordatabus">
<
MessageEndpointMappings>
<
add Messages="Messages" Endpoint="IGNORE" />
</
MessageEndpointMappings>
</
UnicastBusConfig>

You need to create a full copy because of the way the SpringBuilder operates.  Two servers in the same folder will fail, with an extremely unhelpful error message (Object Variable not Set, to be precise.).

Step 2: Run the system

To run it:

  • Open up four command windows
  • C:\NSB\src\distributor\NServiceBus.Unicast.Distributor.Runner\bin\Debug\NServiceBus.Unicast.Distributor.Runner.exe
  • c:\NSB\Samples\FullDuplex\Server\bin\Debug\Server.exe
  • c:\NSB\Samples\FullDuplex\Server\bin\Debug2\Server.exe
  • c:\NSB\Samples\FullDuplex\Client\bin\Debug\Client.exe

You can now start servers, stop servers, watch messages queue up and recover, watch it load balance between the two. 

Step 3: See what it's doing

If you start running parts of this system through the debugger, you'll want to switch off some exceptions fairly quickly, since they happen all the time.

  • System.Messaging.MessageQueueException:  every time it reads from an empty queue
  • System.BadImageFormatException:  when it tries to read a config file as an assembly.  (really)

author: Julian Birch | posted @ Wednesday, April 22, 2009 4:47 PM | Feedback (0)

Building NServiceBus 1.9 RTM


They've gone to a lot of effort to make nServiceBus easy to get working, but it still takes about half an hour with the readme.  A couple of notes that should save you some time:

  • Relatively obviously, you've got to put MSBuild on the path.  This will usually fix that:  set path=C:\WINDOWS\Microsoft.NET\Framework\v3.5;%path%
  • Build_Src.bat doesn't put anything into the build/output directory, which is unfortunate since build_samples.bat expects it to.  build_with_strong_name.bat does, however.
  • For some reason, my box didn't trust nServiceBus.  This gives you the (in)famous "The Project Location is Not Trusted" dialog box.  The following command fixed it:  caspol -machine -addgroup All_Code -url file://c:/nsb/* FullTrust -name nServiceBus  (note that you need to have set the path for this to work)
  • After building nServiceBus, you can finally use the solution file.
Technorati Tags:

author: Julian Birch | posted @ Wednesday, April 22, 2009 10:05 AM | Feedback (0)