Specific Interfaces

08 Jun 2014

While writing my CruiseCli project, I needed to do some data storage, and so used my standard method of filesystem access, the IFileSystem. This is an interface and implementation which I tend to copy from project to project, and use as is. The interface looks like the following:

public interface IFileSystem
{
	bool FileExists(string path);
	void WriteFile(string path, Stream contents);
	void AppendFile(string path, Stream contents);
	Stream ReadFile(string path);
	void DeleteFile(string path);

	bool DirectoryExists(string path);
	void CreateDirectory(string path);
	IEnumerable<string> ListDirectory(string path);
	void DeleteDirectory(string path);
}

And the standard implementation looks like the following:

public class FileSystem : IFileSystem
{
	public bool FileExists(string path)
	{
		return File.Exists(path);
	}

	public void WriteFile(string path, Stream contents)
	{
		using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write))
		{
			contents.CopyTo(fs);
		}
	}

	public Stream ReadFile(string path)
	{
		return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
	}
	//snip...
}

This (I think) is a very good solution to file system access as I can easily mock the interface and add expectations and stub values to it for testing.

However, on the CruiseCli project, I realised I didn’t need most of what the interface provided, so I chopped all the bits off I didn’t want, and added a property for a base directory I was using all the time:

public interface IFileSystem
{
	string HomePath { get; }

	void WriteFile(string path, Stream contents);
	Stream ReadFile(string path);
	bool FileExists(string path);
}

Which was better than the original, as I have a lot less methods to worry about, and thus it is more specific to my use case.

But I got thinking later in the project; “what are my use cases?”, “what do I actually want to do with the filesystem?” The answer to this was simple: Read a config file, and write to the same config file. Nothing else.

So why not make the interface even more specific in this case:

public interface IConfiguration
{
	void Write(Stream contents);
	Stream Read();
}

Even simpler, and I now have the benefit of not caring what the filepaths are outside of the implementing class.

This means that in my integration tests, I can write an in-memory IConfiguration with far less hassle, and not need to worry about fun things like character encoding and case sensitivity on filepaths!

In a more complicated system, I would probably keep this new IConfiguration interface for accesing the config file, and make the concrete version depend on the more general IFileSystem:

public class Configuration : IConfiguration
{
	private const string FileName = ".cruiseconfig";
	private readonly IFileSystem _fileSystem;

	public Configuration(IFileSystem fileSystem)
	{
		_fileSystem = fileSystem;
	}

	public void Write(Stream contents)
	{
		_fileSystem.WriteFile(Path.Combine(_fileSystem.Home, FileName), contents);
	}

	public Stream Read()
	{
		return _fileSystem.ReadFile(Path.Combine(_fileSystem.Home, FileName));
	}
}

For a small system this would probably be overkill, but for a much larger project, this could help provide a better seperation of responsibilities.

design, c#

« Using StructureMap Registries for better separation Strong Type your entity IDs. »