Playing with software architecture in a Space Opera

In 2030 a massive project were kicked-off in order to colonize new planets.
The name of the project was International Space Program.

A lot of planets had been probed looking for the best candidate of the first mankind settlement in the space.
At the end Mars won and a bunch of settlers were chosen to colonize it.

The main reason for the colonization of space was the research and the harvesting of an important resource missing on Earth: “Whatsoever” (on periodic element table: WH)

What you could achieve using the “Whatsoever” was limitless, so a lot of Companies invested on the International Space Program.

The settlers were sent on Mars in 2032 and an interspace link granted the quick send of designs, prototypes useful to build objects on Mars.

The name of this link was: New Universal Gate for Extraterrestrial Transport or N.U.G.E.T for short.

planets-wrong-headquarter

The probes found three possible areas on Mars where to find the WH and the Waterium and Foodorium needful for the survival of the colonies.

planets-wrong-base

A first base were created by the Settlers, but after a short time they decided to split and search for the WH in different places in order to increment the chance, at the same time they searched for Foodorium and Waterium.

planets-wrong-bases

Using the resources provided by the Earth via the N.U.G.E.T they started to create harvesters and silos where to deposit the elements.

planets-wrong-silos

The Earth owned the knowledge and the information gathered by the probes sent in the past years and the Bases needed to access them any time; at the same time the Bases could provide new information about the direct exploration of the planet.
The N.U.G.E.T system wasn’t a good candidate to transport this kind of live data, because only things inert and compressed could be transferred safely.
So on the Earth project they built a Communicator schema that satisfied them and it was sent through the N.U.G.E.T. so the Bases could use it to build their systems.
In a short time Earth and every single Base had a Communicator device up and running.

planets-wrong-communicator

Meantime Foodorium and Waterium were found only in the Valley, the WH was in huge quantities only in the Undergrounds, using the Communicators the Bases were able to update the Earth’s information about the Mars topology.
Every Base started to collect the Resources using the Harvesters.

planets-wrong-robots

For transmitting the Resources it wasn’t possible to use the N.U.G.E.T again, so every Base had to build a Transporter system to send the WH to the Earth every time there was enough in the Silos.

planets-wrong-final

Apparently everything seemed on track, but a lot of problems showed up in a very short time, the Robot schemas provided by the Earth and the prototypes were inefficient in the real situation, the displacement system, for instance, needed to be tuned a lot of times.
In general all systems provided and implemented by the Bases required a lot of tuning, so everytime the Earth sent new prototypes and designs all Bases had to update their systems and had tuning again their custom components.
Very quickly the settlers had to work to keep every single Base updated, up and running instead of gathering efficiently the WH and send it to the Earth.

The main investors of the International Space Program, initially keen to launch parallel colonies in other planets given the amazing result in the short term, decided to cut off the investments and the Program was put on stand-by.

After few time every Base shut down, the settlers were called back on Earth and the Mars Project died.

After few months a post mortem process were started, the general idea was considered good and promising so the investors were available to invest again, once the Company responsible of the Program had found and sorted out the problems happened in the first mission.

In 2038 a new Mars Project was in go.
A new packages of designs and prototypes was ready and was sent through the N.U.G.E.T.

shared-assets-earth

The settlers decided to start designing the Robots.
In the first project the Earth provided a prototype with a lot of functionality, most of them useless and few of them not good enough to face the real situation, so this time the Earth provided a simple design clarifying how to power and activate them.

//N.U.G.E.T.
public interface IRobot
{
	int Fuel { get; set;}
	void Activate();
}

The settlers decided to clarify in an another schema how a MarsRobot had to be built on top of the schema provided by the Earth: this time the Mars Colony took care immediately of the displacement system.

public interface IMarsRobot:IRobot
{
	void MoveTo(LocationMars location);
}

Then they provided a base Prototype to build a lot of different kind of Robot’s prototypes useful on the planet.

public abstract class MarsRobotBase : IMarsRobot
{
	public LocationMars CurrentPosition { get; private set; }

	protected MarsRobotBase()
	{
		Fuel = 10;
	}

	public void MoveTo(LocationMars location)
	{
		CurrentPosition = location;
	}

	public abstract void Activate();

	public int Fuel { get; set; }
}

In the first project every Base built the robots, this time the Settlers decided to create a common infastructure that the Bases could use to manage the Robots.

public interface IMarsRobotFacilityFacade
{
	void CreateRobot<T>() where T : MarsRobotBase, new();
	void MoveRobots(LocationMars from, LocationMars to, int max = -1);
	void ActivateRobots();
	void LoadRobotResource();
	List<MarsRobotBase> Robots { get; }
}

At the same time they defined a HarvestRobot

public sealed class HarvestRobot:MarsRobotBase
{
	public IList<ResourceBase> Resources { get; private set; }
	public override LocationMars CurrentPosition { get; protected set; }

	public HarvestRobot()
	{
		Resources = new List<ResourceBase>();
		CurrentPosition = LocationMars.RobotFacilityFacade;
	}
		
	public override void Activate()
	{
		switch (CurrentPosition)
		{
			case LocationMars.RobotFacilityFacade:
					break;
			case LocationMars.Valley:
				Harvest<Foodorium>();
				Harvest<Waterium>();
				break;
			case LocationMars.Mountains:
				break;
			case LocationMars.Undergrounds:
				Harvest<Whatsoever>();
				break;
			default:
				throw new ArgumentOutOfRangeException();
		}
	}
	
	public IEnumerable<ResourceBase> DownloadResources()
	{
		if (CurrentPosition != LocationMars.RobotFacilityFacade)
		{
			throw new RobotNotInTheCorrectLocation();
		}
		var dump = new List<ResourceBase>();
		dump.AddRange(Resources);
		Resources.Clear();
		return dump ;
	}

	private void Harvest<T>() where T : ResourceBase, new()
	{
		if (typeof(T) == typeof(Waterium) && CurrentPosition != LocationMars.Valley)
		{
			throw new RobotNotInTheCorrectLocation();
		}
		if (typeof(T) == typeof(Foodorium) && CurrentPosition != LocationMars.Valley)
		{
			throw new RobotNotInTheCorrectLocation();
		}

		if (typeof(T) == typeof(Whatsoever) && CurrentPosition != LocationMars.Undergrounds)
		{
			throw new RobotNotInTheCorrectLocation();
		}

		Resources.Add(new T());
	}
}

The Harvester could harvest any kind of ResourceBase following the specific received by the Earth, but the harvest was possible only in the specific location where the requested resource was available.
At the same time the download of the resources had to be managed by the MarsRobotFacilityFacade, so the Harvester had to be there to download correctly its load.

//N.U.G.E.T.
public abstract class ResourceBase
{
	public abstract ResourceKind Kind { get; }
}
//N.U.G.E.T.
public class Waterium : ResourceBase
{
	public override ResourceKind Kind
	{
		get { return ResourceKind.Waterium; }
	}
}
//N.U.G.E.T.
public class Foodorium: ResourceBase
{
	public override ResourceKind Kind
	{
		get { return ResourceKind.Foodorium; }
	}
}
//N.U.G.E.T.
public class Whatsoever: ResourceBase
{
	public override ResourceKind Kind
	{
		get { return ResourceKind.Whatsoever; }
	}
}
//N.U.G.E.T.
public enum ResourceKind
{
	Waterium,
	Foodorium,
	Whatsoever
}

The idea behind the MarsRobotFacilityFacade was to centralize not only the build of the new robots, but also give to the Bases an infrastructure to control all of them very easily.

The MarsRobotoFacilityFacade got two dependencies to make its job properly: a IMarsRobotFactory responsible of the creation of the new robots and a IMarsResourceSilo where to address specifically the harvesters to deposit the resources.

//N.U.G.E.T.
//in File IResourceSilo.cs
public interface IResourceSilo
{
	void Store(ResourceBase resource);
	void StoreAll(IEnumerable<ResourceBase> resource);
	T Get<T>() where T : ResourceBase;
	IEnumerable<ResourceBase> GetAll();
	int Count();
}

//MarsProject
//in File IMarsResourceSilo.cs
public interface IMarsResourceSilo: IResourceSilo
{
}

//MarsProject
//in File MarsResourceSilo.cs
public class MarsResourceSilo : IMarsResourceSilo
{
	private readonly List<ResourceBase> _resources;

	public MarsResourceSilo()
	{
		_resources = new List<ResourceBase>();
	}
	public void Store(ResourceBase resource)
	{
		_resources.Add(resource);
	}

	public void StoreAll(IEnumerable<ResourceBase> resource)
	{
		_resources.AddRange(resource);
	}

	public T Get<T>() where T : ResourceBase
	{
		var resource = _resources.FirstOrDefault(r => r.GetType() == typeof(T)) as T;
		if (resource != null)
		{
			_resources.Remove(resource);
		}
		return resource;
	}

	public IEnumerable<ResourceBase> GetAll()
	{
		var dump = new List<ResourceBase>();
		dump.AddRange(_resources);

		_resources.Clear();
		return dump;
	}

	public int Count()
	{
		return _resources.Count;
	}
}

In this way a Base was composed by a Silo, a Robot’s Factory and a Robot’s Factory’s Facade to build and control the new robots easily.
Operating in this way and separating the responsibilities between different specialized structures in case of changes was necessary update for sure only the specific structure minimizing the maintenance/updating cost for the other structures.

To be sure that everything worked as expected, mostly the integration between the many components, a few Settlers were in charge to create Test Camps where all the infrastructure had to be tested BEFORE to be released.

Three Camps were created at the beginning: ResourceRepositoryTest, HarvestRobotTest, MarsRobotFacilityFacadeIntegrationTest

[TestFixture]
public class ResourceRepositoryTest
{
	[Test]
	public void It_should_be_possible_store_and_get_resources()
	{
		var target = new MarsResourceSilo();
		target.Store(new Foodorium());
		target.Store( new Waterium());
		target.Store(new Whatsoever());

		Assert.AreEqual(3, target.Count());

		var waterium = target.Get<Waterium>();

		Assert.IsInstanceOf<Waterium>(waterium);
		Assert.IsNotNull(waterium);
		Assert.AreEqual(ResourceKind.Waterium, waterium.Kind);
		Assert.IsNotNull(waterium);
		Assert.AreEqual(2, target.Count());

		var waterium2 = target.Get<Waterium>();
		Assert.IsNull(waterium2);
		Assert.AreEqual(2, target.Count());

		target.GetAll();
		Assert.AreEqual(0, target.Count());
	}
}
[TestFixture]
public class HarvestRobotTest
{
	[Test]
	public void It_should_be_possible_harvest_a_resource()
	{
		var target = new HarvestRobot();
		target.MoveTo(LocationMars.Valley);
		target.Activate();
		Assert.AreEqual(2, target.Resources.Count);
	}

	[Test]
	public void It_should_be_possible_harvesting_a_waterium_or_foodorium_when_you_are_on_valley()
	{
		var target = new HarvestRobot();
		target.Activate();
		Assert.AreEqual(0, target.Resources.Count);
	
		target.MoveTo(LocationMars.Valley);
		target.Activate();
		Assert.AreEqual(2, target.Resources.Count);
	}

	[Test]
	public void It_should_be_possible_harvesting_a_titanium_when_you_are_on_mines()
	{
		var target = new HarvestRobot();
			
		target.MoveTo(LocationMars.Undergrounds);
		target.Activate();
		Assert.AreEqual(1, target.Resources.Count);
		Assert.AreEqual(typeof(Whatsoever), target.Resources[0].GetType());
	}

	[Test]
	public void It_should_be_possible_to_dowlonad_resources_to_repository_only_when_in_the_robot_facility()
	{
		var target = new HarvestRobot();
		target.MoveTo(LocationMars.Valley);
		target.Activate();
		Assert.AreEqual(2, target.Resources.Count);

		target.MoveTo(LocationMars.RobotFacilityFacade);
		target.DownloadResources();
		Assert.AreEqual(0, target.Resources.Count);
	}
}
[TestFixture]
public class MarsRobotFacilityFacadeIntegrationTest
{
	private MarsResourceSilo _marsResourceSilo;
	private MarsRobotFactory _marsRobotFactory;
	private MarsRobotFacilityFacade _target;

	private void CreateRobots(int number = 1)
	{
		for (var i = 0; i < number; i++)
		{
			_target.CreateRobot<HarvestRobot>();
		}
	}

	[SetUp]
	public void SetUp()
	{
		_marsResourceSilo = new MarsResourceSilo();
		_marsRobotFactory = new MarsRobotFactory();
		_target = new MarsRobotFacilityFacade(_marsResourceSilo, _marsRobotFactory);

	}

	[Test]
	public void It_should_be_possible_create_a_robot()
	{
		Assert.AreEqual(0, _target.Robots.Count);
		CreateRobots();
		Assert.AreEqual(1, _target.Robots.Count);
	}

	[Test]
	public void It_should_be_possible_to_move_all_robots_in_a_place_to_another_place()
	{
		CreateRobots(3);

		_target.Robots.ForEach(r => Assert.AreEqual(LocationMars.RobotFacilityFacade, r.CurrentPosition));
		_target.MoveRobots(LocationMars.RobotFacilityFacade, LocationMars.Valley);
		_target.Robots.ForEach(r => Assert.AreEqual(LocationMars.Valley, r.CurrentPosition));
	}

	[Test]
	public void It_should_be_possible_to_move_a_specified_number_of_robots_in_a_place_to_another_place()
	{
		CreateRobots(3);

		_target.Robots.ForEach(r => Assert.AreEqual(LocationMars.RobotFacilityFacade, r.CurrentPosition));
		_target.MoveRobots(LocationMars.RobotFacilityFacade, LocationMars.Valley, 2);
		Assert.AreEqual(2, _target.Robots.Where(r => r.CurrentPosition == LocationMars.Valley).ToList().Count);
		Assert.AreEqual(1, _target.Robots.Where(r => r.CurrentPosition == LocationMars.RobotFacilityFacade).ToList().Count);
	}

	[Test]
	public void It_should_throw_an_exception_try_to_move_an_unavailable_number_of_robots()
	{
		CreateRobots(3);

		_target.Robots.ForEach(r => Assert.AreEqual(LocationMars.RobotFacilityFacade, r.CurrentPosition));
		Assert.Throws<OverflowException>(() => _target.MoveRobots(LocationMars.RobotFacilityFacade, LocationMars.Valley, 4));
		_target.MoveRobots(LocationMars.RobotFacilityFacade, LocationMars.Valley, 2);
		Assert.Throws<OverflowException>(() => _target.MoveRobots(LocationMars.RobotFacilityFacade, LocationMars.Valley, 2));
	}

	[Test]
	public void It_should_be_possible_activate_the_robots()
	{
		CreateRobots(3);

		_target.MoveRobots(LocationMars.RobotFacilityFacade, LocationMars.Valley);
		_target.Robots.ForEach(r => Assert.AreEqual(0, ((HarvestRobot)r).Resources.Count));
		_target.ActivateRobots();
		_target.Robots.ForEach(r => Assert.AreEqual(2, ((HarvestRobot)r).Resources.Count));
		_target.MoveRobots(LocationMars.Valley, LocationMars.RobotFacilityFacade);
		_target.LoadRobotResource();
		_target.Robots.ForEach(r => Assert.AreEqual(0, ((HarvestRobot)r).Resources.Count));
	}

	[Test]
	public void It_should_be_possible_call_back_harvester_to_download_their_resources()
	{
		CreateRobots(3);

		Assert.AreEqual(0, _marsResourceSilo.Count());
		_target.MoveRobots(LocationMars.R.obotFacilityFacade, LocationMars.Valley);
		_target.ActivateRobots();
		_target.LoadRobotResource();
		Assert.AreEqual(6, _marsResourceSilo.Count());
		_target.Robots.ForEach(r => Assert.AreEqual(0, ((HarvestRobot)r).Resources.Count));
	}
}

After a successfull test session the Bases could use the prototypes to create the new structures.
test-first-run

planets-right-headquarter2

Now that the harvester could be built again and the Foodorium and Waterium could be harvested again, the second colonization of Mars could begin!

To be continued…

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s