A comprehensive example
2.1 Doing it wrong
2.1.1 Building a tightly coupled application
There’s more than one way to view and design a flexible and maintainable complex application,1 but the n-layer application architecture constitutes a well-known, tried-
and-true approach. The challenge is to implement it correctly.
Armed with a three-layer diagram like the one in figure 2.2, you can now start building an application.
MEET MARY ROWAN
Mary Rowan is a professional .NET developer working for a local Certified Microsoft Partner that mainly develops web applications. She’s 34-years old and has been work- ing with software for 11 years. This makes her one of the more experienced develop- ers in the company, and she often acts as a mentor for junior developers in addition to performing her regular duties as a senior developer.
In general, Mary is happy about the work that she’s doing, but it frustrates her that milestones are often being missed, forcing her and her colleagues to work long hours and weekends to meet deadlines. She suspects that there must be more efficient ways to build software. In an effort to learn about efficiency, she buys a lot of programming books—but she rarely has time to read them.
Much of her spare time is spent with her hus- band and two girls. Mary likes to go hiking in the mountains. She’s also an enthusiastic cook, and she definitely knows how to make a real sauce béarnaise. Mary has been asked to create a new e-commerce application on ASP.NETMVC and the Entity Frame- work with SQL Server as the data store. To maximize modularity, it must be a three-layer application.
The first feature to be implemented should be a simple list of featured products, pulled from a data- base table and displayed on a web page; an example is shown in figure 2.3. If the user viewing the list is a preferred customer, the price on all products should be discounted by five percent.
Let’s look over Mary’s shoulder as she imple- ments the application’s first feature.
1 Currently, the most promising alternative to n-layer applications is an architectural style related to the Command-
Query Responsibility Segregation (CQRS) pattern. For more information, see Rinat Abdullin, “CQRS Starting Page,” http://abdullin.com/cqrs
Figure 2.3 Screen shot of the e-commerce web application Mary has been asked to develop. It features a simple list of featured products and their prices (“kr.” is the currency symbol for Danish Kroner).
DATALAYER
Because she’ll need to pull data from a database table, Mary has decided to begin by implementing the data layer. The first step is to define the database table itself. Mary uses SQL Server Management Studio to create the table shown in figure 2.4.
To implement the Data Access Layer, Mary adds a new library to her solution. From Visual Studio, she uses the Entity Data Model Wizard to generate an entity model from the database she just created. She changes a few names to finalize the model, as shown in figure 2.5.
NOTE Don’t worry if you aren’t familiar with the Microsoft Entity Framework. The details of the data access implementation aren’t
that important in this context, so
you should be able to follow the example even if you’re more familiar with a different data access technology.
The generated ObjectContext and the Product entity are public types contained within the same assembly. Mary knows that she’ll later need to add more features to her application, but the data access component needed to implement the first feature is now completed.
Figure 2.6 shows how far Mary has come in implement- ing the layered architecture envisioned in figure 2.2.
Now that the Data Access Layer has been imple- mented, the next logical step is the Domain Logic Layer.
DOMAIN LAYER
In the absence of any domain logic, the list of Products exposed by the generated ObjectContext could technically have been used directly from the User Interface Layer.
WARNING With the exception of pure data-reporting applications, there’s always domain logic. You may not realize it at first, but as you get to know the domain, its embedded and implicit rules and assumptions will
Figure 2.4 Mary creates the Product table using SQL Server Management Studio; alternative approaches include writing a T-SQL script or creating the table through Visual Studio, or by some other means.
Figure 2.6 So far Mary has implemented the Data Access Layer of her application. The Domain Logic Layer and User Interface Layer are still left before the feature is complete.
Figure 2.5 The Product
entity generated from the Product database table shown in figure 2.4. Mary has changed the name of the Featured column to IsFeatured, as well as changed a few names in the generated
ObjectContext
33
Doing it wrong
gradually emerge. Implementing such logic in either the User Interface or Data Access Layers will lead to pain and suffering. Do yourself a favor and cre- ate a Domain Logic Layer from the beginning.
The requirements for Mary’s application state that preferred customers should be shown the list prices with a five percent discount. Mary has yet to figure out how to identify a preferred customer, so she asks her coworker Jens for advice:
MARY: I need to implement this business logic so that a preferred customer gets a five percent discount.
JENS: Sounds easy. Just multiply by .95.
MARY: Thanks, but that’s not what I wanted to ask you about. What I wanted to ask you is, how should I identify a preferred customer?
JENS: I see. Is this a web application or a desktop application? MARY: It’s a web app.
JENS: Okay, then you can define a user profile and have an IsPreferredCustomer property. You can get the profile through the HttpContext.
MARY: Slow down, Jens. This code must be in the Domain Logic Layer. It’s a library. There’s no
HttpContext.
JENS: Oh. [Thinks for a while] I still think you should use the Profile feature of ASP.NET to look up the value on the user. You can then pass the value to your domain logic as a Boolean. MARY: I don’t know…
JENS: That will also ensure that you have good separation of concerns, because your domain logic doesn’t have to deal with security. You know: The SINGLE RESPONSIBILITY PRINCIPLE! It’s the Agile way to do it!
MARY: I guess you’ve got a point.
WARNING Jens is basing his advice on his technical knowledge of ASP.NET. As the discussion takes him away from his comfort zone, he steamrolls Mary with a triple combo of buzzwords. Be aware that he doesn’t know what he is talking about: he misuses the concept of separation of concerns, com- pletely misses the point with the SINGLE RESPONSIBILITY PRINCIPLE, and he only
mentions Agile because he recently heard someone else talk enthusiasti- cally about it.
Armed with Jens’ advice, Mary creates a new C# library project and adds a class called ProductService, shown in the following listing. To make the ProductService class compile, she must add a reference to her Data Access library, because the Commerce- ObjectContext class is defined there.
public partial class ProductService {
private readonly CommerceObjectContext objectContext;
public ProductService() {
this.objectContext = new CommerceObjectContext(); }
public IEnumerable<Product> GetFeaturedProducts( bool isCustomerPreferred)
{
var discount = isCustomerPreferred ? .95m : 1; var products = (from p in this.objectContext .Products
where p.IsFeatured
select p).AsEnumerable(); return from p in products
select new Product {
ProductId = p.ProductId, Name = p.Name,
Description = p.Description, IsFeatured = p.IsFeatured,
UnitPrice = p.UnitPrice * discount };
} }
Mary is happy that she has encapsulated data access technology (LINQ to Entities), con- figuration, and domain logic in the ProductService class. She has delegated the knowledge of the user to the caller by passing in the isCustomerPreferred parameter, and she uses this value to calculate the discount for all
the products.
Further refinement could include replacing the hard- coded discount value (.95) with a configurable number, but, for now, this implementation will suffice. Mary is almost done—the only thing still left is the User Interface. Mary decides that can wait until the next day.
Figure 2.7 shows how far Mary has come with imple- menting the architecture envisioned in figure 2.2.
With the Data Access and Domain Logic Layers implemented, the only remaining layer to implement is the User Interface Layer.
USER INTERFACE LAYER
The next day, Mary resumes her work with the e-com- merce application, adding a new ASP.NET MVC applica- tion to her solution.
NOTE Don’t worry if you aren’t familiar with the
ASP.NETMVC framework. The intricate details of
Figure 2.7 At this point, Mary has implemented the Data Access Layer and the Domain Logic Layer. Compared to figure 2.6, the Domain Logic Layer has been added. The User Interface Layer still remains to be implemented.
35
Doing it wrong
how the MVC framework operates aren’t the focus of this discussion. The important part is how DEPENDENCIES are consumed, and that’s a relatively plat-
form-neutral subject. 2
The next listing shows how she implements an Index method on her HomeController class to extract the featured products from the database and pass them to the View. To make this code compile, she must add references to both the Data Access library and the Domain library because the ProductService class is defined in the Domain library, but the Product class is defined in the Data Access library.
public ViewResult Index() {
bool isPreferredCustomer =
this.User.IsInRole("PreferredCustomer"); var service = new ProductService();
var products =
service.GetFeaturedProducts(isPreferredCustomer); this.ViewData["Products"] = products
return this.View(); }
As part of the ASP.NETMVC lifecycle, the User property on the HomeController class is automatically populated with the correct user object, so Mary uses it to determine if the current user is a preferred customer. Armed with this information, she can invoke the Domain Logic to get the list of featured products. In a moment, I’ll return to this, because it contains a trap, but for now, I’ll let Mary discover it for herself.
ASP.NET MVC crash course
ASP.NET MVC takes its name from the Model View Controller2 design pattern. In this
context, the most important thing to understand is that when a web request arrives, a Controller handles the request, potentially using a (Domain) Model to deal with it and form a response that’s finally rendered by a View.
A Controller is normally a class that derives from the abstract Controller class. It has one or more action methods that handle requests; for example, a HomeController class has an Index method that handles the request for the default page.
When an action method returns, it passes on the resulting Model to the View through a ViewResult instance.
2 Martin Fowler et al., Patterns of Enterprise Application Architecture (New York: Addison-Wesley, 2003), 330.
The list of products must be rendered by the Index View. The following listing shows the markup for the View.
<h2>Featured Products</h2> <div>
<% var products = (IEnumerable<Product>)this.ViewData["Products"]; foreach (var product in products)
{ %> <div> <%= this.Html.Encode(product.Name) %> (<%= this.Html.Encode(product.UnitPrice.ToString("C")) %>) </div> <% } %> </div>
ASP.NET MVC enables you to write standard HTML with bits of imperative code embedded to access objects cre- ated and assigned by the Controller that created the View. In this case, the HomeController’s Index method assigned the list of featured products to a key called Products that Mary uses in the View to render the list of products.
Figure 2.8 shows how Mary has now implemented the architecture envisioned in figure 2.2.
With all three layers in place the applications should theoretically work, but only a test can verify whether that’s the case.