Com S 362 Lecture Outline ------------------------------------------------------- SOFTWARE ARCHITECTURE Reading: Introduction to Software Architecture by David Garlan and Mary Shaw Motivation: --------------------- Let us suppose that you are asked to write two programs. - A program to sort numbers - A program to merge e-mail lists in alphabetical order Q - What are the key concerns/issues in writing these programs? ---------------------- Q - How are we going to store the numbers to be sorted? Q - How are we going to store the e-mails to be merged? In other words, data structures to be used by the programs. Key constraints in data structure selection: - Size: Fixed/Variable - Access: Sequential/Random Access - Time complexity: Insertion time/ Retrieval time/Manipulation time - Space complexity Q - What techniques are we going to use to sort the numbers/merge the e-mail list? In other words, algorithm to be used by the programs. Key constraints in algorithm selection: - Time complexity - Space complexity - Ease of implementation ---------------------------- Let us suppose, you are asked to construct [an inventory management system] [a library administration system] [a patient monitoring system] [Your favorite medium to large system] Q - What are the key concerns/issues in the construction of these systems? -------------------------------- - How is the system going to be divided? i.e. what are the components of this system? - What is the global control structure? Which component receives control first? How is then control transferred between components? Which component interacts with the users? - How are components sharing data? Mutually exclusive/Shared memory/... - How are components interacting with each other? Protocols: Synchronous/Asynchronous/.. - Where are components physically located? - What are the scalability requirements of this system? What range of inputs does it need to handle? Determines deployment to some extent. - What are the availability requirements of this system? Determines redundancy in component deployment. -------------------------------- The issues above need to be addressed at a higher level of design. This is where software architecture level of design comes into play. --------------------------------- [Garlan and Shaw]: Software architecture involves the description of elements from which systems are built, interactions among these elements, patterns that guide these interactions, and constraint on these patterns. - Overall organization of the system - At a much higher-level of abstraction compared to code and even design Terminology: - Component: constituents of the system - Connectors: Description of the interactions among these components. - Patterns: Patterns of interaction among components - Constraints: Constraints on interaction among components Some Common Architectural Styles discussed in the reading material - Pipe Filter Systems Example: Unix processes - Event Based Implicit Invocation Example: Roots in interrupt driven architecture in kernels - Layered Systems Example: Operating Systems, Network protocol stack) - Data Abstraction and Object-Oriented Organization - Repositories (Also called blackboard) - Interpreters - Process Control Some Recent Architectural Styles (not discussed in the reading material) - Client Server Example: Most Network applications - E-mail, WWW, etc. - Peer-to-peer Example: Gnutella, etc. - Plugin Architecture Example: Eclipse Workbench for Java - Three-tiered model Example: Most e-commerce applications - Service-oriented Architecture Example: J2EE and .NET Web-Services -------------------------------------------------------- SUMMARY OF CONCEPTS SO FAR - Programming in the small vs. Programming in the large - While programming in the small data structures and algorithmic techniques are important, however, while programming in the large it is important to focus on other important details such as: > Components involved in the system > How are these components connected? (Connectors) > What are the interaction patterns between these components? > What are the constraints (if any) on the interaction? - This is the software architecture level of design -------------------------------------------------------- PIPE-FILTER ARCHITECTURE - Each components has a set of inputs and outputs - Reads input streams and produces output stream - Often functionality requires local transformation - Constraints: > Filters are independent, i.e. do not share states > Do not know the identity of other filters upstream and downstream - Advantages: > Allows to study system as a composition of behaviors of individual filters > Support reuse > Easy to maintain and enhance these systems > Naturally support concurrent execution - Disadvantages: > Often lead to batch organization of processing, not good at handling interactive applications > An overly general format for input and output data format is required > The system is as slow as the slowest filter - Example: Consider the unix utilities ls and wc Components: ls and wc Can be connected by pipe: ls | wc -l ls generates output, wc takes the ls’s output as input processes it and generates output - Exercise: Analyze ls and wc with respect to the advantages and disadvantages of pipe-filter systems. - Example: Consider a typical compiler - Consists of scanner, parser, semantic analyzer, code generator - Output of each component is the input of the next component - The components make assumptions about the input, so not an ideal pipe-filter system. --------------------------------------------------------- LAYERED SYSTEMS - Hierarchical organization of components - Components use services of components in the layer below - Components provide services to components in the layer above - Connectors defined by the protocol between layers, often we we talk about well-defined interface between layers that is agreed upon by the teams working on the layer under design and the layer that is going to use it. E.g. intermediate languages in case of virtual machine, Internet Engineering Task Force (IETF) and Request for Comments (RFCs), etc. Exercise: Look up the RFC for IP Version 6 Addressing Architecture - Constraints: > Components may not use the services of components in the layer above their layer, e.g. A file system component in an operation system may not use the services of an application component > Components (optionally) may not use the services of layer more than one layer below their layer, e.g. applications using a network protocol stack such as TCP/IP stack may not access the services of Ethernet component in the physical layer - Advantages: > Support abstraction: Hierarchical organization into layers abstract the details of components in the layer below > Eases Evolution: Layers are only interacting with adjacent layers, therefore the impact of change in a layer is limited (See disadvantages: worm-hole pattern) > Support Reuse: A layer can be replaced, as long as it complies with the interfaces required and provided - Disadvantages: > Structuring difficulties: Not all applications can be structured as layers. The boundary or the interface between layers often conflict with the need for interaction among layers. For example, consider a fast file transfer protocol. To be efficient it requires access to the lower layers in the network protocol stack. Consider, 3-D games. For faster and flicker free experience they often need to directly write to the graphics memory on the graphics controller. Efficiency consideration often requires closer coupling. > Find the right level of abstractions may be difficult, often this is the problem with evolving systems. For example, libraries that are not new, may have problem anticipating all possible usage. > Wormhole pattern: Often a context needs to be passed from components in the upper layers to component in the layers below, which in turn passes the context to the layers below. Exercise: For example, consider the typical organization of a current application. Application runs on middleware such as CORBA, which in turn often runs on a virtual machine such as Java Virtual Machine that in turn executes on an OS kernel such as Linux, which uses the services of components such as file-system, virtual memory system, network protocol stack, etc. These components finally use the services of components such as device drivers. The device drivers use the services of hardware components. In this context, let us assume that an application opens a file by providing a file name and file path. Think about what happens to the argument file name? - Examples: Network protocol stack, OS, VM, etc Repositories a.k.a Active Databases/Blackboard Architecture: - Two different kind of components: > Central shared data store > Components that manipulate shared data - Two different connector types > Databases: transactions in input stream triggers component > Blackboards: only the current state of the data source triggers components - Constraints: > Components may not interact with each other except implicitly through the data store - Advantages: > Supports concurrency: various components may be active concurrently, as long as mutual exclusion is maintained between the data sources that they share. > Eases Evolution: Components independent of each other so new components can be added without changing other. - Disadvantages: > Connectors have to be coerced to fit the database mechanism even when it may seem more natural for components to directly communicate Exercise: Read about the communication bus in the context of computer architecture --------------------------------------------------------- IMPLICIT INVOCATION DESIGN STYLE Three type of relationships between components. 1. Name dependence - Often due to usage of a component in other component. Usage may be of different kinds. - Leads to coupling, component cannot be compiled, linked, tested, etc without having all other components on which it is name dependent. - Introduces an order on software development process, impacting the parallelism. 2. Call Relationship - A calls a method on component B. Often to get a subtask completed. - One cause of name dependence. 3. Invoke Relationships - A and B are in invoke relationship iff A's execution is eventually followed by B. ----------------------------------------------- Q - How do you make sure that A invokes B but A is not dependent on B? ----------------------------------------------- Q - Why is important to have such a relation between components? ---------------------------------------------- Example: Network protocol stack Functionality: Send/receive message Components involved: Protocol Stack, Send Mail Daemon, httpd (collectively called applications) Design goals: Application can be name dependent on protocol stack, but protocol stack cannot be name dependent on application. Why? - Sending is straightforward, application calls the send method on the protocol stack, passing the message to be sent, receives a status code. - Receiving a little complicated, application/protocol stack doesn't know when somebody else out there will send a message. - Alternative designs: > Have protocol stack call application, when it gets the message, but that violates our design goal. > Have protocol stack set a status variable, when it receives the message. The application continuously observes the value of the status variable. Retrieves the message, when it is set. Requires constant polling, which is not very efficient - So how do we achieve our design goal? --------------------------------------------- Q - Do you remember C++ function pointers? Pointer variables that point to the address of functions. All such functions that can be pointed to must have a specific signatures, i.e. the return type, number of arguments, and argument types. When you call a function using the function pointer you may not know its name, but still call it. --------------------------------------------- An example design that solves our problem above and also satisfies our design goal! TCP/IP Stack > Maintains a list of function pointers > Provides a method to iterate through this list and invoke each method pointed to by the elements of the list. > Provides a method to add/remove elements to/from the list. These methods take a function pointer as argument. Applications > Declare a method that is responsible for receiving messages. (lets say rcv) > Use the method add/remove supplied by the TCP/IP stack. > Supply a function pointer to rcv to add method of the TCP/IP stack. Q - How does this design satisfies our design goal? Q - Is TCP/IP Stack name dependent on rcv? Q - Is rcv name dependent on TCP/IP Stack? ------------------------------------------------- What we just emulated is an implementation of an abstraction that is often referred to as events. Using events we organized the components in our system into an architectural style known as "implicit invocation systems". Using the terminology used while describing this architectural style, we can describe our example design above as follows: Have TCP/IP Stack "declare" and "announce" an "event" [message received]. Have Applications "register"/"deregister" with the "event" [message received] to receive "notifications". From the example design description, it follows that the event abstraction can be informally defined as providing three operations: - Announcement - Registration - Deregistration ------------------------------------------------- Event-Based Implicit Invocation Systems: - Components provide operations as well as events (Announcers) - Other components can register with events - Announcers notify all registrants when event occurs - Notification happens "implicitly" in that there is no name dependence from the notifying component to the notified component - Constraints: > Announcers do not know about which component might be affected by the event - Advantages: > Significant Reuse: Introduce a component in the system by just registering with the event of interest > Eases Evolution: Announcers are not dependent on the registrants, so registrants can be replaced without any impact on announcers - Disadvantages: > Component relinquishes control > Exchange of data > Reasoning about correctness becomes a problem - Example: Consider a debugger, an editor, variable watcher Event: A breakpoint is reached in component debugger Interested party: editor, variable watcher Action on event: Scroll to line #, Refresh the value of variables - Example: Consider a compiler, an editor, and a task list Event: An error in compilation in component compiler Interested party: editor, task list Action on event: Highlight the construct that has error, add the error to task list of variables