Characterizing Observable Dependencies and Behaviors
Let’s now summarize the information needed to characterize the observable dependencies and behavior of a component.
Context places the component in question into the larger environment in which it must exist and with which it must interact. It defines the dependencies that the component has upon other components. For services, the consumer of the service is often shown only as an abstraction since there may be many service consumers. For components that are not services, the component may require specific interfaces on the consumer and is designed to work only with that consuming component. In such cases the type of the consuming component is explicitly shown.
Dependencies can be readily shown with an abstracted architecture pattern (Figure 5-18). The difference between this and a full architecture pattern is that the actual communications channels have been replaced with the more abstract <<use>> relationship. As the design is refined, these can be replaced with the more concrete communications channels.
Figure 5-18: Sales Order Service Context Showing Dependencies
This particular diagram indicates another area requiring refinement: The actual mechanisms for subscribing to the Product Change Notification and Customer Change Notification have not been defined, nor is it clear which participant will employ this mechanism to establish the subscriptions. In reality, the implementation of this activity may require manual configuration done at deployment time. The completed component dependency and behavioral description must indicate how this will happen.
The architecture pattern does not indicate how the component (in this case the Sales Order Service) functionally participates in the business processes that comprise the overall solution. For this you need to understand the scenarios that involve the component and show its involvement in the solution’s operation. Process-pattern mappings similar to that of Figure 5-1 are well suited for this purpose. For each triggering event of the component, there should be at least one example of a scenario in which the component is expected to participate. If reusability is an issue and the usages are different, there should be a scenario illustrating each type of usage.
To fully characterize the component, more is required than simply the scenario. You need to know how often each scenario occurs, the expected execution time of the scenario, and the required availability of the scenario. It is from this information that the corresponding throughput, response time, and availability characteristics of the component will be derived.
Particularly in the case of services, it is not unusual for some of the scenarios to be speculative, representing potential future usages. Nevertheless, these scenarios need to be documented along with working assumptions about their associated performance and availability characteristics.
From a practical perspective, usage scenarios may only show the fragment of the larger business process in which the component actually participates. However, if the process requires multiple interactions with the component, it is important that the usage scenario span these multiple interactions. If significantly different sequences must be supported, each sequence must be documented.
The behavior of a component is a description of the sequences of interactions it can have with the components upon which it depends along with the details of those interactions. In most circumstances, this behavior can be readily documented using one or more UML Activity diagrams. The diagrams indicate the behavior’s trigger along with the resulting responses, inputs, outputs, and observable state changes.
Figure 5-7 is an example of a triggered behavior. Its trigger is the invocation of the placeOrder() process, and its initial input is the Place Order Request. Responses include the calls to validate-ProductID(), getCustomer(), and obtainPayment(); the return of the Place Order Response; and the invocation of the fillOrder() operation. The data structures associated with these operations provide details of the interactions. Prior to sending the Place Order Response, the order information is saved and becomes part of the component’s observable state.
This, of course, is just one possible behavior for this trigger. Other scenarios are required to describe the expected behavior when one or more of the dependent components becomes unavailable or returns unexpected results.
To fully characterize a component’s behavior, a triggered behavior description is required for each possible trigger. Triggering events may include the invocation of interface operations, the receipt of notifications, the expiration of timers, and component life-cycle events such as start, stop, deployment, and un-deployment. In the Sales Order Service example, it is likely that the subscriptions to product and customer change notifications would actually be made via configuration changes implemented as part of the deployment process. Here the deployment would be the event, and one of the participants in the process is the person doing the deployment.
The observable state of a component reflects the information or other types of status (such as physical machine state) that can be altered or viewed by interactions among the component and other components. A model of this state information will help the user of the component understand the component’s behavior. The model should clearly distinguish between information for which the component is the system of record and information that is a cached copy of information originating in another component.
Figure 5-15 is an example of an observable state model related to the Sales Order Service. It shows the information for which the Sales Order Service is the system of record and the information that it has cached from other components. It also indicates the relationship between the cached information and the system-of-record information from which it is derived.
Some state information can be a derived summary of information that is distributed across a number of components. The status of the Sales Order is such an example. When this type of information is present, the allowed values that it can assume must be modeled (Figure 5-14), and the triggers and triggered behaviors that result in its update must also be captured (Figures 5-9 and 5-11).
The work of a component does not occur in isolation, and the performance of this work needs to be coordinated with that of other components in the solution. Consequently, the available coordination approaches are a significant part of the component’s observable behavior.
Some coordination patterns are readily captured in the modeling of individual triggered behaviors. For example, in the placeOrder() process of Figure 5-7 it is clear that the interaction between the service consumer and the Sales Order Service uses synchronous request-reply coordination.
Other coordination patterns may involve multiple triggering events and therefore multiple triggered behaviors. For example, place-Order() sends a fillOrder() request to the Order Fulfillment Service, but the responses from the Order Fulfillment Service are returned asynchronously. These interactions involve the Order Fulfillment Services’s invocations of the orderShipped() (Figure 5-9) and orderDelivered() (Figure 5-11) operations. The overall coordination is only apparent when the usage scenario (Figure 5-1) is considered.
Capturing coordination is important because changing coordination patterns involves changes in both components. In the Sales Order Service example, shipment and delivery notices are delivered to the Sales Order Service by calling operations on its Sales Order Status Interface. This makes the design of the Order Fulfillment Service specific to the Sales Order Service.
There is an alternative approach. Consider a situation in which other components in addition to the Sales Order Service need to know about shipments and deliveries. With the present design, accommodating this requirement would necessitate an Order Fulfillment Service change to individually notify each of the additional components.
Alternatively, the Order Fulfillment Service could provide a subscription interface where any component could register to be informed about shipments and deliveries (Figure 5-19). Thus any number of components could subscribe without requiring any design changes in the Order Fulfillment Service. However, to switch to this design the Sales Order Service has to be modified to utilize the new approach to learning about shipments and notifications.
Figure 5-19: Alternative Design for Shipment and Delivery Notification
Usage scenarios show allowed sequences of interactions with the component, but they do not illustrate sequences that are not allowed. These need to be documented as well.
Consider the Sales Order Service Interface shown in Figure 5-20. This interface has some obvious constraints upon its usage. You can’t get, modify, or cancel an order that hasn’t been placed. However, there may be some less obvious constraints. Depending upon business rules, you may not be able to modify or cancel an order that has already shipped. Users of a component need to understand these constraints.
Figure 5-20: Full Sales Order Service Interface
A component may provide all the functionality required for a usage scenario, but still may not be suitable for nonfunctional reasons. Its throughput capability may be insufficient to support the volume of activity required by the scenario or its response time may be inadequate. The availability of the component may not be sufficient to give the usage scenario its required availability.
Nonfunctional requirements are not arbitrary—they are (or should be) derived from the business requirements. The connection between the business requirements and the individual components is established through the usage scenarios. For example, the business might require that customers be able to place orders at a peak rate of 100 per second. With reference to Figure 5-1, this means that the Sales Order Service must be able to accept order requests at this rate. If the business requires that orders be acknowledged within three seconds, this means that the Sales Order Service must be able to validate orders and obtain payment within three seconds with orders coming in at a rate of 100 per second. Similar reasoning can be used to determine the rate at which shipment and delivery notices will occur and be processed.
This same type of thinking applies to other types of nonfunctional requirements as well. If the business requires that online ordering capability be available 24/7, then this means that the Sales Order Service must be available 24/7. Availability, outage time restrictions, and security requirements must also be connected back to the business requirements via the usage scenarios.
There is another reason for establishing this connection between business requirements and component requirements: It captures the design assumptions that went into specifying the component. When a new utilization for the service comes along, this makes it easy to determine whether the new usage is consistent with the original design assumptions. If it is not, then it is necessary to open the black box and determine whether the actual design is capable of meeting the new requirements.
For all of these reasons, it is important to document the nonfunctional behavior of the component. It is an observable characteristic of the component.