In this short article we discuss the role of facades in application architecture, some common problems encountered, and proper solutions for these problems.
Like fake buildings on a movie set, facades in software are a nice exterior representation of something complex. In terms of software, this means a single class that contains public API methods in one place for others to call. Unfortunately, this is often abused.
Put another way, if a facade is an exterior wall of a building, that wall should not contain a furnace, air conditioner, fuse box, and power distribution system. That's not to say the system doesn't need these things, just that they belong in their proper place, not bolted onto the public-facing API.
We'll be discussing a ResumeFacade
containing several methods already.
Ideally each method in this facade should hand off responsibility to a dedicated object, making the ResumeFacade
class a central coordinator of resume-related activities.
Unfortunately, what often happens in these types of classes is that someone needs a new method added to the facade and the developer adding that method simply implements the method inside the facade.
So, for example, someone might add the following method:
public IEnumerable<JobInfo> FindJobsMatchingResume(
ResumeInfo resume,
UserInfo user)
{
// Create a database connection
// Do a query against the database
// Translate results into simple objects
// Return a list of matching objects
}
In an efficient codebase, this could be anywhere from 5 - 35 lines of code long. That's not too bad, right?
Well, now we want to add error handling and request validation, so our code now looks like this:
public IEnumerable<JobInfo> FindJobsMatchingResume(
ResumeInfo resume,
UserInfo user)
{
try {
ValidateUser(user);
// Create a database connection
// Do a query against the database
// Translate results into simple objects
// Return a list of matching objects
}
catch (SqlException ex) {
// Custom handling goes here
}
catch (AuthenticationException ex) {
// Custom handling goes here
}
catch (InvalidOperationException ex) {
// Custom handling goes here
}
}
As you can see, things start to pile up, so even if your code was very efficient, it now has a lot more boilerplate logic. Possibly you could move that logic upstream to the caller, but if you can't, your facade is growing quite a bit.
Let's say that the next sprint some new business logic comes around on filtering and sorting job matches for resumes and you now need to do more logic inside of the method. Your code just grew yet again.
These types of classes tend to grow significantly due to common causes such as:
- New methods being added to represent new capabilities being offered
- Additional error handling needed
- Additional business logic
- Changes required for bugfixing existing logic
Before you know it, your methods are 50 lines long or worse and you have an awful lot of them. You're now far from a very cohesive set of very short methods. Maybe you solve this by extracting private methods for things to call and your methods aren't that large, but this makes your method count problem even worse.
As a general rule, I like to have methods that are no longer than 20 lines of code and classes that are no longer than 250 total lines. With facades, I aim for closer to 10 lines of code per method and accept a larger number of public methods.
The solution for rapidly growing facades is for the facade to be simply a coordinator between various objects, responsible for chaining things together and packaging together appropriate responses for external callers.
In the case of our example, the refactored method might look like this:
public IEnumerable<JobInfo> FindJobsMatchingResume(
ResumeInfo resume,
UserInfo user)
{
try {
UserRepository.Validate(user);
var analyzer = new ResumeAnalyzer();
var keywords = analyzer.Analyze(resume);
return JobsRepository.FindJobsMatching(keywords, resume.YearsExperience);
}
catch (ResumeAnalyzerException ex) {
// Custom handling goes here
}
catch (AuthenticationException ex) {
// Custom handling goes here
}
}
In the above example, we still have a lot of error handling logic in the facade, but that's part of the facade's core responsibilities in this case - responding to internal errors in an externally-acceptable way.
The key thing here is that we're outsourcing the core logic to dedicated classes. This keeps the facade simple, easy to read, and easy to maintain. It also makes it easier to swap out different implementations of various pieces of business logic as needed.
Facades have an important place in application development, but that doesn't mean they get to be god objects spanning thousands of lines of code. Keep your facades simple coordinators and your architecture will be all the better for it.
Top comments (5)
Huge +1 for the lines of code in methods and classes. I've recently started refactoring a lot of the code I've come across to have more smaller methods than one super method. Readibility improved dramatically.
Whilst I don't think there are hard and fast rules, I still like the 'if it doesn't fit in a viewport window it's too long' as a general rule of thumb
I adopted hard rules on this after seeing my temptation to violate the single responsibility principle and taking over a codebase filled with long classes and methods. Everyone is different and different needs mean different standards.
Agreed! Of all the 'rules' around development, class and method lengths is one I think is really valuable.
What are your thoughts on moving the error handling to a decorating class that wraps the facade? Something dedicated to error handling...
The same could be done for logging or caching to keep cross cutting out of the business logic.
Often what I'll do is either use a private method inside the facade for standard error handling / logging if there's a lot of overlap or use a static class in C# with extension method support so, the class might look like this for logging:
and then in my handler I might do:
If, for some reason, you have multiple facades in an application (can happen if you support different types of external callers, for example), you can extract a base class for all facades and add protected methods for these cross-cutting concerns.
The other thing you could consider is following an
Action
pattern where you have a single method that does standard stuff like validation and error handling and it just takes in a function that it invokes for the implementation-specific logic.