At Microsoft we are working hard in putting together the different components that are involved deciding the architecture approach. One of these components is the architecture frame, which together with the architecture style and application type creates the architectural baseline for modern design. This guideline is targeting architects and not developers, therefore the topics and recommendations cover high level decisions.
One of the most common situations when we sit down during reviews is that the team starts choosing the technology first without analysing what they really need. They usually end up in a model that fails to deliver value to the business and trigger a set of failures around the qualities of service. In order to avoid this, a proper review of the architecture frame can help architects and developers to foresee potential problems and chose the right technology and pattern based on the real execution context. The frame is pictured below with the section covered in part 1.
The following guideline can help you to identify the key issues on each area of the frame and some recommendations that we usually share with our customers in the ADC team (Application Development Consultant ). Note that this list is not exhaustive and are general suggestions that can help you on your design process.
Authentication & AuthorizationThis area is one of the most misunderstood concepts in the frame, as many architects confuse these concepts as they security has been a complex area to understand. Remember that the authentication process validates the user credentials and the authorization involves the roles and actions that the user has on the system. Designing a good strategy on this area helps to improve the security and reliability of the application.
· Lack of authentication across trust boundaries, as this leaves your application vulnerable to spoofing attacks, dictionary attacks and session hijacking.
· Granular or improper authorization can leave your application expose to vulnerabilities like information disclosure, data tampering and elevation of privileges.
Consider the following guideline:
· The most important consideration is performing a STRIDE analysis that may help to identify risks.
· Identify your trust boundaries; authenticate and authorize users and calls across trust boundaries. Some calls may need to be authenticated on the client and the server (mutual authentication).
· If your system involves multiple applications based on different user subset consider using a single sign-on strategy
· Do not store passwords in plain text; instead consider using hash codes or proper encrypted algorithms.
· Do not transmit passwords in plain text.
· Protect resources based on identity, groups or roles.
· Use role based authorization for business decisions.
· Use resource based authorization for system auditing.
· Use claims based authorization when you need to support federated environments or complex rights models.
· Consider using a trusted subsystem for access to back-end services to maximize the use of pooled connections.
· If the presentation and business layers are on the same machine and you need to keep the original caller’s ACL permission consider using impersonation. On the other hand, if they are on different machines consider using delegation. Note that this may affect performance.
· Implement IP filtering to protect your business layer.
· Consider the implications of using different trust settings for executing service code.
· Ensure that secure protocols such as SSL are used with basic authentication.
· Use secure mechanisms such as WS security with SOAP messages
· Run services under the most restrictive account that is appropriated
Use message-based encryption to protect sensitive data in a message.
Ensure that messages have not been tampered with in transit.
Data Origin Authentication
Validate the origin of a message as an advanced form of data integrity.
Prevent a service from exposing information about its internal implementation when an exception occurs
An integrated view of information distributed across multiple services and consumers.
Enforce message idempotency by preventing an attacker from intercepting a message and executing it multiple times
Check the content and values in messages to protect a service from malformed or malicious content
CachingThis is the developer’s favourite, we have seen many applications implementing caching patterns without considering why and where it is needed. Poorly designed caching strategy can lead to degradations on performance and responsiveness; on the other hand, good caching design can really speed up responsiveness and contributes to the concurrency and efficiency factor.
· Caching invalid content
· Caching sensitive data
· Incorrect choice of caching store
· You should use caching to optimize reference to lookups and avoid network round trips.
· Do not cache volatile data.
· Try to load the cache asynchronously to avoid call delays.
· Consider ready-to-use objects when storing them in the cache instead of raw database data.
· If you need to cache sensitive data use encryption as a simple memory dump can reveal it.
· If your application is deployed in a farm avoid using local caches that need to be synchronized, instead consider using transactional resource manager or a product that supports distributed caching.
· Do not depend on data still in your cache, as it may have been removed.
Use external information to determine the state of the data stored in a cache.
Improve the response time for dynamic pages that are accessed frequently
CommunicationThis area covers how the components on different layers will communicate with each other, the mechanism depends on the deployment scenario but the key issues are usually similar.
· Incorrect choice of transport protocol
· Chatty communications
· Failure to protect sensitive data
· When crossing physical boundaries consider using message based communications, on the other hand, for crossing logical boundaries you should use object based communication.
· If you are not concern about the order of the messages consider using asynchronous communication to unblock processing or UI threads
· Consider using message queuing to queue messages for later delivery to cover network interruptions. Remember that that this pattern supports transacted message delivery and supports reliable once-only delivery.
· Consider net pipes for inter-process communication, TCP for local networks and HTTP based protocols for external links.
· Determine how to handle unreliable or intermittent communication.
· Validate endpoint addresses in messages.
· Decide if a message communication needs to be two-way or one-way
A chain of composable filters that implement common pre-processing and post-processing tasks.
Pipes and Filters
Route messages through pipes and filters that can modify or examine the message as it passes through the pipe.
A programmatic interface that other systems can use to interact with the service.
Two-way message communication where both the service and the client send messages to each other independently, irrespective of the use of the one-way or the
Fire and Forget
A one-way message communication mechanism used when no response is expected.
End-to-end reliable transfer of messages between a source and a destination, regardless of the number or type of intermediaries that separate endpoints.
A two-way message communication mechanism where the client expects to receive a response for every message sent.
A component that can access the application's API or data and publish messages on a channel based on this data, and can receive messages and invoke functionality inside the application.
Structure the connecting middleware between applications as a communication bus that enables them to work together using messaging.
A component that connects messaging systems and replicates messages between these systems.
Point to point channel
Send a message on a Point-to-Point Channel to ensure that only one receiver will receive a particular message.
Create a mechanism to send messages only to the applications that are interested in receiving the messages without knowing the identity of the receivers
A message structure used to support commands
A structure that provides reliable asynchronous event notification between applications.
Set multiple consumers on a single message queue and have them compete for the right to process the messages, which allows the messaging client to process multiple messages concurrently
In a disconnected scenario, messages are saved and then made accessible to the client when connecting to the message channel to provide guaranteed delivery.
Ensure that a service will only handle a message once
A component that sends messages to multiple consumers.
Encapsulate message-based calls into a single interface in order to separate it from the rest of the application code.
Transform requests into business objects for incoming messages, and reverse the process to convert business objects into response messages.
A service consumer that checks the channel for messages at regular intervals.
The service consumer uses filters to receive messages that match specific criteria.
A service that receives asynchronous requests to invoke operations in business components.
A client that can implement transactions when interacting with a service.
CompositionThis part of the frame is very important an it has a significant impact in the user experience. The composition is the process used to define how the user interfaces are structured in order to provide a consistent look and feel.
· Cooperating UI 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 update and dynamic modules loading difficult due hardcoded dependencies.
· Use abstraction patterns when possible to avoid issues with dependencies between components.
· Consider creating templates with placeholders, a good approach may be using the template view pattern to compose dynamic screens to ensure reuse and consistency.
· Consider composing views from reusable modular parts like user controls. The composite view pattern is a good approach to build a view from atomic component parts.
· Avoid using dynamic layout as they are difficult to maintain.
· If you need to allow communication between presentation components consider using the pub/sub pattern, this will lower the coupling between components and improve testability.
Concurrency and TransactionsDesigning good concurrency and transaction models is a key part of the architecture as it directly affects performance (specially concurrency). Identifying the right requirements in order to select optimistic or pessimistic models can be a critical decision. There are plenty of documentation and best practices around this area but from the architectural perspective we should worry about the general issues that are system may face.
· Nonexistent or poor protection for concurrent access to static data
· Deadlocks caused by improper locking
· Not choosing the right data concurrency model.
· Long running transactions that hold locks on data
· Using pessimistic/exclusive locks when not necessary
Consider the following guideline around transactions:
· Wrap business critical operations in transactions
· Use connection based transactions when accessing a single data source
· Don’t be afraid of using transaction scopes to manage transactions on multiple data sources.
· Avoid holding locks for long periods.
· If transactions are not an option consider the use of compensation methods to revert the data to the previous state.
Consider the following guideline around concurrency:
· Remember that static data is not thread safe.
· Updates to shared data should be mutually exclusive; this will prevent two threads from attempting to update the data at the same time.
· Locks should be scoped at a very fine grained level, the idea is to lock only when is necessary and release it as soon as possible.
· If you need to update static fields use the check-lock-check pattern as other thread may have changed the value between your check and your lock.
· Never apply a lock to a type definition or the current instance. Using these constructs can lead to deadlock issues that are difficult to locate. Use private static objects as locking reference instead.
· Consider the use of the synchronization support provided by collections when working with shared ones.
Capture transaction details
User DB objects such as triggers and shadow tables to record changes to all tables belonging to the transaction.
Optimistic offline lock
Ensure that changes made by one session do not conflict with changes made by other session.
Pessimistic offline lock
Prevents conflicts by forcing a exclusive lock
Coarse Grained lock
Lock a set of related objects with a single lock
Organize the business logic for each transaction in a single procedure, making calls directly to the database or through a thin database wrapper.
Use framework code to acquire locks on behalf of code that accesses shared resources.
On the next blog entry we are going to explore the second subset of considerations using the architecture frame.
You can find part 2 here.