When developers hear the term “design pattern,” they generally envision one of two scenarios. One group of developers assumes you’re talking about lots of brainstorming and meetings without much real coding being done. The other group has visions of proper planning to ensure successful development and reuse of code.
The reality, as always, lies somewhere in between. Architects and system designers should look for repeatable patterns when designing solutions for their companies. But patterns should serve as guidelines for developing robust, reusable code. Architects have to resist the desire to overarchitect a solution at the expense of the most important application feature: delivery.
Among the prime victims of overarchitecting are Web applications. Since most Web applications are used to browse data, they should be architected to favor speed of display over speed of data update. In many cases, building a complex, multilayer architecture does not serve the needs of users or developers. Let’s look at a simple pattern for developing .NET Web applications.
Implementing a classic design pattern using ASP.NET
Smalltalk, one of the earliest object-oriented languages, gave developers a platform to develop best practices for object-oriented systems. The classic Model, View, Controller (MVC) design pattern was developed from this research and is still used today as a reference. The Model holds the data displayed by the View and managed by the Controller. The View is responsible for delivering output to the user, and the Controller reacts to user actions and updates the Model appropriately.
ASP.NET provides a perfect analogy for implementing this classic design pattern. The data set contains the application data and represents the Model component. A developer implements the View by developing the user interface in the ASPX page. The Controller functions are implemented in the code-behind file (Foo.aspx.vb or Foo.aspx.cs).
Implementing this design on .NET provides a two-tier system with significant advantages over classic ASP architecture. Separating user display (View) from actions (Controller) promotes code reuse. Separating the data (Model) from the actions performed on it (Controller) allows you to design a system independent of which back end happens to be used to store the data. If designed correctly, a system based on the MVC design pattern will neither know nor care whether the data provided to the Model component was stored in a SQL Server or Oracle database or a set of XML documents.
Many argue that developers can easily implement this pattern using ASP pages and COM objects. But the reality is that most systems I have audited either did not use COM objects or pushed only database access to the COM objects; they still did all their business logic in scripts on the ASP pages. I’m not saying that the MVC pattern advocates that scripting should be removed from ASP pages. I am saying that scripting in ASP pages should be limited to what is necessary to support the View functions and not Controller functions.
Performance and extensibility
When architecting a solution based on this pattern, be sure to consider two other issues. First, how well does the solution perform and how can I increase performance? Second, how extensible and scalable is the solution and where does it make sense to extend or break the pattern?
Although access to the Model from the Controller and the View should be database-independent, that does not mean the Model itself can’t be optimized. Since the ADO DataSet does not care about the data source, performance is improved without breaking the pattern by taking advantage of database-specific features. For example, rather than using SQL Select statements embedded in your code-behind file (Controller), build stored procedures that return expected values given specific parameters. Stored procedures are not only precompiled by the database, but they also have a predetermined execution path that makes them very fast and efficient.
You’ll break the model, however, if you use stored procedures to process business rules. These rules belong in the Controller, allowing multiple views to take advantage of them. As a general rule, you should use database-specific features to optimize performance or to enforce referential integrity, but not to implement Controller features.
Extensibility and scalability
To scale successfully, an MVC-pattern application has to work in a Web farm. As long as you design your application to either be stateless or to maintain state between the View and the user (the default for ASP.NET applications), you can then scale an MVC-based application by simply replicating the ASPX pages and code-behind files to multiple IIS Servers in a server farm, all pointing to the same database server.
When implementing this pattern, I’ve found that it almost always makes sense to separate the logical Controller layer into two physical layers. Rather than having the same data access code replicated across multiple methods in the Controller layer, I prefer to consolidate all the code in a single data access object that performs all data access for the application. This data access object can be used by the code-behind files from multiple applications. Microsoft provides a great example of a consolidated data access layer in its data access application block, which you can download from MSDN. Centralizing data access code promotes code reuse, but more importantly, it allows you to guarantee that your application takes advantage of connection pooling by ensuring that connections are set up to take advantage of the capability.
Better software, faster
Using design patterns helps you to build more reliable and supportable software. When architecting systems for your customers, you should consider building applications based on known patterns first and then extending them based on specific application needs or performance demands.