The information in this book is something that I plan to refer back to frequently when designing and building application. Rather than add new information, the purpose of this blog post is to summarize and organize the information provided by the authors to make it easier to access. I hope you find it to be a help in that way.
The book starts off with a high-level discussion of architecture and architects along with a review of UML essentials. The good stuff starts in Chapter 3: Design Principles and Patterns. Below is a summary of what I thought to be the key points from Chapter 3. I thought this would make a good checklist of high-level principles to consider when designing a solution.
Design Principles and Patterns (Chapter 3, p. 63)
Find Pertinent Objects First (p. 74)
The first key step in OOD is creating a crisp and flexible abstraction of the problem’s domain. To successfully do so, you should think about things instead of processes. You should focus on the whats instead of the hows. You should stop thinking about algorithms to focus mostly on interacting entities. Interacting entities are your pertinent objects.
Favor Low Coupling and High Cohesion (p. 68, 69, 74)
Coupling measures the level of dependency existing between to software modules, such as classes, functions, or libraries. Two modules, A and B, are said to be coupled when it turns out that you have to make changes to B every time you make any change to A.
Cohesion indicates that a given software module – be it a subroutine, class, or library – features a set of responsibilities that are strongly related. Put another way, cohesion measures the distance between the logic expressed by the various methods on a class, the various functions in a library, and the various actions accomplished by a method.
Key Points:
- A good object-oriented design is characterized by low coupling and high cohesion.
- A software module should focus on a single concern and the details of its implementation should be hidden behind a stable interface (modularity and information hiding).
-
Program to an interface, not an implementation.
Favor Code Reuse with Object Composition Over Class Inheritance (p. 78)
Object composition entails creating a new type that holds an instance of the base type and typically references it through a private member. In this case, you have a wrapper class that uses a type as a black box and does so through a well-defined contract. The wrapper class has no access to internal members and cannot change the behavior in any way – it uses the object as it is rather than changing it to do its will. External calls reach the wrapper class, and the wrapper class delegates the call internally to the held instance of the class it enhances. With composition, changes to the composite object don’t affect the internal object. Likewise, changes to the internal object don’t affect the outermost container as long as there are no changes to the public interface.
In addition to composition, another approach is frequently used to contrast class inheritance – aggregation. The difference between composition and aggregation is that with composition you have a static link between the container and contained classes. If you dispose of the container, the contained classes are also disposed of. With aggregation, the link is weaker and the container is simply associated with an external class. As a result, when the container is disposed of, the child class blissfully survives.
Open/Closed Principle (p. 80)
We need to have a mechanism that allows us to enter changes where required without breaking existing code that works. The Open/Closed Principle address exactly this issue by saying the following: A module should be open for extension but closed for modification. Applied to object-oriented design, the principle recommends that we never edit the source code of a class that works in order to implement a change. In other words, each class should be conceived to be stable and immutable and never face change – the class is closed for modification.
Today, the most common way to comply with the Open/Closed Principle is by implementing a fixed interface in any classes that we figure are subject to changes. Callers will then work against the interface as in the first principle of object-oriented design. The interface is then closed for modification. But you can make your callers interact with any class that, at a minimum, implements that interface. So the overall model is open for extension, but it still provides a fixed interface to dependent objects.
Liskov’s Substitution Principle (p. 81)
The principle says the following: Subclasses should be substitutable for their base classes. It doesn’t go without saying that derived classes (subclasses) can safely replace their base classes. You have to ensure that. You should handle keywords such as sealed and virtual with extreme care. Virtual (overridable) methods, for example, should never gain access to private members. Access to private members can’t be replicated by overrides, which makes base and derived classes not semantically equivalent from the perspective of a caller. Generally, virtual methods of a derived class should work out of the same preconditions of corresponding parent methods. They also must guarantee at least the same post conditions.
Dependency Inversion Principle (p. 84)
High-level modules should not depend upon low-level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions. The inversion in the name of the principle refers to the fact that you proceed in a top-down manner during the implementation and focus on the work flow in high-level modules rather than focusing on the implementation of lower level modules. At this point, lower level modules can be injected directly into the high-level module.
Chapters 4 through 7 each focus on one of the principle layers that will found in a typical enterprise application. These chapters begin by describing the main responsibilities and characteristics of the layer and then go on to describe in detail the common design patterns used within the layer. For each pattern, I have included a reference to the page on which the pattern can be found in the book as well as a link to additional information about the pattern.
Business Logic Layer (Chapter 4, p. 129)
General Characteristics
- The BLL consists of an array of operations to execute on some data. Data is modeled after the real entities in the problem’s domain. Operations try to model business processes.
- Typically includes an object model, business rules, services, and workflows.
- Security in the BLL is role based and restricts access to business objects to authorized users.
- A significant share of business rules go arm in arm with data validation. Put another way, many of the business rules are ultimately rules for validating the current content of a given business object.
-
Ideally, the percentage of business logic distributed across the presentation layer, BLL, and DAL should be 0 – 100 – 0. However, some gray areas such as data formatting, CRUD operations, and stored procedures do exist.
Transaction Script (p. 145)
Transaction Script encourages you to skip any object-oriented design and map your business components directly onto required user actions. You focus on the operations the user can accomplish through the presentation layer and write a method for each request. This method is referred to as a transaction script.
Table Module (p. 154)
Compared to TS, TM is a more structured pattern because it provides more guidance on how to do things. The simple rule for this pattern can be summarized as follows: define a business component for each database table. The business component is known as the table module class and contains all the code that operates on the given table.
Table Data Gateway (p. 164)
Used in conjunction with the Table Module pattern, the Table Data Gateway is the layer of code that retrieves a result set from the database to populate the table module and persist changes back to the database. In the .NET Framework, the DataAdapter class serves as the Table Data Gateway and a typed DataSet serves as the Table Module.
Active Record (p. 165)
Active Record is any object that wraps a record in a database table or view. The object is expected to incorporate both data and behavior. An AR object represents a row of data and typically embeds data access logic.
Foreign Key Mapping (p. 170)
An Active Record may model data that includes foreign key references to other entities. The Foreign Key Mapping pattern expands these relationships to return references to objects that represent the entities rather than the ID of the entity.
Row Data Gateway (p. 172)
The Row Data Gateway pattern delivers an object that acts as the direct interface between the wrapper AR class and the physical database. In other words, RDG will simply encapsulate all data access code for an Active Record, thus saving the Active Record class from knowing about database details.
Domain Model (p. 176)
The Domain Model pattern aims to get you an object model that is a conceptual model of the system. This object model is referred to as the domain model. The domain model describes the entities that participate in the system and captures any relationships and flow of data between them. Domain Model is totally independent of the database and is a model that is, by design, as close as possible to real processes and not an idealized model of how an architect would have processes go.
Repository (p. 188)
Use the Repository pattern to create a gateway through which code in the presentation or service layer can script the domain and update the underlying data store or load from there data that is represented through the objects in the model. Put another way, the Repository pattern adds yet another abstraction layer between the domain model and the data access code.
You typically have a repository object for each entity, such as customer, order, and product. The interface of the repository is up to you; but it will likely contain finder and update methods that work both on the single entity and on a collection of entities. Looking back on the Active Record pattern, we can say that the repository pattern offers a way to group all the CRUD methods that in Active Record belong to the entity class.
Special Case (p. 189)
The Special Case pattern recommends that you create ad hoc types that represent cases such as an entity that is not found. Rather than returning null to indicate that an entity was not found, an instance of the ad hoc type should be returned instead. This allows the method to provide a more precise answer about what happened.
Service Layer (Chapter 5, p. 193)
General Characteristics
- A service layer is an additional layer that sets a boundary between two interfacing layers.
- Orchestrates business components, application specific services, workflows, and any other special components in the business logic.
- Typically, it shields the presentation layer from all of the details regarding the business logic.
- Ideally, it returns DTOs and internally converts DTOs into instances of the Domain Model classes.
-
Allows, but does not require, the logic behind the service interface to be moved to a remote tier.
Service Layer (p. 205)
Addresses the need of having a common layer of simple operations for any external interface the application is required to support. The service layer contains operations that script the domain model and invoke application services in a sequence that is mostly determined by use cases. The service layer responds to input coming from the presentation layer. The presentation layer, in turn, doesn’t care much about the module that operates on the other end. All that matters to it is that the module does what it claims to be able to do.
Remote Facade (p. 213)
Remote Facade refers to a particular flavor of facade – a facade for remote objects. The benefits of this facade is that it allows you to create a coarse-grained interface over a set of fine-grained objects. You need a Remote Facade when you have to refactor the service layer to a more coarse-grained interface, when you need to use explicit DTOs in the signature, or both.
Data Transfer Object (p. 216)
A DTO is merely an object that carries data across an application’s boundaries with the primary goal of minimizing roundtrips. There are two main scenarios for the DTO pattern. One is minimizing roundtrips when you call remote objects; another is maintaining a loose coupling between the front end and the classes of your domain model, if you have one.
Adapter (p. 218)
Adapter essentially converts the interface of one class into another interface that a client expects. The Adapter pattern is widely applied to render a domain object into a DTO and vice versa.
Data Access Layer (Chapter 6, p. 251)
General Characteristics
- The DAL has to persist data to the physical storage and supply CRUD services to the outside world.
- The DAL is responsible for servicing any query requests that it receives.
- The DAL must be able to provide transactional semantics.
- The DAL must handle concurrency properly.
- Real database independence is achieved when you devise your DAL as a black box with a contracted interface and read the details of the current DAL implementation dynamically from configuration.
Separated Interface (p. 264)
In general terms, the Separated Interface pattern addresses the problem of separating the interface that describes a certain functionality from its implementation. The pattern prescribes that you use different packages (for example, assemblies in the .NET Framework) for the interface and any of its implementations. Only the definition of the interface is known to any assemblies that need to consume the functionality. Applied to the design of a data access layer, Separated Interface means that you define a contract for all the functionality you expose through the DAL. The contract is ultimately an interface compiled to its own assembly. The Separated Interface pattern is useful in any scenario where a given functionality of the application might be implemented through a different behavior based on runtime conditions. The DAL is just one of these cases.
Plugin (p. 267)
The Plugin pattern is described as a basic factory with some special features. In particular, the Plugin pattern suggests that you read the information about the type to create from an external configuration point. Another key point of the Plugin pattern is that the client that consumes the interface does not link the actual implementation. With regard to the DAL scenario, this means that the assembly that implements the service layer doesn’t include a reference to the assembly that contains the actual DAL component. The DAL assembly, instead, is loaded in memory dynamically on demand. In the .NET Framework, this means using a pinch of reflection.
Service Locator (p. 271)
The main Service Locator focus is achieving the lowest possible coupling between components. It represents a centralized console that an application uses to obtain all the external dependencies it needs. In doing so, you also incur the pleasant side effect of making your code more flexible and extensible.
Inversion of Control (p. 273)
The basic idea behind IoC is that the consumer of some functionality doesn’t bother managing dependencies itself but delegates this burden to a specialized component. The dependency injector finds the DAL implementation and makes it available to the consumer. Typically, the consumer class exposes a consutructor or a setter property through which the dependency injector can inject the references it finds.
Data Mapper (p. 281)
To persist in-memory changes, you need some mapping logic and a gateway to a DBMS-specific context. The Data Mapper pattern helps a lot here. In brief, a data mapper is the class that takes care of persistence on behalf of a given type.
Data mappers are essentially developer-created classes that fill the logical gap between the object model and the physical structure of databases.
Repository (p. 291)
Use the Repoisitory pattern to create a gateway through which code in the presentation or service layer can script the domain and update the underlying data store or load from there data that is represented through the objects in the model. Put another way, the Repository pattern adds yet another abstraction layer between the domain model and the data access code.
You typically have a repository object for each entity, such as customer, order, and product. The interface of the repository is up to you; but it will likely contain finder and update methods that work both on the single entity and on a collection of entities. Looking back on the Active Record pattern, we can say that the repository pattern offers a way to group all the CRUD methods that in Active Record belong to the entity class.
Identity Map (p. 305)
The Identity Map pattern is defined as a map that keeps track of all objects read from the database. The purpose is to return to callers an existing instance instead of running a new query. The identity map is hidden in the finder object that exposes the query services – a repository, the data context, or both. If the finder can get the requested object directly from the map, that’s fine. Otherwise, it gets a reference to the object from the database and then it saves the reference into the map for further requests.
Lazy Loading (p. 315)
The Lazy Loading pattern refers to an object that doesn’t hold all the data it needs, but knows how to retrieve it on demand. Applied to a domain model object, this pattern enables you to enhance the original class with the ability to prioritize data.
Presentation Layer (Chapter 7, p. 343)
General Characteristics
- The presentation layer must be able to survive any changes in the graphical user interface that do not require a change in the data flow and in the presentation logic.
- The presentation layer must also be independent from the UI technology and platform.
- The presentation layer should be tested to some (good) extent, just like any other part of the application.
-
Whatever model you choose for the data in the middle tier, the presentation layer should be unaffected
Choosing a Pattern (p. 372)
- Web – Stick to web forms and improve them with a bit of separation of concerns or move to a different model using ASP.NET MVC.
- Windows – MVP is the way to go if you are looking for a presentation pattern that goes beyond the basic capabilities offered by the .NET UI toolkit of your choice – be it Windows Forms, Windows Presentation Foundation, or Silverlight.
-
Multiple GUIs – MVP is the pattern that provides the best combination of testability, separation of concerns, maintenance, and code reuse. It is possible to write a presenter and reuse it all, or in large part, in Windows and ASP.NET.
Model-View-Controller (p. 353)
The primary goal of MVC is to split the application into distinct pieces – the model, the view, and the controller. The model refers to state of the application, wraps the application’s functionalities, and notifies the view of state changes. The view refers to the generation of any graphical elements displayed to the user, and it captures and handles any user gestures. The controller maps user gestures to actions on the model and selects the next view. These three actors are often referred to as the MVC triad.
Model2 (p. 362)
In a Model2 application, requests flow from the browser directly to a front controller implemented as an HTTP interceptor – in ASP.NET jargon, we would call it an HTTP module. In other words, the user interface of a Model2 application offers HTML input elements and all of them cause a link to be followed and an HTTP post to occur.
The front controller on the Web server captures the request and looks at it – in particular, it looks at the structure of the URL. based on the URL, the front controller figures out which MVC controller should be instantiated to service the request. After the controller has been identified, a method is invoked that can affect the model. As the controller’s method returns, the controller orders the view to render out to HTML. The view received fresh data for its response directly from the controller.
Model-View-Presenter (p. 364)
MVP is a derivative of MVC aimed at providing a cleaner separation between the view, the model, and the controller. Starting from the MVC triad, the creators of MVP neatly separated the model from the view/controller pair, which they call presentation. The core of MVP is the strictly regulated interaction taking place between the view and the controller. In MVP, the controller is renamed to presenter.
In MVP, the view and the model are neatly separated and the view exposes a contract through which the presenter accesses the portion of the view that is dependent on the rest of the system. Summarizing the situation further, we can say that MVP is a refinement of MVC based on three facts:
- The view doesn’t know the model
- The presenter ignores any UI technology behind the view
-
The view is mockable for testing purposes
Presentation Model (p. 370)
Presentation Model is another variation on MVP that is particularly suited to a rich and complex user interface. On the Windows platforms, PM works well with user interfaces build with Windows Presentation Foundation and Silverlight. In PM, the view doesn’t expose any interface, but a data model for the view is incorporated in the model. The model is not the business logic, but simply a class that represents the state of the view. The view elements are directly bound to properties on the model. In summary, in PM the view is passive and doesn’t implement any interface. The interface is transformed into a model class and incorporated into the presenter.
Page Controller (p. 372)
The traditional ASP.NET pattern requires you to use page classes as controllers. The page receives events from the view and processes them against the application logic. The page is also responsible for navigation. This traditional model can be improvbed with a deeper separation of concerns by using a manual implementation of MVC or MVP.