Note that the information on this page is the BETA 1 of a guide that is now released. See
http://www.codeplex.com/AppArchGuide for the latest PDF and HTML content.
Architecture and Design
- J.D. Meier, Alex Homer, David Hill,
Prashant Bansode, Lonnie Wall, Rob Boucher Jr, Akshay Bogawat
Software architecture is often described as the organization or structure of a system, while the system represents a collection of components that accomplish a specific function or set of functions. In other words, architecture is focused on organizing components
to support specific functionality. This organization of functionality is often referred to as grouping components into “areas of concern”. The following diagram illustrates common application architecture with components grouped by different areas of concern.
In addition to the grouping of components, other areas of concern focus on interaction between the components and how different components work together. The guidelines in this chapter examine different areas of concern that you should consider when designing
the architecture of your application.
Key Design Principles
- Separation of Concerns. Break your application into distinct features that overlap in functionality as little as possible.
- Single Responsibility Principle. Each component or a module should be responsible for only a specific feature or functionality
- Principle of least knowledge. A component or an object should not know about internal details of other components or objects. Also known as the Law of Demeter** (LOD).
- Don’t Repeat Yourself (DRY). There should be only one component providing a specific functionality, the functionality should not be duplicated in any other component. Often abbreviated as “DRY”.
- Avoid doing a big design upfront. If you are not clear with requirements or if there are possibility of design evolution. This type of design often abbreviated as “BDUF”.
- Prefer Composition over Inheritance. For reusing the functionality prefer using composition over inheritance, wherever possible, as inheritance increases dependency between parent and child classes limiting the reuse of child classes.
When designing an application or system, the goal of a software architect is to minimize the complexity by separating things into different areas of concern. For example, the user interface (UI), business processing, and data access all represent different
areas of concern. Within each area, the components you design should focus on that specific area and should not mix code from other areas of concern. In other words, UI processing components should not include code that directly accesses a data source. Instead
UI processing components should use either business components or data access components to retrieve data.
The following guidelines should be followed when designing an application:
- Avoid doing big design upfront. If you are not clear with requirements or if there are possibility of design evolution, it might be a good idea not to do complete design upfront, rather evolve the design as you progress through the project.
- Separate the areas of concern. Break your application into distinct features that overlap in functionality as little as possible. The main benefit is that a feature or functionality can be optimized independently of other features or functionality.
Also if one feature fails it won’t cause other features to fail as well, and they can run independently. It also helps to make the application easier to understand, design and manage complex interdependent systems.
- Each component or module should have single responsibility. Each component or a module should be responsible for only a specific feature or functionality. This makes your components cohesive helping to optimize the components if a specific feature
or functionality changes.
- A component or an object should not know about internal details of other components or objects. A component or an object should** call a method of another object or component, and that method should have information about how to process the request
and if needed route to appropriate sub-components or other components. This helps in developing a application that is more maintainable and adaptable.
- Do not duplicate functionality within an application. There should be only one component providing a specific functionality, the functionality should not be duplicated in any other component. Duplication of functionality within application leads
to difficulty to change, decrease in clarity and potential inconsistency.
- Identify the kinds of components you will need in your application. The best way to do this is to identify patterns that match your scenario and examine the types of components that are used by the pattern or patterns that match your scenario. For
example, a smaller application may not need business workflow or UI processing components.
- Group different types of components into logical layers. Within a logical layer, the design of components should be consistent for a particular type. For example, if you choose to use the Table Data Gateway pattern to create an object that acts as
a gateway to a table in a data source for data access, you should not include another pattern like Query Object to define an object that represents a database query.
- You should not mix different types of components in the same logical layer. For example, the User Interface (UI) layer should not contain business processing components. Instead, the UI layer should contain components used to handle user input
and process user requests.
- Determine the type of layering you want to enforce. In a strict layering system, components in layer A cannot call components in layer C; they always call components in layer B. In a more relaxed layering system, components in a layer can call components
in other layers that are not immediately below it. In all cases, you should avoid upstream calls and dependencies.
- Use abstraction to implement loose coupling between layers. This can be accomplished by defining interface components such as a façade with well-known inputs and outputs that translates requests into a format understood by components within
the layer. In addition, you can also use Interface types or abstract base classes to define a common interface or shared abstraction (Dependency Inversion) that must be implemented by interface components.
- Do not overload the functionality of a component. For example, a UI processing component should not contain data access code. A common anti-pattern named Blob is often found with base classes that attempt to provide too much functionality. A Blob
object will often have hundreds of functions and properties providing business functionality mixed with cross-cutting functionality such as logging and exception handling. The size is caused by trying to handle different variations of child functionality requirements,
which requires complex initialization. The end result is a design that is very error prone and difficult to maintain.
- Understand how components will communicate with each other. This requires an understanding of the deployment scenarios your application will need to support. You need to determine if communication across physical boundaries or process boundaries
should be supported, or if all components will run within the same process.
- Do not create value types with operations that attempt to modify fields in the type. Doing so creates a mutable value type; however, all value types should be immutable. Accessing a value type gives you a copy, which means that mutable fields
will not behave the same as they would in reference types, leading to bugs that are extremely difficult to track down.
- Prefer composition over inheritance. For reusing the functionality prefer using composition over inheritance, wherever possible, as inheritance increases dependency between parent and child classes limiting the reuse of child classes. This also reduces
the inheritance hierarchies which can become quite hard to deal with.
- Keep the data format consistent within a layer or component. Mixing data formats will make the application more difficult to implement, extend, and maintain. Every time you need to move data from one format to another you are required to implement
translation code to perform the operation.
- Keep cross-cutting code abstracted from the application business logic as much as possible. Cross-cutting code refers to code related to security, communications, or operational management such as logging and instrumentation. Attempting to mix this
code with business logic can lead to a design that is difficult to extend and maintain. Changes to the cross-cutting code would require touching all of the business logic code that is mixed with the cross-cutting code. Consider using frameworks that can help
implement the cross-cutting concerns
- Be consistent in the naming conventions used. You should check if naming standards have been established by the organization. If not, you should establish common standards that will be used for naming. This provides a consistent model that makes
it easier for team members to evaluate code they did not write, which leads to better maintainability.
- Establish the standards that should be used for exception handling. For example, you should always catch exceptions at layer boundaries, you should not catch exceptions within a layer unless you can handle them there, and you should not use exceptions
to implement business logic. The standards should also include policies for logging and instrumentation as related to exceptions.
|Authentication and Authorization
||Lack of authentication across trust boundaries.
||Lack of authorization across trust boundaries.
||Granular or improper authorization.
||Caching data that is volatile.
||Caching sensitive data.
||Incorrect choice of caching store.
||Incorrect choice of transport protocol.
||Chatty communication across physical and process boundaries.
||Failure to protect sensitive data.
||Cooperating application modules are coupled by dependencies making development, testing, and maintenance more difficult.
||Dependency changes between modules forces code recompilation and module redeployment.
||Dynamic UI layout and update difficult due to hardcoded dependencies.
||Dynamic module loading difficult due to hardcoded dependencies.
|Concurrency and Transactions
||Not protecting concurrent access to static data.
||Deadlocks caused by improper locking.
||Not choosing the correct data concurrency model.
||Long running transactions that hold locks on data.
||Using exclusive locks when not required.
||Lack of or incorrect configuration information.
||Not securing sensitive configuration information.
||Unrestricted access to configuration information.
|Coupling and Cohesion
||Incorrect grouping of functionality.
||No clear separation of concerns.
||Tight coupling across layers.
||Per user authentication and authorization when not required.
||Chatty calls to the database.
||Business logic mixed with data access code.
||Leaving the application in an unstable state.
||Revealing sensitive information to the end user.
||Using exceptions for application logic.
||Not logging sufficient details about the exception.
||Incorrect grouping of components within a layer.
||Not following layering and dependency rules.
||Not considering the physical distribution of layers.
|Logging and Instrumentation
||Lack of logging and instrumentation.
||Logging and instrumentation that is too fine-grained.
||Not making logging and instrumentation an option that is configurable at runtime.
||Not suppressing and handling logging failures.
||Not logging business critical functionality.
||Using an incorrect state store.
||Not considering serialization requirements.
||Not persisting state when required.
||Choosing the incorrect structure for your scenario.
||Creating an overly complex structure when not required.
||Not considering deployment scenarios.
||Not following published guidelines.
||Not considering accessibility
||Creating overloaded interfaces with un-related functionality.
||Lack of validation across trust boundaries.
||Not validating for all appropriate aspects of parameters, such as “Range”, “Type” and “Format”.
||Not reusing validation logic.
||Not considering management requirements.
||Choosing an incorrect workflow pattern.
||Not considering exception states and how to handle them.
Designing a good authentication strategy is important for the security and reliability of your application. Failing to design and implement a good authentication strategy can leave your application vulnerable to spoofing attacks, dictionary attacks, session
hijacking, and other types of attack.
When designing an authentication strategy, consider following guidelines:
- If you have multiple systems within the application, consider a single sign-on strategy.
- Identify your trust boundaries; authenticate users and calls across trust boundaries.
- Do not store passwords in a database or data store as plain text. Instead, store a hash of the password.
- Enforce the use of strong passwords or password phrases.
- Do not transmit passwords over the wire in plain text.
- Protect your authentication cookie using encryption.
- Never display passwords in clear-text, either to the user or customer service. Instead provide password reset options
Designing a good authorization strategy is important for the security and reliability of your application. Failing to design and implement a good authorization strategy can make your application vulnerable to information disclosure, data tampering, and elevation
When designing an authorization strategy, consider following guidelines:
- Identify your trust boundaries; authorize users and callers across trust boundary.
- Protect resources by applying authorization to callers based on their identity, groups, or roles.
- Consider authorization granularity for your application scenario, too fine granularity increases management overheads and too coarse granularity reduces flexibility.
- Use role-based authorization for business decisions.
- Use resource-based authorization for system auditing.
- Use claims-based authorization when you need to support federated authorization based on a mixture of information such as identity, role, permissions, rights, and other factors.
- Use a trusted sub-system wherever possible.
- Avoid using impersonation and delegation.
- When using delegation, use constrained delegation.
Caching improves the performance and responsiveness of your application. However, a poor design can degrade performance and responsiveness. You should use caching to optimize reference data lookups, avoid network round trips, and avoid unnecessary and duplicate
processing. To implement caching you must decide when to load the cache data. Try to load the cache asynchronously or by using a batch process to avoid client delays.
When designing caching, consider following guidelines:
- Choose an appropriate cache location.
- Avoid affinity to a server.
- Avoid distributed coherent caches.
- Do not cache volatile data.
- Consider using ready-to-use cache data when working with an in-memory cache. For example, use a specific object instead of caching raw database data.
- Differentiate between cached data and state.
- Do not cache sensitive data.
- Evaluate the capabilities of your cache provider for caching sensitive data, and choose the one which satisfies your requirement.
- Evaluate the usage patterns and update frequency, to decide if caching will have the required positive performance impact.
- Use the appropriate combination of absolute expiration and sliding expiration settings.
- Be deliberate while choosing time based expiration, too short expiration time might degrade the performance of your application and too long a duration might force your application to use stale data.
- Evaluate and use appropriate cache dependencies such as SQL dependencies and file dependencies.
- Consider automatically removing (scavenging) cached items when working with memory based cache.
- Consider manually removing (explicitly flushing) cached items when working with disk based cache.
- Do not depend on data being in your cache.
- If you have a persistent cache, consider proactive loading.
- If you have an in-memory cache, avoid proactive loading unless the cached item is expensive to recreate.
Communication concerns the interaction between components across different boundary layers. The mechanism you choose depends on the deployment scenarios your application must support. When crossing physical boundaries, you should use message-based communication.
When crossing logical boundaries, you should use object-based communication.
When designing communication mechanisms, consider the following guidelines:
- Choose the appropriate remote communication mechanism.
- Design chunky interfaces.
- Consider how to pass data between layers.
- Minimize the amount of data sent across the wire.
- Batch work to reduce calls over the network.
- Reduce transitions across boundaries.
- Consider using asynchronous communication to unblock your processing threads.
- Consider using message queuing for reliable messaging.
- Consider using "fire and forget" invocation model for one way communication.
Composition is the process used to define how interface components in a user interface are structured to provide a consistent look and feel for the application. One of the goals with user interface design is to provide a consistent interface in order to avoid
confusing users as they navigate through your application. This can be accomplished by using templates, such as a master page in ASP.NET, or by implementing one of many common design patterns.
When designing for composition, consider the following guidelines:
- Avoid using dynamic layouts. They can be difficult to load and maintain.
- Be careful with dependencies between components. Use abstraction patterns when possible to avoid issues with maintainability.
- Consider creating templates with placeholders. For example use the Template View pattern to compose dynamic web pages to ensure reuse and consistency.
- Consider composing views from reusable modular parts. For example use the Composite View pattern to build a view from modular, atomic component parts.
- Use well-known design patterns to implement a composite interface containing separate modules or user controls where appropriate.
Concurrency and Transactions
When designing for concurrency and transactions related to accessing a database it is important to identify the concurrency model you want to use and determine how transactions will be managed. For concurrency, you can choose between an optimistic model where
the last update applied is valid, or a pessimistic model where updates can only be applied to the latest version. For example, if two people modify a file, and one person updates that file, the other person will not be allowed to apply an update to the original
version. Transactions can be executed within the database, or they can be executed in the business layer of an application. Where you choose to implement transactions depends on your transactional requirements.
When designing concurrency and transactions, consider the following guidelines:
- If you have business critical operations, consider wrapping them in transactions.
- Use connection-based transactions when accessing a single data source.
- Use Transaction Scope (System.Transaction) to manage transactions that span multiple data sources.
- Where you cannot use transactions, implement compensating methods that revert the data store to its previous state.
- Avoid holding locks for long periods – for example when using long-running atomic transactions.
- Consider using compensating locks for long running transactions.
- Consider whether to use optimistic or pessimistic locking.
- Choose the appropriate transaction isolation level.
- Consider using asynchronous transactions, if the transaction is split across multiple components or it’s a long running transaction.
- Ensure that service interfaces are idempotent so that your application will not reach an inconsistent state if the same message is received twice.
- If implementing transaction across systems, consider using industry standard protocols
Concurrency should also be considered when accessing static data within the application or when using threads to perform asynchronous operations. Static data is not thread-safe, which means that changes made in one thread will affect other threads using the
same data. Threading in general requires careful consideration when it comes to manipulating data that is shared by multiple threads and applying locks to that data.
When designing for concurrency at the application code level, consider the following guidelines:
- Updates to shared data should be mutually exclusive, which is accomplished by applying locks or using thread synchronization. This will prevent two threads from attempting to update shared data at the same time.
- Locks should be scoped at a very fine grained level. In other words, you should implement the lock just prior to making a modification and then release it immediately.
- When modifying static fields you should check the value, apply the lock, and check the value again before making the update. It is possible for another thread to modify the value between the point that you check the field value and the point that you apply
- Locks should not be applied against a type definition or instance of a type. In other words, you should not use lock (typeof(foo)) or Monitor.Enter(typeof(foo)) and you should not use lock(this) or Monitor.Enter(this). Using these constructs can lead to
deadlock issues that are difficult to locate. Instead, define a private static field within the type and apply locks against the private field. You can use a common object instance when locking access to multiple fields or you can lock a specific field.
- You can read static or shared data without applying locks around the operation or using synchronization.
- Use synchronization support provided by collections when working with static or shared collections.
Designing a good configuration management mechanism is important for the security and flexibility of your application. Failing to do so can make your application vulnerable to a variety of attacks, and also leads to an administrative overhead for your application.
When designing configuration management, consider following guidelines:
- Identify the configuration requirements for your application.
- Choose an appropriate configuration store.
- Secure your configuration store.
- Restrict access to your configuration information.
- Provide a separate administrative UI for editing configuration information.
- Use least-privileged process and service accounts.
- Consider when you want the configuration changes to take effect.
Coupling and Cohesion
When designing components for your application, you should ensure that these components are highly cohesive, and that loose coupling is used across layers. Coupling is concerned with dependencies and functionality. When one component is dependent upon another
component, it is tightly coupled to that component. Functionality can be decoupled by separating different operations into unique components. Cohesion concerns the functionality provided by a component. For example, a component that provides operations for
validation, logging, and data access represents a component with very low cohesion. A component that provides operations for only logging represents high cohesion.
When designing for coupling and cohesion, consider the following guidelines:
- Partition application functionality into logical layers.
- Design for loose coupling across layers in the application.
- Design for tight coupling within layers unless dynamic behavior requires loose coupling.
- Design for high cohesion. Components should contain only functionality specifically related to that component.
- Use abstraction to implement loose coupling between layers. This can be accomplished with interface components, common interface definitions, or shared abstraction where concrete components depend on abstractions and not on other concrete components (the
principle of Dependency Inversion).
- Know the overhead of designing abstract or loosely-coupled interface.
- Know the benefits of loosely coupled interfaces. These benefits include a shortened dependency chain, and a simplified build process.
Designing an application to use a separate data access layer is important for maintainability and extensibility. The data access layer should be responsible for managing connections with the data source and executing commands against the data source. Depending
on your business entity design, the data access layer may have a dependency on business entities; however, the data access layer should never be aware of business processes or workflow components.
When designing data access components, consider the following guidelines:
- Do not couple your application model to your database schema.
- Open connections as late as possible and release them as early as possible.
- Enforce data integrity in the database, not through data layer code.
- Move code that makes business decisions to the business layer.
- Avoid accessing the database directly.
- Consider isolating resources outside your application (for example, by using an object that acts as a gateway to the resource).
Designing a good exception management strategy is important for the security and reliability of your application. Failing to do so can make your application vulnerable to denial of service (DoS ) attacks, and may also allow it to reveal sensitive and critical
information. Raising and handling exceptions is an expensive process. It is important that the design also takes into account the performance considerations. A good approach is to design a centralized exception management and logging mechanism, and consider
providing access points within your exception management system to support instrumentation and centralized monitoring that assists system administrators.
When designing an exception management strategy, consider following guidelines:
- Differentiate between boundary exceptions and internal exceptions.
- Do not catch internal exceptions unless you can handle them.
- Centralize your approach for handling boundary exceptions.
- Consider if you need to transform exceptions at the boundary.
- Design an appropriate exception propagation strategy.
- Design a strategy for dealing with unhandled exceptions.
- Design an appropriate exception logging strategy.
- Design an appropriate notification strategy for critical errors and exceptions.
- Do not reveal sensitive information in exception messages and log files.
The use of layers in a design allows you to separate functionality into different areas of concern. In other words, layers represent the logical grouping of components within the design. You should also define guidelines for communication between layers. For
example, layer A can access layer B, but layer B cannot access layer A.
When designing layers, consider the following guidelines:
- Layers should represent a logical grouping of components. For example, use separate layers for user interface, business logic, and data access components.
- Establish guidelines for communication between layers.
- Components within a layer should be cohesive. In other words, the business layer components should provide only operations related to application business logic.
- When designing the interface for each layer, consider physical boundaries. If communication crosses a physical boundary to interact with the layer, use message-based operations. If communication does not cross a physical boundary, use object-based operations.
- For Web applications, implement a message-based interface between the presentation and business layers, even when the layers are not separated by a physical boundary. A message-based interface is better suited to stateless Web operations, provides a façade
to the business layer, and allows you to physically decouple the business tier from the presentation tier if this is required by security policies or in response to a security audit.
- Consider using an Interface type to define the interface for each layer. This will allow you to create different implementations of that interface to improve testability.
Logging and Instrumentation
Designing a good logging and instrumentation strategy is important for the security and reliability of your application. Failing to do so can make your application vulnerable to repudiation threats, where users deny their actions. Log files may be required
for legal proceedings to prove the wrongdoing of individuals.
You should audit and log activity across the tiers of your application. Using logs, you can detect suspicious activity. This frequently provides an early indication of a serious attack. Generally, auditing is considered most authoritative if the audits are
generated at the precise time of resource access, and by the same routines that access the resource.
When designing a logging and instrumentation strategy, consider following guidelines:
- Centralized your logging and instrumentation mechanism.
- Choose appropriate log sinks or targets that will receive log entries.
- Design instrumentation within your application to detect system and business critical events.
- Consider how you will access and pass auditing and logging data across application tiers.
- Consider how you will flow user identity across tiers.
- Create secure log file management policies.
- Back-up and analyze log files regularly.
- Do not store sensitive information in the log files.
State management concerns the persistence of data that represents the state of a component, operation, or step in a process. State data can be persisted using different formats and stores. The design of a state management mechanism can affect the performance
of your application. You should only persist data that is required, and you must understand the options that are available for managing state.
When designing a state management mechanism, consider following guidelines:
- Choose an appropriate state store.
- Only persist the data required to maintain state.
- Design a state management mechanism that is appropriate deployment on a Web farm if necessary.
- If you have to persist the state or share it across the network, consider using serialization.
- Understand how view state affects the size of a Web page in ASP.NET.
Software architecture is often defined as being the structure or structures of an application. When defining these structures, the goal of a software architect is to minimize the complexity by separating items into areas of concern using different levels of
abstraction. You start by examining the highest level of abstraction while identifying different areas of concern. As the design evolves, you dive deeper into the levels, expanding the areas of concern, until all of the structures have been defined.
When designing the application structure, consider the following guidelines:
- Identify common patterns used to represent application structure such as Client/Server, and N-Tier.
- Understand security requirements for the environment in which your application will be deployed. For example, many security policies require physical separation of presentation logic from business logic across different sub-nets.
- Consider scalability and reliability requirements for the application.
- Consider deployment scenarios for the application.
Designing for an effective user experience can be critical to the success of your application. If navigation is difficult, or users are directed to unexpected pages, the user experience can be negative.
When designing for an effective user experience, consider the following guidelines:
- Consider using published user interface guidelines. In many cases, an organization will have published guidelines that you should adhere to.
- Design for a consistent navigation experience. Use composite patterns for the look-and-feel, and controller patterns such as MVC, Supervising Controller, and Passive View, for UI processing.
- Design the interface so that each page or section is focused on a specific task.
- Consider breaking large pages with a lot of functionality into smaller pages.
- Design similar components to have consistent behavior across the application. For example, a grid used to display data should implement a consistent interface for paging and sorting the data.
Designing an effective validation mechanism is important for the security and reliability of your application. Failing to do so can make your application vulnerable to cross-site scripting, SQL injection, buffer overflow, and other types of malicious input
attack. However, there is no definitive definition of what constitutes valid input or malicious input. In addition, how your application actually uses the input influences the risks associated with exploit of the vulnerability.
When designing a validation mechanism, consider following guidelines:
- Identify your trust boundaries, validate all inputs across trust boundary.
- Centralize your validation approach, if it can be reused.
- Assume all input is malicious.
- Choose appropriate validation techniques.
- Do not rely on only client-side validation.
- Constrain, reject, and sanitize your input.
Workflow components are used when an application must execute a series of information processing tasks that are dependent on the information content. The values that affect information process steps can be anything from data checked against business rules,
to human interaction and input. When designing workflow components, it is important to consider the options that are available for management of the workflow.
When designing a workflow component, consider the following guidelines:
- Determine management requirements. If a business user needs to manage the workflow, you require a solution that provides an interface that the business user can understand.
- Determine how exceptions will be handled.
- With human workflow, consider the un-deterministic nature of users. In other words, you cannot determine when a task will be completed, or if it will be completed correctly.
- Use service interfaces to interact with external workflow providers.
- If supported, use designers and metadata instead of code to define the workflow.
|Authentication and Authorization
||Federated Authentication (Single Sign On or SSO)
||Pipes and Filters
|Concurrency and Transactions
||Capture Transaction Details
||Coarse Grained Lock
||Optimistic Offline Lock
||Pessimistic Offline Lock
|Coupling and Cohesion
||Inversion of Control
||Row Data Gateway
||Table Data Gateway
|Logging and Instrumentation
||Platform as a Service (PaaS)
||Software Services (SS)
||Software as a Service (SaaS)
||Chain of Responsibility
- Active Record – Include data access object within a domain entity.
- Adapter – Convert the interface of a class into another interface that clients expect.
- Asynchronous Callback – Execute long running tasks in a separate operation while providing a function for the thread to call back into when the task is complete.
- Brokered Authentication – Perform authentication through a broker, which provides a token to use for authentication when accessing services or systems.
- Capture Transaction Details – Create database objects, such as triggers and shadow tables, to record changes to all tables belonging to the transaction.
- Chain of Responsibility – Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request.
- Cache Dependency – Use external information to determine the state of data stored in a cache.
- Client/Server – An architectural pattern that defines a structure with two components, a client and a server.
- Coarse Grained Lock – Lock a set of related objects with a single lock.
- Command – Encapsulate request processing in a separate command object with a common execution interface.
- Composite View – Combine individual views into a composite representation.
- Context Object – Implement an object used to manage the current execution context.
- Data Mapper – Implement a mapping layer between objects and the database structure that is used to move data from one structure to another while keeping them independent.
- Data-driven workflow – Implement a workflow based on the state of data.
- Dependency Injection – Use a base class or interface to define a shared abstraction that can be used to inject object instances into components that interact with the shared abstraction interface.
- Direct Authentication – Authenticate directly against the service or system that is being accessed.
- Entity Translator – Implement an object that transforms message data types to business types for requests and reverses the transformation for responses.
- Exception Shielding – Filter exception data that should not be exposed to external systems or users.
- Façade – Implement a unified interface to a set of operations to reduce coupling between systems.
- Federated Authentication – A form of brokered authentication that supports Single-Sign-On (SSO) against multiple services or systems.
- Implicit Lock – Use framework code to acquire locks on behalf of the code accessing shared resources.
- Intercepting Filter - Create a chain of composable filters (independent modules) to implement common pre-processing and post-processing tasks during a Web page request.
- Layered Architecture – An architectural pattern where a system is organized into layers.
- N-Tier – An architectural pattern where the layers of a design can be distributed across physical boundaries.
- Optimistic Offline Lock – Ensures that changes by one session do not conflict with changes made by another session.
- Page Cache –Use a page cache to improve the response time for dynamic Web pages that are accessed frequently, but change less often and consume a large amount of system resources to construct.
- Pessimistic Offline Lock – Prevent conflicts by forcing a transaction to obtain a lock on data before using it.
- Pipes and Filters – A pattern where messages are routed through pipes and filters that can modify or examine the message as it passes through the pipe.
- Provider – Crceate a component that implements an API that is different from the client API, and allows any custom implementation to be seamlessly plugged in.
- Query Object – Define an object that represents a database query.
- Repository – An in-memory representation of a data source that works with domain entities.
- Row Data Gateway – Create an object that acts as a gateway to a single record in a data source.
- Service Interface – A programmatic interface that systems used to interact with other systems.
- Service Layer – An architectural design pattern where the service interface and implementation is grouped into a single layer.
- Table Data Gateway – Create an object that acts as a gateway to a table in a data source.
- Template View – Implement a common template view, and derive or construct views using the template view.
- Transform View – Transform the data passed to the presentation tier into HTML to be displayed on UI.
- Two-Step View – Transform the model data into a logical presentation without any specific formatting and then convert that logical presentation into the actual formatting required.
- For more information on authentication, see Designing Application-Managed Authorization at
- For more information on caching, see Caching Architecture Guide for .NET Framework Applications at -
- For more information on data access and data access components, see .NET Data Access Architecture Guide at
- For more information, see Designing Data Tier Components and Passing Data Through Tiers at
- For more information, see Enterprise Solution Patterns Using Microsoft .NET at
- For more information, see Integration Patterns at
- For more information, see Service Orientation Patterns at
- For more information on exception management, see Exception Management Architecture Guide at
- For more information on performance design and performance reviews, see Design Guidelines for Application Performance at
- For more information, see Architecture and Design Review of a .NET Application for Performance and Scalability at
- For more information on security and security reviews, see Architecture and Design Review for Security at
- For more information, see Design Guidelines for Secure Web Applications at