#Simpler
You probably won't like Simpler. If you enjoy spending your time maintaining complex domain models, configuring ORMs, interfacing with DI/IOC frameworks, and regenerating code, then you will probably hate Simpler. Simpler's primary goal is help developers create quick, simple solutions while writing the least amount of code possible. Every piece of code in an application should have a clearly visible business purpose - the rest is just noise.
###"What is it?"
For the most part, Simpler is just a philosophy on .NET class design. All classes that contain functionality are defined as Tasks named as verbs. A Task has optional input and/or output, a single Execute() method, and possibly some sub-tasks - that's it.
public class Ask : Task
{
// Inputs
public string Question { get; set; }
// Outputs
public string Answer { get; private set; }
public override void Execute()
{
Answer =
Question == "Is this cool?"
? "Definitely."
: "Get a life.";
}
}
Simpler 2 adds some additional base classes, InTask, OutTask, and InOutTask, that allow for explicity defining the input and/or output of the Task.
public class Ask : InOutTask<Ask.Input, Ask.Output>
{
public class Input
{
public string Question { get; set; }
}
public class Output
{
public string Answer { get; set; }
}
public override void Execute()
{
Out.Answer =
In.Question == "Is this cool?"
? "Definitely."
: "Get a life.";
}
}
Input is available to the Execute() method by way of the In property, and output is set using the Out property. This eliminates the need to comment your input and output properties, and makes it easy to identify the input and output within the Execute() method since all input is wrapped by In, and all output is set on Out.
###"How do I use it?"
First, you build a Task class. You then instantiate Tasks using the Task.New() method.
Task.New() appears to just return an instance of the given Task type. However, it actually returns a proxy to the Task. The proxy allows for intercepting Task Execute() calls to perform actions before and/or after the Task executes. Simpler uses this to automatically inject sub-task properties (only if null) before Task execution by way of the Simpler.EventsAttribute. Another common use of this functionality is to build a custom EventsAttribute to log task activity.
public class LogAttribute : EventsAttribute
{
public override void BeforeExecute(Task task)
{
Console.WriteLine(String.Format("{0} started.", task.Name));
}
public override void AfterExecute(Task task)
{
Console.WriteLine(String.Format("{0} finished.", task.Name));
}
public override void OnError(Task task, Exception exception)
{
Console.WriteLine(String.Format("{0} bombed; error message: {1}.", task.Name, exception.Message));
}
}
[Log]
public class BeAnnoying : InTask<BeAnnoying.Input>
{
public class Input
{
public int AnnoyanceLevel { get; set; }
}
public Ask Ask { get; set; }
public override void Execute()
{
// "BeAnnoying started." was logged to the console before Execute() began.
// Notice that Ask was automatically instantiated.
Ask.In.Question = "Is this cool?";
for (var i = 0; i < In.AnnoyanceLevel; i++)
{
Ask.Execute();
}
// "BeAnnoying finished." will be logged to the console after Execute() finishes.
}
}
public class Program
{
Program()
{
var beAnnoying = Task.New<BeAnnoying>();
beAnnoying.In.AnnoyanceLevel = 10;
beAnnoying.Execute();
}
}
That pretty much sums it up. You build task classes, you use Task.New to create them, and you can use the power of the proxy to address cross cutting concerns like logging.
###"What about database interaction?"
Simpler provides a small set of Simpler.Data.Tasks classes that simplify interacting with System.Data.IDbCommand. Using SQL, Simpler makes it pretty easy to get data out of a database and into POCOs, or persist data from a POCO to a database.
public class Stuff
{
public string Name { get; set; }
}
public class FetchCertainStuff : InOutTask<FetchCertainStuff.Input, FetchCertainStuff.Output>
{
public class Input
{
public string SomeCriteria { get; set; }
}
public class Output
{
public Stuff[] Stuff { get; set; }
}
public BuildParameters BuildParameters { get; set; }
public FetchMany<Stuff> FetchStuff { get; set; }
public override void Execute()
{
using (var connection = new SqlConnection("MyConnectionString"))
using (var command = connection.CreateCommand())
{
connection.Open();
command.Connection = connection;
command.CommandText =
@"
select
AColumn as Name
from
ABunchOfJoinedTables
where
SomeColumn = @SomeCriteria
and
AnotherColumn = @SomeOtherCriteria
";
BuildParameters.In.Command = command;
BuildParameters.In.Values = new {In.SomeCriteria, SomeOtherCriteria = "other criteria"};
BuildParameters.Execute();
FetchStuff.In.SelectCommand = command;
FetchStuff.Execute();
Out.Stuff = FetchStuff.Out.ObjectsFetched;
}
}
}
Simpler 2 adds a new Simpler.Data.Db static class that eliminates most of the boilerplate code.
public class FetchCertainStuff : InOutTask<FetchCertainStuff.Input, FetchCertainStuff.Output>
{
public class Input
{
public string SomeCriteria { get; set; }
}
public class Output
{
public Stuff[] Stuff { get; set; }
}
public override void Execute()
{
using(var connection = Db.Connect("MyConnectionString"))
{
const string sql =
@"
select
AColumn as Name
from
ABunchOfJoinedTables
where
SomeColumn = @SomeCriteria
and
AnotherColumn = @SomeOtherCriteria
";
var values = new {In.SomeCriteria, SomeOtherCriteria = "other criteria"};
Out.Stuff = Db.GetMany<Stuff>(connection, sql, values);
}
}
}
The Db class also offers GetOne(), GetResult() and GetScalar() methods. Simpler isn't a full-featured ORM, but for most scenarios it gets the job done.
###"Is it easy to test?"
[TestFixture]
public class FetchCertainStuffTest
{
[Test]
public void should_return_9_pocos()
{
// Arrange
var task = TaskFactory<FetchCertainStuff>.Create();
task.In.SomeCriteria = "whatever";
// Act
task.Execute();
// Assert
Assert.That(task.Out.Stuff.Length, Is.EqualTo(9));
}
}
By design, all Tasks clearly define their inputs, outputs, and code to test, so tests are very straightforward.
A Task's dependencies are its inputs, outputs, and sub-tasks. The automatic sub-task injection provides the power to do testing by allowing for mocking sub-task behavior. This eliminates the need for repository nonsense when the only purpose is for testing.
###"I just need to get things done well, will Simpler help?"
Simpler is a tool for developing applications as sets of consistent, discrete, interchangable classes that aren't THINGS, but rather DO THINGS. Simpler works great in team environments because everybody is designing classes with the same termnilogy, and any class can easily integrate with another.
Develpers don't waste time making decisions about class design. Need to fetch a list of contacts? Create classes called FetchContactsTest and FetchContact and get to work. That's Simpler.
###"How do I install it?"
Use Nuget. Simpler works with .NET 3.5 and above.
###"Is Simpler so simple it doesn't need documentation?"
Exactly. I seriously hope to create some proper documentation at some point, but the coding is so much more fun.
###Acknowledgments
The following have contributed in some way and/or have built something awesome with Simpler.
- bobnigh
- Clancey
- corys
- Crosis
- danvanorden
- dchristine
- jkettell
- JOrley
- jshoemaker
- ralreegorganon
- rodel-rdi
- sonhuilamson
- timrisi
###License Simpler is licensed under the MIT License. A copy of the MIT license can be found in the LICENSE file.