How to Design
Grady Booch
How to Design
Grady Booch
Why patterns?
Patterns help you build on the collective experience of many skilled software engineers
Defining Patterns
Each pattern describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem, in such a way that you can use this solution a million times over, without ever doing it the same way twice
Christopher Alexander - A Pattern Language, 1977
Patterns
Goal: practical advice and solutions to concrete design problems
A pattern is a standard solution to common and recurring design problems
Related: related (but different) to Object-Oriented Design Patterns
Hint: apply patterns where they actually solve a problem
Warning: as with all design decisions, all consequences of choosing to use a pattern should be evaluated and fully understood
Design Patterns
Capture the static and dynamic roles and relationships in solutions that occur repeatedly
Douglas C. Schmidt
Architectural Patterns
Express a fundamental structural organization for software systems that provide a set of predefined subsystems, specify their relationships, include the rules and guidelines for organizing the relationships between them
Architectural Styles
General constraints: Usually there is one dominant style for each architecture.
George Fairbanks
Architectural Patterns
Fine-grained constraints: Many patterns are usually combined. The same pattern can be used many times
Layered
- State-Logic-Display
- Model-View-Controller
- Presenter-View
State-Logic-Display
Goal: separate elements with different rate of change
cluster elements that change at the same rate
Pattern: apply the layered style to separate the user interface (display/client), from the business logic (server), from the persistent state (database) of the system
State-Logic-Display
Model-View-Controller
Goal: support many interaction and display modes for the same content
separate content (model) from presentation (output) and interaction (input)
Pattern: model objects are completely ignorant of the UI. When the model changes, the views react. The controller's job is to take the user's input and figure out what to do with it. Controller and view should (mostly) not communicate directly but through the model.
Model-View-Controller
Presenter-View
Goal: keep a consistent look and feel across a complex UI
extract the content from the model to be presented from the rendering into screens/web pages
Pattern: Apply separation of concerns. The presenter extracts and prepares the logical content to be displayed. The view formats the content consistently (possibly using templates).
Synonym: two-step View
Presenter-View
Components
- Interoperability
- Directory
- Dependency Injection
Interoperability
Goal: enable communication between different platforms
map to a standardized intermediate representation and communication style
Pattern: components of different architectural styles running on
different platforms are integrated by wrapping them with adapters. These adapters enable interoperability as they implement a mapping to common means of interaction.
Interoperability
Interoperability
- Why two adapters?
- For Point to Point solutions one adapter is enough (direct mapping)
- In general, when integrating among N different platforms, the total
number of adapters is reduced by using an intermediate representation
- Adapters do not have to be re-implemented for each component, but can be generalized and shared among all components of a given platform
- This pattern helps to isolate the complexity of having to deal with a different platform when building each component. The adapters make the heterogeneity transparent.
- Warning: performance may suffer due to the overhead of applying potentially complex transformations at each adapter
Directory
Goal: facilitate location transparency (avoid hard-coding service addresses in clients)
use a directory service to find service endpoints based on abstract descriptions
Pattern: clients lookup services through a directory in order to find out how and where to invoke them
Directory
Directory
- Clients cannot directly invoke services, they first need to lookup their endpoint for every invocation.
- Directories are a service in its own right (the difference is that its endpoint should be known in advance by all clients).
- The directory data model heavily depends on the context where the directory is applied and to the common assumptions shared by clients and services:
-
Simplest model: map
Symbolic Name
to IP:Port
- Directories can store complete service descriptions or only store references to service
descriptions
- Depending on the metadata stored in the directory, clients can perform sophisticated queries over the syntax and semantics of the published services
- Directories are a critical point for the performance and the fault-tolerance of the system. Clients usually cache previous results and repeat the lookup only on failure of the invocations.
- The amount of information stored in a directory may grow quite large over time and needs to be validated and kept up to date. Published services should be approved in order for clients to trust the information provided by the directory.
Dependency Injection
Goal: facilitate location transparency (avoid hard-coding service addresses in clients)
use a container which updates components with bindings to their dependencies
Pattern: clients expose a mechanism (setter or constructor) so that they can be configured
Dependency Injection
Dependency Injection
- Used to design architectures that follow the inversion of control
principle (“don't call us, we'll call you”, Hollywood Principle)
- Components are passively configured (as opposed to actively looking up services) to satisfy their dependencies:
-
Components should depend on required interfaces of services so that they are decoupled from the actual service implementations
- Flexibility:
- Systems are a loosely coupled collection of components that are externally
connected and configured
- Components could be reconfigured at any time (multiple times)
- Testability: Easy to switch components with mockups
Notification
- Event Monitor
- Observer
- Publish/Subscribe
- Messaging Bridge
- Half Synch-Half Asynch
Event Monitor
Goal: inform clients about events happening at the service
poll and compare state snapshots
Pattern: clients use an event monitor that periodically polls the service, compares its state with the previous one and notifies the clients about changes
Event Monitor
Event Monitor
- Necessary only if a service (or a component) does not provide a publish/subscribe interface
- To minimize the amount of information to be transferred, snapshots should be tailored to what events the client is interested in detecting. This may be a problem if the service interface cannot be changed.
- One event monitor should be shared among multiple clients by using the observer pattern
- Clients should be able to control polling frequency to reduce the load on the monitored service
- Warning: this solution does not guarantee that all changes will be detected, but only those which occur at most as often as the sampling rate of the event monitor
Observer
Goal: ensure a set of clients gets informed about all state changes of a service as soon as they occur
detect changes and generate events at the service
Pattern: clients register themselves with the service, which will inform them whenever a change occurs
Observer
Observer
- Assuming that a service can implement it, this pattern improves on the Event Monitor pattern:
-
State changes are not downsampled by clients
- State changes are propagated as soon as they occur
- If no change occurs, useless polling by clients is spared
- Clients could share the same endpoint with several observed services. Notifications should identify the service from which the event originates.
- To avoid unnecessary polling by the client, notification messages should also include information about the new state and not just that a state change has occurred.
- Warning: the set of registered clients becomes part of the state of the service and may have to be made persistent to survive service failures.
- A client which stops listening without unregistering should not affect the other
registered clients
Publish/Subscribe
Goal: decouple clients from services generating events
factor out event propagation and subscription management into a separate service
Pattern: clients register with the event service by subscribing to certain event types, which are published to the event service by a set of one or more services.
Publish/Subscribe
Publish/Subscribe
- The interaction between published and subscriber is similar to the Observer pattern. It is also more decoupled, as messages are routed based on their type.
- Warning: event types become part of the interface of a service, and it may be difficult to determine the impact of changes in event type definitions
- Like in the Observer pattern, subscriptions should be made persistent, as it is difficult for clients to realize that the pub/sub service has failed.
- Unlike with the Observer pattern, all events in the system go through a centralized component, which can become a performance bottleneck. This can be alleviated by partitioning the pub/sub service by event type.
Messaging Bridge
Goal: connect multiple messaging systems
link multiple messaging systems to make messages exchanged on one also available on the others
Pattern: introduce a bridge between the messaging systems, the bridge forwards (and converts) all messages between the buses.
Messaging Bridge
Messaging Bridge
- Enable the integration of existing multiple messaging systems so that applications do not have to use multiple messaging systems to communicate
- Often it is not possible to use a single messaging system (or bus) to integrate all applications:
-
Incompatible messaging middleware (JMS vs. MQ)
- External bus(es) vs. Internal bus
- One bus does not scale to handle all messages
- Although messaging systems may implement the same standard API, they are rarely interoperable so they cannot be directly connected.
- Messaging bridges are available (or can be implemented) so that messages can be exchanged between different buses. They act as a message consumer and producer at the same time and may have to perform message format translation.
Half-Synch/Half-Asynch
Goal: interconnect synchronous and asynchronous components
Add a layer hiding asynchronous interactions behind a synchronous interface
Pattern: The intermediate layer buffers synchronous requests using queues and maps them to the low-level asynchronous events.
Half-Synch/Half-Asynch
Half-Synch/Half-Asynch
Half-Synch/Half-Asynch
- Asynchronous events are more difficult to program with compared to synchronous function/method calls
- Asynchronous interfaces can give better performance:
- Closer to the hardware
- More efficient implementation
- Advantages:
- Simplify access to low-level asynchronous interfaces (no need to deal with
buffers, interrupts, and concurrency)
- Separation of Concerns (each layer can use the most efficient implementation)
- Centralized Interactions (all communication happens through the queues)
- Disadvantages:
- Potential performance loss due to layer crossing (data copy, context switching, synchronization)
Composition
- Scatter/Gather
- Canary Call
- Master/Slave
- Load Balancing
- Composition
Scatter/Gather
Goal: send the same message to multiple recipients which will (or may) reply to it
combine the notification of the request with aggregation of replies
Pattern: broadcast a message to all recipients, wait for all (or some) answers and aggregate them into a single message
Scatter/Gather
Scatter/Gather
- This is a simple composition pattern, where we are interested in aggregating replies of the components processing the same message.
- Alternatives for controlling the components:
- The recipients can be discovered (using subscriptions) or
be known a priori (distribution list attached to request)
- Warning: the response-time of each component may vary and the response-time
of the scatter/gather is the slowest of all component responses
- Different synchronization strategies:
- Wait for all messages
- Wait for some messages (within a certain time window, or using an N-out-of-M synchronization)
- Return fastest reply (discriminator, maybe only under certain conditions)
- Example:
- Contact N airlines simultaneously for price quotes
- Buy ticket from either airline if price<=200 CHF
- Buy the cheapest ticket if price >200 CHF
- Make the decision within 2 minutes
Canary Call
Goal: avoid crashing all recipients of a poisoned request
use an heuristic to evaluate the request
Pattern: try the potentially dangerous requests on one recipient and scatter the request only if it survives
Canary Call
Canary Call
Canary Call
- Performance/Robustness Trade-Off:
- Decreased Performance: the scatter phase waits for the canary call to succeed (response time doubles)
- Increase Robustness: most workers survive a poisonous call, which would have failed anyway
- Apply when worker recovery is expensive, or when there are thousands of workers involved
- Heuristic: Failed canary calls are not necessarily poisonous
Master/Slave
Goal: speed up the execution of long running computations
split a large job into smaller independent partitions which can be processed in parallel
Pattern: the master divides the work among a pool of slaves and gathers the results once they arrive.
Synonyms: Master/Worker, Divide-and-Conquer.
Master/Slave
Master/Slave
- This composition pattern is a specialized version of the scatter/gather pattern.
- Clients should not know that the master delegates its task to a set of slaves
- Master Properties:
- Different partitioning strategies may be used
- Fault Tolerance: if a slave fails, resend its partition to another one
- Computational Accuracy: send the same partition to multiple slaves and compare their results to detect inaccuracies (this works only if slaves are deterministic)
- Master is application independent
- Slave Properties:
- Each slave runs its own thread of control
- Slaves may be distributed across the network
- Slaves may join and leave the system at any time (may even fail)
- Slaves do not usually exchange any information among themselves
- Slaves should be independent from the algorithm used to partition the work
- Example Applications:
- Matrix Multiplication (compute each row independently)
- Movie Rendering (compute each picture frame independently)
- TSP Combinatorial Optimization (local vs. global search)
Load Balancing
Goal: speed up and scale up the execution of multiple requests of many clients
deploy many replicated instances of the server on multiple machines
Pattern: the master assigns the requests among a pool of workers, which answer directly to the clients.
Load Balancing
Load Balancing
- This composition pattern is similar to the master/worker pattern, but there is no partitioning of the request nor aggregation of the response
- Clients should not know that the master delegates its task to a set of workers
- Master Properties:
- Different load balancing policies may be used
- Fault Tolerance: if a slave fails, resend the request to another one (assuming idempotent failed requests can be detected by the master)
- Master and load balancing policies are application independent
- Worker Properties:
- Each worker runs its own thread of control
- Workers may be distributed across the network
- Workers may join and leave the system at any time (may even fail)
- Workers do not usually exchange any information among themselves
- Workers should be independent from the algorithm used to load balance the requests
- Variants:
- Stateless load-balancing (every request from any client goes to any worker)
- Session-based load-balancing (requests from the same client always go to the same worker)
- Elastic load balancing (the pool of workers is dynamically resized based on the amount of
work)
Composition
Goal: Improve reuse of existing applications
Build systems out of the composition of existing ones
Pattern: by including compositions as an explicit part of the architecture, it becomes possible to reuse existing services in order to aggregate basic services into value-added ones
Variants: Synchronous, Asynchronous
Synonym: Orchestration
Composition
Composition
- Composition is recursive: a composite service is a service that can be composed.
- Services involved in a composition do not necessarily have to know that they are being orchestrated as part of another service and may be provided by different organizations
- Services can be reused across different compositions and compositions can be reused by binding them to different services
- Composite services may be implemented using a variety of tools and techniques:
-
Ordinary programming languages (Java, C#, ...)
- Scripting languages (Python, PERL, ...)
- Workflow modeling languages (JOpera, BPEL...)
- Compositions may be long-running processes and involve asynchronous
interactions (both on the client side, and with the services)
Free Advice
The best architectures are full of patterns
Do not use too many unnecessary patterns
(this is an anti-pattern)
More Free Advice
Most software systems cannot be structured according to a single
architectural pattern.
Choosing one or more patterns does not give a complete software architecture.
Further refinement is needed.
References
- Paul Monday, Web Service Patterns: Java Edition, APress, 2003
- Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software, Addison- Wesley, 1994
- Frank Buschmann, Regine Meunier, Hans Rohnert, Peter Sommerlad, Patterns-Oriented Software Architecture, Vol. 1: A System of Patterns, John Wiley, 1996
- Martin Fowler, Patterns of Enterprise Application Architecture, Addison-Wesley, 2003
- Gregor Hohpe, Bobby Woolf, Enterprise Integration Patterns, Addison Wesley, 2004
Use a spacebar or arrow keys to navigate