starting the robocode robot with an anti-corruption layer

I'm about to get started with my robocode implementation and have a few ideas about what I want the robot to achieve. The first and most important thing I want to concentrate on is the initial set up of code and how I want to interact with the robocode library.

One thing I'm very aware of on recent projects is the way external dependencies influence the core of your system at an early stage. It often seems harmless to use many of the types in those third party libraries when putting those first pieces of code together, especially if that third party is another team in the same organisation. But if left to proliferate this can mean that, in those that grow into large systems, these third party hooks reach right in through all the layers like tentacles into the very heart of what we do and influence our test driven process.

So my very first goal is to separate my model from the robocode layer via an adapter giving me an anti-corruption layer. I'm aware that the robocode library is rarely going to change if at all during the lifetime of my project, but I want this layer so that I can minimise the influence that library may have on my design choices.
I also want to choose the data that filters into my domain model rather than rely on the robocode types and all the rich information on there, especially when I'm initially only going to be using some of it.

Here goes then with the first specification in the TeamSlayer project. Note that I have moved to using Machine.Specifications that give a very clean way of writing context specifications:

    public class when_a_slayer_robot_is_created
    {
        Because of = () => robot = new SlayerRobot();

        It should_be_a_robocode_robot = () =>
            robot.ShouldBeOfType<Robocode.Robot>();

        protected static SlayerRobot robot;
    }

This is setting up the core robot class in an Adapter namespace and ensuring it inherits from the robocode Robot class and I can get this to pass very easily.
I have in mind rolling in an IOC container to resolve all my future dependencies and I'm going to hide this behind a service locator so that the adapter knows very little about dependency resolution. Because the robocode library will create my robot with a default constructor I'm going to need to use property injection and I need to ensure that on construction it begins with a default concrete implementation of the service locator:

    public class when_a_slayer_robot_is_created
    {
        Because of = () => robot = new SlayerRobot();

        It should_be_a_robocode_robot = () =>
            robot.ShouldBeOfType<Robocode.Robot>();

        It should_already_have_a_service_locator_because_robocode_creates_in_a_default_way = 
           () => robot.ServiceLocator.ShouldBeOfType<ServiceLocator>();

        protected static SlayerRobot robot;
    }

Once I get this passing my code looks like this so far:

    public class SlayerRobot : Robot
    {
        public SlayerRobot()
        {
            ServiceLocator = new ServiceLocator();
        }

        public ServiceLocator ServiceLocator { get; set; }
    }

    public class ServiceLocator
    { }

For unit testing purposes, I need to be able to swap functionality in that property so I introduce a specification that allows me to do that:

    public class when_a_service_locator_is_given_to_the_slayer_robot : with_robot_set_up
    {
        Because of = () => robot.ServiceLocator = serviceLocator;

        It should_have_the_service_locator_it_was_given = () =>
            robot.ServiceLocator.ShouldEqual(serviceLocator);
    }

    public class with_robot_set_up
    {
        Establish context = () =>
        {
            serviceLocator = MockRepository.GenerateStub<ICanLocateServices>();
            robot = new SlayerRobot();
        };

        protected static SlayerRobot robot;
        protected static ICanLocateServices serviceLocator;
    }

I have now rolled in a base specification set up class called "with_robot_set_up" (and moved the earlier specification to use that also). I now introduce a new interface ICanLocateServices and inject that into the property in my Because delegate. This requires a few changes to implementation here and there and leaves me with the following code:

namespace TeamSlayer.Services
{
    public class ServiceLocator : ICanLocateServices
    { }

    public interface ICanLocateServices
    { }
}

namespace TeamSlayer.Adapter
{
    public class SlayerRobot : Robot, IRobot
    {
        public SlayerRobot()
        {
            ServiceLocator = new ServiceLocator();
        }

        public ICanLocateServices ServiceLocator { get; set; }
    }
}

So now I'm all wired up I need to now look at what happens when a robot is activated by the robocode library. A virtual method Run() on the robot class is called to enable me to start my processes and issue the first commands for the robot. This seems like a good place to do my initialisation:

    public class when_the_slayer_robot_is_run : with_robot_set_up
    {
        Establish context = () => robot.ServiceLocator = serviceLocator;

        Because of = () => robot.Run();

        It should_ask_the_service_locator_for_a_robot_engine = () =>
            serviceLocator.AssertWasCalled(x => x.ProvideRobotEngine());
    }

I inject a service locator stub into the robot's property in my set up and then call the Run() method. I expect the adapter to ask the service locator for a robot engine which I describe with a new interface ImARobotEngine. The implementation looks like this:

    public class SlayerRobot : Robot
    {
        public SlayerRobot()
        {
            ServiceLocator = new ServiceLocator();
        }

        public ICanLocateServices ServiceLocator { get; set; }        

        public override void Run()
        {
            ImARobotEngine robotEngine = ServiceLocator.ProvideRobotEngine();
        }
    }

I now want the robot to hand control over to my model by asking the robot engine to initialise. I plan on sending the robot itself in the initialisation call, but I do not want this adapter type to leak into my model. So I now want an abstracted ImARobot interface that lives in my own model and describes interactions in my own terms. I go back to the original specification and add to the bottom of that:

    public class when_a_slayer_robot_is_created : with_robot_set_up
    {
        Because of = () => robot = new SlayerRobot();

        It should_already_have_a_service_locator_because_robocode_creates_in_a_default_way 
= () => robot.ServiceLocator.ShouldBeOfType<ServiceLocator>(); It should_be_a_robocode_robot = () => robot.ShouldBeOfType<Robocode.Robot>(); It should_be_a_model_based_robot = () => robot.ShouldBeOfType<ImARobot>(); }

I can now write my specification for this initialisation where the Initialise() method will take in an ImARobot type:

   public class when_the_slayer_robot_is_run : with_robot_set_up
    {
        Establish context = () => 
        {
            robot.ServiceLocator = serviceLocator;

            robotEngine = MockRepository.GenerateStub<ImARobotEngine>();
            serviceLocator.Stub(x => x.ProvideRobotEngine()).Return(robotEngine);
        };

        Because of = () => robot.Run();

        It should_ask_the_service_locator_for_a_robot_engine = () =>
            serviceLocator.AssertWasCalled(x => x.ProvideRobotEngine());

        It should_ask_the_robot_engine_to_initialise_with_itself = () =>
            robotEngine.AssertWasCalled(x => x.Initialise(robot));

        static ImARobotEngine robotEngine;
    }

I now have an anti-corruption layer where I've sorted my code into distinct boundaries and formed a walking skeleton.
Here is the final cut of code for this first piece of work:

(Note that the service locator purposefully returns null for the time being and causes a null reference exception when the Robot is run. This is so I can observe what happens when a robot does not function correctly and I'll be covering this in one of the next few posts.)

namespace TeamSlayer.Adapter
{
    public class SlayerRobot : Robot, ImARobot
    {
        public SlayerRobot()
        {
            ServiceLocator = new ServiceLocator();
        }

        public ICanLocateServices ServiceLocator { get; set; }        

        public override void Run()
        {
            ImARobotEngine robotEngine = ServiceLocator.ProvideRobotEngine();
            robotEngine.Initialise(this);
        }
    }
}

namespace TeamSlayer.Service
{
    public class ServiceLocator : ICanLocateServices
    {
        public ImARobotEngine ProvideRobotEngine()
        {
            return null;
        }
    }

    public interface ICanLocateServices
    {
        ImARobotEngine ProvideRobotEngine();
    }
}

namespace TeamSlayer.Model
{
    public interface ImARobot
    {
    }

    public interface ImARobotEngine
    {
        void Initialise(ImARobot robot);
    }
}



4 comments:

  1. Pavel Ĺ avara July 8, 2010 at 11:12 PM

    Hi Justin, you have my full attention now :-)
    I'm really interested how far you could get with MSpec. Does robocode security stand in your way when using it ?

     
  2. Justin Davies July 9, 2010 at 5:59 PM

    Hi Pavel, thanks for your comment. I think robocode is an extremely interesting environment to try new stuff out in and there are many more posts on their way about acceptance testing from the outside in and then moving onto modelling in a bounded context.

    To answer your question, I have a separate mspec test assembly for executing the specifications and acceptance. All my specifications are particular to my own model and acceptance is always done through the console runner end-to-end.

    However I do have autofac as an IOC container in the robot assembly itself and found that it has to be deployed to the GAC but otherwise works fine so far. Hope to continue conversations in the future. Justin.

     
  3. Flemming N. Larsen July 9, 2010 at 10:27 PM

    I really enjoy this article, and learned something new, which is how to actually implement an anti-corruption layer.
    Like Pavel, I look forward to see new articles from you. :-)

     
  4. Justin Davies July 10, 2010 at 9:58 AM

    Hi Flemming, thanks for the comment and glad you learnt something from it.

    What is interesting about this situation is that the flow of the system actually initiates in the outer boundary and comes into my model unlike "usual" development where your own GUI drives calls out to a service boundary.
    So requires some thought.

    Keep up the good robcode work guys.
    Justin.