Monday, 11 January 2010

Framework Design Guidelines by Krzysztof Cwalina

I have just started reading a great book about Software Architecture by Cwalina Krzysztof. Here are some notes I have made from a presentation on the CD that comes with the book. Unfortunately an electronic version of the presentation was not available.  The book goes into the Design aspects in great depth and it is a great read.

The Processes Affecting Framework APIs are
- Organization
- Planning
- Architecture
- Design
- Development

Organization: The most powerful design tool

The size of the organization will determine if you have a large or a small set of APIs

Project Management Triangle: Scope Cost Time
Assumes costs are uniform ie add/remove 1 person has a proportional effect
Project Management Square: Scope Cost Time Organization

Organizational Influences
- Size of the Organization
  - Small Team -> Simple Design
  - Large Team -> Powerful Design
- Organization's Culture/Biases
  - Customer-Focused -> End-2-End Scenarios
  - Technology-Focused -> Long Lasting Architecture
- Decision-Making Process
  - Individuals Empowered -> Time to Market
  - Hierarchy -> Interoperability and integration
  - Consensus -> Team building

DO: understand how the organizational structure, culture and decision making processes impact your product

Planning: Make sure we are building the right thing?

Peanut Butter vs Skyscrapper

Peanut butter
Focus: features
Results: stability
Incremental improvements, not great end-to-end scenarios
Does not require so much interaction across teams

Skyscrapers
Focus: scenarios
Results: Excitement, breakthroughs, but beware of leaving existing customers behind
Requires interaction with other teams

AVOID: Peanut butter

Architecture: Ensuring the long term health of the framework
DO: Manage your dependencies

Component and Componentization
- A component is a set of types that ship and evolve as a unit
- Componentization is a process of organizing types into components, with explicitly designed controlled dependencies between components
- NOTE: Componentization is different from assembly factoring (ie an assembly might have more than one component)

Types of Dependencies
API Dependency
Component A has an API dependency on component B, if a type in B shows in the publicly accessible (public or protected) API surface of a type in A. This includes
- Base types and implemented interfaces
- Generic parameter constraints
- Return types and parameters of members
- Applied attributes
- Nested types

Implemented Dependency
If a type in A uses a type in B in its implementation
- Hard dependencies (required to run)
- Soft dependencies (optional)

Circular Dependency
Occurs when component A depends on Component B and component B depends on component A (even indirectly)
- Note: Two (or more) components with a circular API dependencies can be considered a single component for all practical purposes.

Framework Layering
Layering is a process of organizing components in layers and enforcing dependency rules between components in these layers
Distinguish between Core components these are components that are required throughout. This is important from a dependency point of view because it limits what can be replaced

Dependency Management Rules
-Types in a component can freely depend on each other
-Cross component dependencies must be carefully controlled
  - A component can freely take dependencies on components in a lower layer
  - A component must not have hard coded dependencies on components in higher layers
  - A component should avoid soft dependencies on components in higher layers
  - Dependencies between components of the same layer must be carefully managed.[NOTE: we have an approval process for this]
- In general it's good to minimize dependencies, if it does not create to much duplication (bloat)

Taxonomy of Types and Framework Architecture
- Primitives
- Abstractions
- Reusable Components

Primitives
- Very little policy (behavior design decisions)
- Stable design
- Commonly appear in publicly accessible APIs
- Almost impossible to evolve/change design, any design changes have huge breaking change impact on other APIs
- eg Ing32, String

DO: Use the least policy! because at sometime you might want to revisit that decision!!!

Abstractions
- Interfaces, abstract classes, some concrete classes with extensive virtual surface
- Similar to Primitives (show in APIs), but usually have more policy(through less than libraries)
- Hardest types to design right
  - With the exception on a few historically well understood abstractions (lists, streams etc) abstarctions with more that 2-3 members are rarely successful
- Difficult to evolve
- Glue between parts of the framework
  - Through polymorphism
- Very useful for decoupling components
- eg Stream, IEnumerable<T>
DO: Keep them extremely simple

Reusable Components
- Perform operations on primitives and abstractions (or the system)
- Almost never passed around
- Change often as frameworks evolve
  - XmlDocument(Fx 1.0 - XML DOM)
  - XPathDocuments(Fx 2.0 - XPath)
  - XDocument(Fx 3.5 - Linq to XML)
- Relatively easy to evolve (if designed and used properly) they can be simply replaced
- Eg XmlDocument, EventLog, SerialPort

Easy to evolve
DO NOT: Mixing types eg Primitives, Reusable components

Component Orientated Design
- Rich APIs with lots of features, thus with lots of dependencies
- Great usability, poor evolvability
- Eg Object.GetType() - Every object has a very easy access to its type but also every object depends on Reflection
- Good higher level components, not for the core of a platform
- NOTE: "Componenet" is an overloaded term. In this context it does not have anything to do with componentization. Unfortunately COD is already an established term

Primitive Orientated Design
- aka Handle base design (functional)
- Great evolvability, poor usability (sometimes)
- Low level stable primities + highlevel reusable components with limited dependencies other than to the primitives
- EG Type.GetType(object> - works but not as convenient as Object.GetType

Improving Usability of Primitive Oriented Design
- Members with heavy dependencies can be extension methods for primitives in a separate component
- This is essentially functional programming

// low level assembly with no dependency on globalization
namespace System
{
  public struct Decimal
  {
    public string ToString(); // Culture independent
  }
}

// higher level assemblynamespace System
{
  public static class DecimalFormatter
  {
    // decimal point character is culture sensitive
    public static string ToString(this.Decimal d, string format); // Culture independent
                                //====
  }
}

NOTE: Same namespace makes the API easy to use

DO: Balance advances with compatibility
People underestimate the advantages of compatibility. When you break something how much are you improving?

Types of Compatibility, which do we need
- Cross-Version Compatibility: Code written for a version of a redist works on a different version of the same redist
- Cross-Redist Compatibility: Code written for a redist works on a different redist
- Backward compatibility: Code written for a version of redist works on a newer version of the same redist
- Forward compatibility: Code written for a vcersion of a redist works on a previous version of the same redist

Forward is important for service components

- Binary Compatibility: a binary runs on a different version of a different redist than what it was build for without recompilation
- Source Compatibility: Source code compiling on a version of redist can recompile without changes on a different version of a different redist
- API Compatibility: Stronger than source. Weaker than binary. Source compatibility allows for some changes in APIs (ge covariant changes to input parameters). API Compatibility does not.

Establishing Compatibility Bar
- Define what is a breaking change
- This definition depends on the objective
  - Eg Enable portable code between Silverlight and .Net Framework
  - Eg Enable cross version portability
- For example, Silverlight interfaces cannot have less members than .NET interfaces, but concrete types can.

AVOID: Duplication and Overlap

When to replace existing APIs
- When the new technology is "10x better" (It cannot be a small improvement)
- Make sure you understand the impact on theecosystem
  - What would happen if the BCL team added a new String?
- Whats the migration path for code using the old API

Design: This is where quality happens

DO: Design APIs first by first writing code samples for the main scenarios and then define the object model to support the code samples
You cannot start with a UML Case tool and starting to decide entities and primitives of the system. Case tools are designed for internal implementation where you are trying to minimize ripple effects. API design has different constraints because once you ship it you can't change it

DO: Treat simplicity as a feature

Simplification Techniques
- Remove Requirements
- Reuse Existing Concepts or APIs
- Adjust Abstraction Level eg making an API for printer proporties -> Just get a XML document with Printer properties

Measure Measure Measure: It is not the act of measuring that is important its the decision what to measure that is important

Specification Document: Qualities
- Performance Goals
  - Baseline: What is the best my components could do?
  - Measure delta from the baseline
- Threat Models
  - Threat: What is the worst that my components could do
  - Mitigate the threats
- Same for many other qualities you want your framework to have
These are things that we want to design in eg security.

Development The bits customers get.. or not
Development happens in branches that periodically integrate into the product tree

AVOID: Integrating unfinished features
Because once you check it in people can take dependency from it

Feature Complete & Quality Gates
- Functional Specification
- Developer Design Specification¨(eg FxCop)
- Test Plan
- Threat Model
- API Review
- Architectural Review
- Dependency Management
- Static Analysis
- Code Coverage
- Testing (Unit and Integration)
- 0 bugs
- Performance

DO: Pay your debt
Stop from time to time to improve what you have implemented already

Milestone quality MQ
- Initiatives that are hard to do in regular milestones
- Large productivity and efficiency improvements
- Infrastructure changes
  - For example a new source control system
- Refactoring of fragile subsystems
- Internal implementation documents
- Bug backlog

What is the debt that we acquired during the last release? The framework is a fragile thing and it's important to get it right. This is a way to slow this down and stop

I