Introduction
A very popular architecture for enterprise applications is the triplet Application, Business Logic Layer (BLL), Data Access Layer (DAL). For some reason, as time goes by, the Business Layer starts getting fatter and fatter losing its health in the process. Perhaps, I was doing it wrong.
Somehow very well designed code gets old and turns into a headless monster. I ran into a couple of these monsters that I have been able to tame using FubuMVC's behaviour chains. A pattern designed for web applications that I have found useful for breaking down complex BLL objects into nice maintainable pink ponies.
Paradise Beach Service
I need an example to make this work. So let's go to the beach. Spain has some of the best beaches in Europe. Let's build a web service to search for the dream beach. I want the clients to enter some criteria: province, type of sand, nudist, surf, some weather conditions as some people might like sun, others shade and surfers will certainly want some wind. The service will return the whole matching list.
There would be 2 entry points:
- Minimal. Results will contain only beach Ids. Clients must have downloaded the json Beach List.
- Detailed. Results will contain all information I have about the beaches.
The weather report will be downloaded from a free on-line weather service like OpenWeatherMap. All dependencies will be abstract and constructor injected.
public IEnumerable<BeachMin> SearchMin(SearchRequest request) {
var candidates = beachDal.GetBeachesMatching(
request.Location,
request.TypeOfSand,
request.Features);
var beachesWithWeatherReport = candidates
.Select(x => new {
Beach = x,
Weather = weather.Get(x.Locality);
});
var filteredBeaches = beachesWithWeatherReport
.Where(x => x.Weather.Wind == request.Weather.Wind)
.Where(x => (x.Weather.Sky & request.Weather.Sky) == request.Weather.Sky)
.Select(x => x.Beach);
var orderedByPopularity = filteredBeaches
.OrderBy(x => x.Popularity);
return orderedByPopularity
.Select(x => x.TranslateTo<BeachMin>());
}
This is very simple and might look as good code. But hidden in these few lines is a single responsibility principle violation. Here I'm fetching data from a DAL and from an external service, filtering, ordering and finally transforming data. There are five reasons of change for this code. This might look OK today but problems will come later, as code ages.
Let's Feed It Some Junk Food
In any actual production scenario, this service will need some additions. Logging, to see what is going on and get some nice looking graphs; Cache to make it more efficient, and some Debug information to help us exterminate infestations. Where would all these behaviors go? To the business, of course. Nobody likes to put anything that is not database specific into the DAL. The web service itself does not have access to what is really going on. So...everything else goes to the BLL. This might look a little exaggerated, but believe me...it's not.
public IEnumerable<BeachMin> SearchMin(SearchRequest request) {
Debug.WriteLine("Entering SearchMin");
var stopwatch = new Stopwatch();
stopwatch.Start();
Logger.Log("SearchMin.Request", request);
Debug.WriteLine("Before calling DAL: {0}", stopwatch.Elapsed);
var cacheKey = CreateCacheKey(request);
var candidates = Cache.Contains(cacheKey)
? Cache.Get(cacheKey)
: beachDal.GetBeachesMatching(request.Location, request.TypeOfSand, request.Features);
Cache.Set(cacheKey, candidates);
Debug.WriteLine("After calling DAL: {0}", stopwatch.Elapsed);
Logger.Log("SearchMin.Candidates", candidates);
Debug.WriteLine("Before calling weather service: {0}", stopwatch.Elapsed);
var beachesWithWeatherReport = candidates
.Select(x => new {
Beach = x,
Weather = weather.Get(x.Locality);
});
Debug.WriteLine("After calling weather service: {0}", stopwatch.Elapsed);
Logger.Log("SearchMin.Weather", beachesWithWeatherReport);
Debug.WriteLine("Before filtering: {0}", stopwatch.Elapsed);
var filteredBeaches = beachesWithWeatherReport
.Where(x => x.Weather.Wind == request.Weather.Wind)
.Where(x => (x.Weather.Sky & request.Weather.Sky) == request.Weather.Sky)
.Select(x => x.Beach);
Debug.WriteLine("After filtering: {1}", stopwatch.Elapsed);
Logger.Log("SearchMin.Filtered", filteredBeaches);
Debug.WriteLine("Before ordering by popularity: {0}", stopwatch.Elapsed);
var orderedByPopularity = filteredBeaches
.OrderBy(x => x.Popularity);
Debug.WriteLine("After ordering by popularity: {0}", stopwatch.Elapsed);
Debug.WriteLine("Exiting SearchMin");
return orderedByPopularity;
}
If you don't own any code like previous: Bravo!! lucky you. I have written way too many BLLs that look just like this one. Now, ask yourself: What, exactly, does a "Paradise beach service" have to do with logging, caching and debugging? Easy answer: Absolutely nothing.
Usually, there would be anything wrong with this code. But every application needs maintenance. With time, business requirements will change and I would need to touch it. Then a bug will be found: touch it again. At some point, the monster will wake up and there would be no more good news from that point forward.
Actual Business Logic
Let's see what I'm actually doing:
- Find candidate beaches. Those in specified province with wanted features and type of sand.
- Get weather report about each of the candidates.
- Filter out those beaches not matching desired weather.
- Order by popularity.
- Transform the data into expected output.
This is how you would do it manually with a map and maybe a telephone and a patient operator to get the weather reports. This is exactly what a BLL must do, and nothing else.
I will implement a BLL for each of previous steps, they will have just one Execute
method with one argument and a return value. Each step will have a meaningful intention revealing name that will receive an argument with the same name ended with Input
and return type with the same name ended with Output
. Conventions rock!!
public class FindCandidates : IFindCandidates
{
private readonly IBeachesDal beachesDal;
public FindCandidates(IBeachesDal beachesDal)
{
this.beachesDal = beachesDal;
}
public FindCandidatesOutput Execute(FindCandidatesInput input)
{
var beaches = beachesDal.GetCandidateBeaches(
input.Province,
input.TypeOfSand,
input.Features);
return new FindCandidatesOutput
{
Beaches = beaches
};
}
}
public class GetWeatherReport : IGetWeatherReport
{
private readonly IWeatherService weather;
public GetWeatherReport(IWeatherService weather)
{
this.weather = weather;
}
public GetWeatherReportOutput Execute(GetWeatherReportInput input)
{
var beachesWithWeather = input.Beaches
.Select(NewCandidateBeachWithWeather);
return new GetWeatherReportOutput
{
BeachesWithWeather = beachesWithWeather
};
}
private CandidateBeachWithWeather NewCandidateBeachWithWeather(CandidateBeach x)
{
var result = x.TranslateTo<CandidateBeachWithWeather>();
result.Weather = weather.Get(x.Locality);
return result;
}
}
public class FilterByWeather : IFilterByWeather
{
public FilterByWeatherOutput Execute(FilterByWeatherInput input)
{
var filtered = input.BeachesWithWeather
.Where(x => x.Weather.Sky == input.Sky)
.Where(x => input.MinTemperature <= x.Weather.Temperature
&& x.Weather.Temperature <= input.MaxTemperature)
.Where(x => input.MinWindSpeed <= x.Weather.WindSpeed
&& x.Weather.WindSpeed <= input.MaxWindSpeed);
return new FilterByWeatherOutput
{
Beaches = filtered
};
}
}
public class OrderByPopularity : IOrderByPopularity
{
public OrderByPopularityOutput Execute(OrderByPopularityInput input)
{
var orderedByPopularity = input.Beaches.OrderBy(x => x.Popularity);
return new OrderByPopularityOutput
{
Beaches = orderedByPopularity
};
}
}
public class TranslateToBeachMin : IConvertToMinResult
{
public TranslateToBeachMinOutput Execute(TranslateToBeachMinInput input)
{
return new TranslateToBeachMinOutput
{
Beaches = input.Beaches
.Select(x => x.TranslateTo<BeachMin>())
};
}
}
I know what you're thinking: I took a 15 lines of code (LOC) program and transformed it into a 100 or more one... You are right. But let's see what I have. Five clean and small BLLs, each represents a part of our previous single BLL. Their dependencies are abstract which will also make it easy to test them thoroughly. They are easy to manage, because they are so small, they will be very easy to maintain, substitute and even reuse. For instance, you don't really need to have performed a life weather search to get a list of beaches and weather conditions to be filtered, you just need to create the input for each of the steps and voilĂ , you can execute that particular step. At the end, I added a step to translate CandidateBeach
into BeachMin
which is the response I really need for our original service. I also extracted interfaces for each of the steps, it helps with abstractions and some other things I'll do later.
Chain'em up
public IEnumerable<BeachMin> SearchMin(SearchRequest request) {
var candidates = findCandidates.Execute(new FindCandidatesInput {
Province = request.Province,
TypeOfSand = request.TypeOfSand,
Features = request.Features
});
var candidatesWithWeather = getWeatherReport.Execute(new GetWeatherReportInput {
Beaches = candidates.Beaches
});
var filtered = filterByWeather.Execute(new FilterByWeatherInput {
Beaches = candidates.Beaches,
Sky = request.Sky,
MinTemperature = request.MinTemperature,
MaxTemperature = request.MaxTemperature,
MinWindSpeed = request.MinWindSpeed,
MaxWindSpeed = request.MaxWindSpeed
});
var orderedByPopularity = orderByPopularity.Execute(new OrderByPopularityInput {
Beaches = filtered.Beaches
});
var result = translateToBeachMin.Execute(new TranslateToBeachMinInput {
Beaches = orderedByPopularity.Beaches
});
return result;
}
What do you know? I'm back to 15 LOC, maybe less. I think this code doesn't even need explaining. I took our steps and chain them into a Behavior Chain. From now on, we will refer to Steps as Behaviors. I'm kind of where I started, but now our service depends on external extensible, reusable and abstract behaviors. Still, it must know them all. This makes it difficult to add a new behavior. Another thing is I will have almost identical code for the other entry point. I must do something to improve these two.
Mechanize It
I know... Sarah Connor wouldn't agree. I have this tool which takes some objects and automatically chains them together into a function but before, let's see what a service depending on functions would look like.
public class SearchService : Service {
public SearchService(
Func<SearchMinRequest, SearchMinResponse> searchMin,
Func<SearchDetailsRequest, SearchDetailsResponse> searchDetails) {
this.searchMin = searchMin;
this.searchDetails = searchDetails;
}
public object Any(SearchMinRequest request) {
return searchMin(request);
}
public object Any(SearchDetailsRequest request) {
return searchDetails(request);
}
}
I'm using ServiceStack as web framework. Basically, both Any
method in the examples are web service entry points. As you can see, they delegate the actual work to functions injected through the constructor. At some point, which for ServiceStack
is the application configuration, I need to create these functions and register them into the IoC container.
public override void Configure(Container container) {
var searchMin = Chain<SearchMinRequest, SearchMinResponse>(
findCandidates,
getWeatherReport,
filterByWeather,
orderByPopularity,
translateToBeachMin);
var searchDetails = Chain<SearchDetailsRequest, SearchDetailsResponse>(
findCandidates,
getWeatherReport,
filterByWeather,
orderByPopularity,
addDetails);
container.Register(searchMin);
container.Register(searchDetails);
}
private static Func<TInput, TOutput> Chain<TInput, TOutput>(params object[] behaviors)
where TInput : new()
where TOutput : new()
{
return behaviors
.ExtractBehaviorFunctions()
.Chain<TInput, TOutput>();
}
There are some points here that are worth mentioning:
- Each behavior kind of depends on previous but it doesn't really know it.
- The chain is created from functions which could be instance or
static
methods, lambda expressions or even functions defined in another language like F#. - The
ExtracBehaviorFunctions
method takes in objects and extracts their Execute
method or throws an exception if there is none. This is my convention, you could define your own. - The
Chain
method takes in delegates and creates a function by chaining them together. It will throw exceptions if incompatible delegates are used.
Additions
I will enrich our BLLs by means of transparent decorators. Using Castle.DynamicProxy I will generate types which will intercept the calls to our behaviors and add some features. Then I will register the decorated instances instead of the original. I will start with cache and debugging. The cache is trivial in memory, 10 min. More complicated solutions can be easily implemented.
container.Register(new ProxyGenerator());
container.Register<ICache>(new InMemory10MinCache());
container.RegisterAutoWired<CacheDecoratorGenerator>();
CacheDecoratorGenerator = container.Resolve<CacheDecoratorGenerator>();
container.RegisterAutoWired<DebugDecoratorGenerator>();
DebugDecoratorGenerator = container.Resolve<DebugDecoratorGenerator>();
With this code, our decorator generators are ready, let's look now at how to decorate the behaviors.
var findCandidates = DebugDecoratorGenerator.Generate(
CacheDecoratorGenerator.Generate(
container.Resolve<IFindCandidates>()));
var getWeatherReport = DebugDecoratorGenerator.Generate(
container.Resolve<IGetWeatherReport>());
var filterByWeather = DebugDecoratorGenerator.Generate(
container.Resolve<IFilterByWeather>());
var orderByPopularity = DebugDecoratorGenerator.Generate(
container.Resolve<IOrderByPopularity>());
var convertToMinResult = DebugDecoratorGenerator.Generate(
container.Resolve<IConvertToMinResult>());
Here, I decorated every behavior with debugging and only findCandidates
with caching too. It might be interesting to add some cache to weather report as well, but since the input might be a very big list of beaches, caching won't be correct. Instead, I will add caching to both the DAL and the weather service.
container.Register(c => DebugDecoratorGenerator.Generate(
CacheDecoratorGenerator.Generate(
(IBeachesDal) new BeachesDal(c.Resolve<Func<IDbConnection>>()))));
container.Register(c => DebugDecoratorGenerator.Generate(
CacheDecoratorGenerator.Generate(
(IWeatherService) new WeatherService())));
Manual Decorators
Generated decorators are not enough for some tasks, and if you are a friend of IDE Debugging, they will certainly give you some headaches. There is always the manual choice.
public class FindCandidatesLogDecorator : IFindCandidates
{
private readonly ILog log;
private readonly IFindCandidates inner;
public FindCandidatesLogDecorator(ILog log, IFindCandidates inner)
{
this.log = log;
this.inner = inner;
}
public FindCandidatesOutput Execute(FindCandidatesInput input)
{
var result = inner.Execute(input);
log.InfoFormat("Execute({0}) returned {1}", input.ToJson(), result.ToJson());
return result;
}
}
By using more powerful IoC containers like AutoFac, you would be able to create more powerful decorators, both automatically generated and manual. You won't ever have to touch your BLL unless there are Business Requirement changes or bugs.
When to Use
When your BLL is a set of steps that are:
- Well defined. The responsibilities are clear and have clear boundaries.
- Independent. The steps don't know each other.
- Sequential. The order cannot be changed based on input. All steps must be always executed.
The behavior chain functions are kind of static
, they are not meant to be altered in execution. You can create, though, a new function to replace an existing one based on any logic of your specific problem.
How It Works
The generation code isn't really that interesting. Just a lot of hairy statements generating lambda expressions using the wonderful Linq.Expressions
. You can still look at it on the source code. Let's see instead how generated code works. This is how the generate function looks like, or kind of.
var generatedFunction = new Func<SearchDetailsRequest, SearchDetailsResponse>(req => {
var valuesSoFar = new Dictionary<string, object>();
valuesSoFar["Provice"] = req.Provice;
valuesSoFar["TypeOfSand"] = req.TypeOfSand;
valuesSoFar["Features"] = req.Features;
valuesSoFar["Sky"] = req.Sky;
valuesSoFar["MinTemperature"] = req.MinTemperature;
valuesSoFar["MaxTemperature"] = req.MaxTemperature;
valuesSoFar["MinWindSpeed"] = req.MinWindSpeed;
valuesSoFar["MaxWindSpeed"] = req.MaxWindSpeed;
var input0 = new FindCandidatesInput {
Provice = (string)valuesSoFar["Provice"],
TypeOfSand = (TypeOfSand)valuesSoFar["TypeOfSand"],
Features = (Features)valuesSoFar["Features"]
};
var output0 = behavior0(input0);
valuesSoFar["Beaches"] = output0.Beaches;
var input1 = new GetWeatherReportInput {
Beaches = (IEnumerable<CandidateBeach>)valuesSoFar["Beaches"]
}
var output1 = behavior1(input1);
valuesSoFar["Beaches"] = output1.Beaches;
var input2 = new FilterByWeather {
Beaches = (IEnumerable<CandidateBeachWithWeather>)valuesSoFar["Beaches"]
Sky = (Sky)valuesSoFar["Sky"],
MinTemperature = (float)valuesSoFar["MinTemperature"],
MaxTemperature = (float)valuesSoFar["MaxTemperature"],
MinWindSpeed = (float)valuesSoFar["MinWindSpeed"],
MaxWindSpeed = (float)valuesSoFar["MaxWindSpeed"]
}
var output2 = behavior2(input2);
valuesSoFar["Beaches"] = output2.Beaches;
var input3 = new OrderByPopularityInput {
Beaches = (IEnumerable<CandidateBeach>)valuesSoFar["Beaches"]
}
var output3 = behavior3(input3);
valuesSoFar["Beaches"] = output3.Beaches;
var input4 = new AddDetailsInput {
Beaches = (IEnumerable<CandidateBeach>)valuesSoFar["Beaches"]
}
var output4 = behavior4(input4);
valuesSoFar["Beaches"] = output4.Beaches;
return new SearchDetailsResponse {
Beaches = (IEnumerable<BeachDetails>)valuesSoFar["Beaches"]
};
});
Using the Code
What is it good for if you cannot see it working?
This will start the server in configured port, 52451
by default. Now you need to create a client program. You can manually create a client project by using ServiceStack. Or any other web framework. You can also use included Linqpad file at <project_root>\linqpad\search_for_beaches.linq
which basically does as follows:
var client = new JsonServiceClient("http://localhost:52451/");
var response = client.Post(new SearchDetailedRequest {
Province = "Huelva",
Features = Features.Surf,
MinTemperature = 0f,
MaxTemperature = 90f,
MinWindSpeed = 0f,
MaxWindSpeed = 90f,
TypeOfSand = TypeOfSand.White,
Sky = Sky.Clear
});
Conclusions
The high code quality is very important if you want a maintainable application with a long lifespan. By choosing the right design patterns and applying some techniques and best practices, any tool will work for us and produce really elegant solutions to our problems. If on the other hand, you learn just how to use the tools, you are going to end up programming for the tools and not for the ones that sign your pay-checks.