Performance Design Principles - Coupling and Cohesion

From Guidance Share
Jump to navigationJump to search

- J.D. Meier, Srinath Vasireddy, Ashish Babbar, Rico Mariani, and Alex Mackman


Design for Loose Coupling

Aim to minimize coupling within and across your application components. If you have tight coupling and need to make changes, the changes are likely to ripple across the tightly coupled components. With loosely coupled components, changes are limited because the complexities of individual components are encapsulated from consumers. In addition, loose coupling provides greater flexibility to choose optimized strategies for performance and scalability for different components of your system independently.

There may be certain performance-critical scenarios where you need to tightly couple your presentation, business, and data access logic because you cannot afford the slight overhead of loose coupling. For example, code inlining removes the overhead of instantiating and calling multiple objects, setting up a call stack for calling different methods, performing virtual table lookups, and so on. However, in the majority of cases, the benefits of loose coupling outweigh these minor performance gains.

Some of the patterns and principles that enable loose coupling are the following:

  • Separate interface from implementation. Providing facades at critical boundaries in your application leads to better maintainability and helps define units of work that encapsulate internal complexity.

For a good example of this approach, see the implementation of the "Exception Management Application Block for .NET" on MSDNĀ®, at http://msdn.microsoft.com/library/en-us/dnbda/html/emab-rm.asp.

  • Message-based communication. Message queues support asynchronous request invocation, and you can use a client-side queue if you need responses. This provides additional flexibility for determining when requests should be processed.


Design for High Cohesion

Logically related entities, such as classes and methods, should be grouped together. For example, a class should contain a logically related set of methods. Similarly, a component should contain logically related classes.

Weak cohesion among components tends to result in more round trips because the classes or components are not logically grouped and may end up residing in different tiers. This can force you to require a mix of local and remote calls to complete a logical operation. You can avoid this with appropriate grouping. This also helps reduce complexity by eliminating complex sequences of interactions between various components.


Partition Application Functionality into Logical Layers

Using logical layers to partition your application ensures that your presentation logic, business logic, and data access logic are not interspersed. This logical organization leads to a cohesive design in which related classes and data are located close to each other, generally within a single boundary. This helps optimize the use of expensive resources. For example, co-locating all data access logic classes ensures they can share a database connection pool.


Use Early Binding Where Possible

Prefer early binding where possible because this minimizes run-time overhead and is the most efficient way to call a method.

Late binding provides a looser coupling, but it affects performance because components must be dynamically located and loaded. Use late binding only where it is absolutely necessary, such as for extensibility.


Evaluate Resource Affinity

Compare and contrast the pros and cons. Affinity to a particular resource can improve performance in some situations. However, while affinity may satisfy your performance goals for today, resource affinity can make it difficult to scale your application. For example, affinity to a particular resource can limit or prevent the effective use of additional hardware on servers, such as more processors and memory. Server affinity can also prevent scaling out.

Some examples of affinity that can cause scalability problems include the following:

  • Using an in-process state store. As a result of this, all requests from a specific client must be routed to the same server.
  • Using application logic that introduces thread affinity. This forces the thread to be run on a specific set of processors. This hinders the ability of the scheduler to schedule threads across the processors, causing a decrease in performance gains produced by parallel processing.